1. 简介

early_irq_init完成中断子系统软件部分初始化。

irqchip_init初始化中断控制器,向系统注册struct irq_domain

irq_intc_init

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_MAXstruct 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_initacpi_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,其主要工作如下:

  1. 设置中断回调函数
  2. irq_domain_create_linear注册irq_domain
  3. 初始化硬件
  4. 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,其主要工作如下:

  1. 注册irq_domain
  2. irq_domain_create_tree注册irq_domain
  3. 设置中断回调函数
  4. 初始化硬件
  5. gic_smp_init使用SGI的前8号中断作为IPI,并建立virq和struct irq_data的映射关系
  6. ITS(Interrupt Translation Service)相关初始化
  7. gic_enable_nmi_support使用fiq来实现NMI中断

《IHI0069H_gic_architecture_specification.pdf》中中断号划分如下。