1. 简介
early_irq_init
完成中断子系统软件部分初始化。
irqchip_init
初始化中断控制器,向系统注册struct irq_domain
。
2. 中断软件子系统初始化
early_irq_init
初始化数组或树,用于保存virq到struct irq_desc
的转换关系。
irq_to_desc
用于将virq转换为struct irq_desc
指针。
2.1. 线性映射
未定义CONFIG_SPARSE_IRQ时使用线性映射,使用数组实现,静态分配,支持的最大virq由NR_IRQS决定。
1struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
2 [0 ... NR_IRQS-1] = {
3 .handle_irq = handle_bad_irq,
4 .depth = 1,
5 .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
6 }
7};
8
9struct irq_desc *irq_to_desc(unsigned int irq)
10{
11 return (irq < NR_IRQS) ? irq_desc + irq : NULL;
12}
13EXPORT_SYMBOL(irq_to_desc);
2.2. 非线性映射
定义CONFIG_SPARSE_IRQ时使用非线性映射,使用树实现,早期版本使用radix tree
,linux-6.6使用maple tree
。支持的最大virq由MAX_SPARSE_IRQS
决定,其值为INT_MAX
。struct irq_desc
在注册设备中断时动态分配。
1/// kernel/irq/irqdesc.c
2static DEFINE_MUTEX(sparse_irq_lock);
3static struct maple_tree sparse_irqs = MTREE_INIT_EXT(sparse_irqs,
4 MT_FLAGS_ALLOC_RANGE |
5 MT_FLAGS_LOCK_EXTERN |
6 MT_FLAGS_USE_RCU,
7 sparse_irq_lock);
8/// ... ...
9struct irq_desc *irq_to_desc(unsigned int irq)
10{
11 return mtree_load(&sparse_irqs, irq);
12}
13#ifdef CONFIG_KVM_BOOK3S_64_HV_MODULE
14EXPORT_SYMBOL_GPL(irq_to_desc);
15#endif
16/// ... ...
17static void irq_insert_desc(unsigned int irq, struct irq_desc *desc)
18{
19 MA_STATE(mas, &sparse_irqs, irq, irq);
20 WARN_ON(mas_store_gfp(&mas, desc, GFP_KERNEL) != 0);
21}
3. 中断控制器初始化
先进行软件初始化,再进行硬件初始化。核心函数 of_irq_init
,遍历 __irqchip_table
段,扫描设备树中可以匹配的中断控制器节点,并调用对应的初始化函数。
1/// drivers/irqchip/irqchip.c
2void __init irqchip_init(void)
3{
4 of_irq_init(__irqchip_of_table);
5 acpi_probe_device_table(irqchip);
6}
irqchip_init
使用of或者使用acpi获取中断控制器的设备树节点,并调用 of_irq_init
和 acpi_probe_device_table
进行初始化。
3.1. IRQCHIP_DECLARE
所有的中断控制器初始化函数由IRQCHIP_DECLARE
修饰,在链接时放到vmlinux的__irqchip_of_table
段中。
通过搜索IRQCHIP_DECLARE
,可以找到所有的中断控制器初始化函数。如下为gic相关的初始化函数。
1# git grep --heading -Inw IRQCHIP_DECLARE drivers/irqchip/irq-gic*
2drivers/irqchip/irq-gic-realview.c
376:IRQCHIP_DECLARE(armtc11mp_gic, "arm,tc11mp-gic", realview_gic_of_init);
477:IRQCHIP_DECLARE(armeb11mp_gic, "arm,eb11mp-gic", realview_gic_of_init);
5drivers/irqchip/irq-gic-v3.c
62328:IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
7drivers/irqchip/irq-gic.c
81516:IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
91517:IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic", gic_of_init);
101518:IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic", gic_of_init);
111519:IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
121520:IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
131521:IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);
141522:IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
151523:IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);
161524:IRQCHIP_DECLARE(pl390, "arm,pl390", gic_of_init);
vmlinux.lds.h
控制链接布局。
1/// include/asm-generic/vmlinux.lds.h
2#define ___OF_TABLE(cfg, name) _OF_TABLE_##cfg(name)
3#define __OF_TABLE(cfg, name) ___OF_TABLE(cfg, name)
4#define OF_TABLE(cfg, name) __OF_TABLE(IS_ENABLED(cfg), name)
5#define _OF_TABLE_0(name)
6#define _OF_TABLE_1(name) \
7 . = ALIGN(8); \
8 __##name##_of_table = .; \
9 KEEP(*(__##name##_of_table)) \
10 KEEP(*(__##name##_of_table_end))
11
12#define TIMER_OF_TABLES() OF_TABLE(CONFIG_TIMER_OF, timer)
13/// __irqchip_of_table段
14#define IRQCHIP_OF_MATCH_TABLE() OF_TABLE(CONFIG_IRQCHIP, irqchip)
15#define CLK_OF_TABLES() OF_TABLE(CONFIG_COMMON_CLK, clk)
16#define RESERVEDMEM_OF_TABLES() OF_TABLE(CONFIG_OF_RESERVED_MEM, reservedmem)
17#define CPU_METHOD_OF_TABLES() OF_TABLE(CONFIG_SMP, cpu_method)
18#define CPUIDLE_METHOD_OF_TABLES() OF_TABLE(CONFIG_CPU_IDLE, cpuidle_method)
IRQCHIP_DECLARE
的实现,细节可以自行分析。
1/// include/linux/irqchip.h
2/*
3 * This macro must be used by the different irqchip drivers to declare
4 * the association between their DT compatible string and their
5 * initialization function.
6 *
7 * @name: name that must be unique across all IRQCHIP_DECLARE of the
8 * same file.
9 * @compat: compatible string of the irqchip driver
10 * @fn: initialization function
11 */
12#define IRQCHIP_DECLARE(name, compat, fn) \
13 OF_DECLARE_2(irqchip, name, compat, typecheck_irq_init_cb(fn))
3.2. gic-v2
gic-v2的初始化函数是gic_of_init
,其主要工作如下:
- 设置中断回调函数
- irq_domain_create_linear注册irq_domain
- 初始化硬件
- gic_smp_init使用SGI的前8号中断作为IPI,并建立virq和
struct irq_data
的映射关系
如下内容摘自CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual
SGIs are generated by writing to the Software Generated Interrupt Register, GICD_SGIR. Each CPU interface can generate a maximum of 16 SGIs, ID0-ID15, for each target processor.
gic-v2
支持中断级联,级联的中断控制器与普通的中断控制器初始化代码都是gic_of_init
,只是加了一个判断。
1/// drivers/irqchip/irq-gic.c
2int __init
3gic_of_init(struct device_node *node, struct device_node *parent)
4{
5 /// ... ...
6
7 /// 用于级联中断控制器的初始化
8 if (parent) {
9 irq = irq_of_parse_and_map(node, 0);
10 gic_cascade_irq(gic_cnt, irq);
11 }
12
13 /// ... ...
14
15 gic_cnt++;
16 return 0;
17}
3.3. gic-v3
gic-v3的初始化函数也是gic_of_init
,其主要工作如下:
- 注册irq_domain
- irq_domain_create_tree注册irq_domain
- 设置中断回调函数
- 初始化硬件
- gic_smp_init使用SGI的前8号中断作为IPI,并建立virq和
struct irq_data
的映射关系 - ITS(Interrupt Translation Service)相关初始化
- gic_enable_nmi_support使用fiq来实现NMI中断
《IHI0069H_gic_architecture_specification.pdf》中中断号划分如下。