大家好,我是王鸽,本文主要分析了linux kernel中GIC中断控制器的驱动代码(位于drivers/irqchip/irq-gic-v3.c和irq-gic-common.c)。 irq-gic-common.c中是GIC V2和V3的通用代码,而irq-gic.c是V2 specific的代码,irq-gic-v3.c是V3 specific的代码。之前上一篇文章有介绍有简单介绍过中断控制器,GICv3(ARM Generic Interrupt Controller v3)是面向 ARMv8‑A/R 架构的中断控制器,采用分层分布式组件架构,核心由 Distributor、Redistributor、CPU Interface、ITS 四大模块构成,驱动以 Linux 内核irq‑gic‑v3.c为核心实现,覆盖硬件初始化、中断路由、LPI/ITS 管理与虚拟化适配。GICv3 按中断类型与处理层级划分为四大核心组件,形成 “全局分发→本地重分发→CPU 接口→消息转换” 的完整中断处理链路。 | | | | |
|---|
| GICD | | 全局中断仲裁、路由配置、优先级管理、中断使能 / 禁用、触发方式配置 | | |
| GICR | | 私有中断管理、LPI 配置、CPU 电源管理、中断状态缓存 | PPI(私有外设中断,ID:16~31)、SGI(软件生成中断,ID:0~15)、LPI(局部外设中断,ID:8192+) | 每个 CPU/PE 独立实例,支持 core 休眠时缓存 pending 中断 |
| GICC | | 中断应答 (ACK)、结束 (EOI)、优先级掩码、中断抢占控制 | | 集成于 CPU 内核,通过系统寄存器访问,替代 GICv2 的 CP15 接口 |
| ITS | Interrupt Translation Service | 消息中断转换、设备 ID→LPI 映射、MSI-X 虚拟化 | | |
中断类型与路由逻辑
SGI
软件触发,用于 CPU 间通信(IPI),经 GICD 路由至目标 GICR→GICC 处理。PPI
绑定单 CPU 的私有外设中断(如本地定时器),直接路由至对应 GICR→GICC。
SPI
全局共享外设中断,经 GICD 仲裁后路由至目标 GICR→GICC。
LPI
消息式中断,经 ITS 转换后,由 GICR 通过内存表(PROPBASER/PENDBASER)管理,适配高并发消息中断场景。
。GICv3 控制器内部模块和各中断类型的关系如下图所示:
中断处理流程1. 外设发起中断,发送给 Distributor2. Distributor 将该中断,分发给合适的 Redistributor3. Redistributor 将中断信息,发送给 CPU interface4. CPU interface 产生合适的中断异常给处理器5. 处理器接收该异常,并且软件处理该中断gic: interrupt-controller@2c001000 {compatible = "arm,gic-v3";#interrupt-cells = <3>;#address-cells = <2>;interrupt-controller;reg = <0x0 0x2c001000 0 0x1000>,<0x0 0x2c002000 0 0x2000>,<0x0 0x2c004000 0 0x2000>,<0x0 0x2c006000 0 0x2000>;interrupts = <1 9 0xf04>;};
节点“ interrupt-controller@2c001000”描述中断控制器的信息, “ gic”是标号。( 1)属性“ compatible”:值是字符串列表,用来匹配驱动程序。第一个字符串指定准确的设备名称,后面的字符串指定兼容的设备名称。( 2)属性“#interrupt-cells”:指定属性“ interrupts”的单元数量,一个单元是一个 32位整数。属性“#interrupt-cells”的值为 3,表示属性“ interrupts”用 3 个 32 位整数描述。( 3)属性“ interrupt-controller”:表示本设备是中断控制器。( 4)属性“ reg”:描述中断控制器的寄存器的物理地址范围,第一个物理地址范围是分发器的,第二个物理地址范围是处理器接口的。 <0 0x2c001000 0 0x1000>表示起始地址是“ 0 0x2c001000”,长度是“ 0 0x1000”。(5)属性“interrupts”:包含 3 个单元,依次描述中断类型、硬件中断号和中断触发方式。处理器有 4 个核,每个核对应一个定时器。第 1 个单元是中断类型, 值为 1 表示中断类型是私有外设中断, 参考头文件“ scripts/dtc/include-prefixes/dt-bindings/interrupt-controller/arm-gic.h” 定义的宏: GIC_SPI 为 0, GIC_PPI为 1。第 2 个单元是硬件中断号, 4 个核的定时器分别使用硬件中断号 13、 14、 11 和 10。第 3 个单元是标志位组合,参考头文件“ include/linux/irq.h”定义的标志位, 0xf08 是以下标志位的组合关于设备数的各个字段含义,详细可以参考 Documentation/devicetree/bindings 下的对应信息。初始驱动代码位于drivers/irqchip/irq-gic-v3.c中,设备树和驱动匹配起来。IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
定义 IRQCHIP_DECLARE 之后,相应的内容会保存到 __irqchip_of_table 里边:#define IRQCHIP_DECLARE(name,compstr,fn) \ static const struct of_device_id irqchip_of_match_##name \ __used __section(__irqchip_of_table) \ = { .compatible = compstr, .data = fn }
编译 ARM64 架构的内核时,链接器执行下面的链接脚本,使用全局变量__irqchip_of_table 存放节“ __irqchip_of_table”的起始地址,也就是中断控制器匹配表的起始地址。arch/arm64/kernel/vmlinux.lds.S…__initdata_begin = .;.init.data : {INIT_DATA…}…include/asm-generic/vmlinux.lds.h#defineINIT_DATA \…IRQCHIP_OF_MATCH_TABLE() \…把 IRQCHIP_OF_MATCH_TABLE()展开以后是. = ALIGN(8); \__irqchip_of_table = .; \KEEP(*(__irqchip_of_table)) \KEEP(*(__irqchip_of_table_end))说白了,在内核初始化的时候,匹配设备树文件中的中断控制器的属性“compatible”和内核的中断控制器匹配表,找到合适的中断控制器驱动程序,执行驱动程序的初始化函数。函数 irqchip_init把主要工作委托给函数 of_irq_init,传入中断控制器匹配表的起始地址__irqchip_of_table。start_kernel() -> init_IRQ() -> irqchip_init()drivers/irqchip/irqchip.cvoid __init irqchip_init(void){ of_irq_init(__irqchip_of_table);…}static int __init gic_of_init(struct device_node *node, struct device_node *parent){- dist_base = of_iomap(node, 0); //映射GICD的寄存器地址空间 reg = readl_relaxed(dist_base + GICD_PIDR2) & GIC_PIDR2_ARCH_MASK; if (reg != GIC_PIDR2_ARCH_GICv3 && reg != GIC_PIDR2_ARCH_GICv4) { pr_err("%s: no distributor detected, giving up\n", node->full_name); err = -ENODEV; goto out_unmap_dist; } if (of_property_read_u32(node, "#redistributor-regions", &redist_regions))//通过 DTS 读取 redistributor-regions 的值。 redist_regions = 1; redist_base = kzalloc(sizeof(*redist_base) * redist_regions, GFP_KERNEL); if (!redist_base) { err = -ENOMEM; goto out_unmap_dist; } for (i = 0; i < redist_regions; i++) {//为一个 GICR 域分配基地址 redist_base[i] = of_iomap(node, 1 + i); if (!redist_base[i]) { pr_err("%s: couldn't map region %d\n", node->full_name, i); err = -ENODEV; goto out_unmap_rdist; } } if (of_property_read_u64(node, "redistributor-stride", &redist_stride)) redist_stride = 0;// GIC 初始化流程,执行完成后,GIC 中断控制器即可正常工作:外部外设中断、软件中断(SGI)会被正确分发到指定 CPU,并触发你注册的中断处理逻辑。 set_handle_irq(gic_handle_irq);/将GIC 中断处理的顶层入口函数 gic_handle_irq 注册到系统的中断向量表中 gic_smp_init();/初始化多核(SMP)环境下的 GIC 中断路由与同步机制,让中断能够被正确分发到指定的 CPU 核心 gic_dist_init();/初始化 GIC 分发器(GICD,Distributor),这是 GIC 的 “全局中枢” gic_cpu_init();/初始化当前执行该函数的 CPU 核心对应的 GIC CPU 接口(GICC,CPU Interface) return 0;
set_handle_irq()绑定软件入口,gic_smp_init()适配多核,gic_dist_init()初始化全局分发器,gic_cpu_init()初始化本地 CPU 接口。
GICv3 在该流程基础上,新增了 GICR/ITS 的初始化,且寄存器访问方式从内存映射改为系统寄存器。