2. 中断控制器与GIC:GIC架构、中断分发、中断路由、SPI/PPI/SGI

2.1 GIC在中断子系统中的位置

通用中断控制器(Generic Interrupt Controller, GIC)是ARM架构中管理中断的核心硬件单元。它位于外设与CPU核心之间,负责中断的收集、优先级排序、分发与路由。驱动开发者理解GIC架构,是正确注册中断、配置触发方式、保证并发安全的前提。

核心认知: GIC不是“一根线”,而是一个包含分发器(Distributor)、CPU接口(CPU Interface)和虚拟化支持(VGIC)的复杂状态机。中断的“注册”本质上是配置GIC的路由表与CPU接口的使能位。

2.2 GIC架构演进与版本对比

特性 GICv2 GICv3 GICv4
CPU接口 内存映射(MMIO) 系统寄存器(SysReg) 系统寄存器 + 直接注入
中断路由 固定路由到指定CPU 支持亲和性路由(Affinity Routing) 支持虚拟LPI直接注入
最大中断数 最多1020个SPI 几乎无限(基于ITS) 几乎无限 + 虚拟中断
虚拟化支持 基础VGIC 增强VGIC + 虚拟LPI 直接注入(Direct Injection)
驱动关注点 irq_domain + 静态映射 irq_domain + ITS + MSI irq_domain + 虚拟中断映射
驱动提示: 当前主流嵌入式平台(如Cortex-A72/A76)多采用GICv3。驱动代码中通过 #ifdef CONFIG_ARM_GIC_V3 区分版本,中断号分配逻辑在GICv3中依赖 irq_domain 的线性映射。

2.3 GIC内部架构与中断流

GIC由三个主要逻辑单元组成:Distributor(分发器)、CPU Interface(CPU接口)和ITS(中断翻译服务,GICv3+)。中断从外设产生到CPU处理的完整路径如下:

  1. 外设断言中断线 → 进入Distributor的SPI/PPI输入。
  2. Distributor 检查中断使能、优先级、目标CPU掩码。
  3. Distributor 将最高优先级的中断转发给目标CPU的 CPU Interface。
  4. CPU Interface 向CPU核心发送IRQ信号,CPU跳转到中断向量表。
  5. CPU读取 ICC_IAR0_EL1(GICv3)获取中断ID,进入Linux中断处理流程。
GIC中断流架构图 外设 SPI/PPI Distributor 使能/优先级/路由 目标CPU掩码 中断ID分配 CPU Interface 优先级过滤 抢占逻辑 IAR/EOI寄存器 CPU Core IRQ向量表 中断处理 上下文切换 ITS (GICv3+) LPI/MSI路由 中断流说明: 外设 → Distributor(使能/优先级/路由) → CPU Interface(抢占/优先级过滤) → CPU Core(向量表/处理) GICv3中,ITS负责将MSI/LPI中断翻译为物理中断ID,再送入Distributor。

2.4 中断类型:SPI、PPI、SGI

GIC将中断分为三种类型,驱动开发者在 device tree 或 ACPI 中描述中断时,必须明确指定类型和编号。

类型 全称 中断ID范围 目标CPU 典型用途 驱动关注点
SPI Shared Peripheral Interrupt 32 ~ 1019 (GICv2)
32 ~ 1023 (GICv3)
可路由到任意CPU或CPU组 外设中断(UART、I2C、DMA、网卡) 设备树 interrupts = <0 33 4>;
第一个参数0表示SPI
PPI Private Peripheral Interrupt 16 ~ 31 仅属于单个CPU私有 本地定时器、PMU、GIC本地中断 设备树 interrupts = <1 13 4>;
第一个参数1表示PPI
SGI Software Generated Interrupt 0 ~ 15 由软件写入ICDSGIR触发,
可定向到指定CPU
核间通信(IPI)、
CPU唤醒、调度
Linux使用SGI实现 smp_call_function;
驱动一般不直接使用
常见错误: 在设备树中错误地将PPI中断号(如29)写为SPI类型(第一个参数写0),会导致GIC分发器无法正确路由中断,驱动 request_irq 返回 -EINVAL。务必核对SoC手册中中断号与类型的对应关系。

2.5 中断分发与路由机制

GIC的分发与路由是中断到达正确CPU的关键。GICv2使用静态路由表,GICv3引入亲和性路由,支持更灵活的中断分发策略。

2.5.1 GICv2 分发流程

  • Distributor 维护每个SPI的 ICDIPTRn 寄存器,指定目标CPU掩码(8位掩码,每位对应一个CPU)。
  • 当多个CPU共享一个SPI时,GIC使用 轮询(Round-Robin) 或 最低优先级 策略选择目标CPU。
  • PPI固定路由到其所属CPU,SGI由发送端指定目标CPU列表。

2.5.2 GICv3 亲和性路由

  • 使用 GICR_TYPER 和 GICD_IROUTERn 寄存器,支持 8位亲和性(PE:PE Group:Cluster:Core)。
  • 支持 中断分组:Group 0(安全中断,EL3处理)、Group 1(非安全中断,EL1/EL2处理)。
  • 驱动通过 irq_set_affinity 动态调整中断亲和性,实现负载均衡。
GICv3中断路由与亲和性示意图 SPI 33 SPI 45 PPI 29 Distributor GICD_IROUTERn SPI 33 → CPU0, CPU2 SPI 45 → CPU1, CPU3 PPI 29 → CPU0 (私有) 亲和性掩码: 0xF0 → CPU4~7 0x0F → CPU0~3 CPU Cluster 0 CPU0 (PPI 29) CPU1 (SPI 45) CPU2 (SPI 33) CPU Cluster 1 CPU3 (SPI 45) CPU4~7 (空闲) 路由规则: SPI可路由到任意CPU(通过GICD_IROUTERn设置亲和性);PPI固定路由到所属CPU;SGI由发送端指定目标CPU列表。

2.6 中断ID与irq_domain映射

Linux内核使用 irq_domain 将硬件中断ID(hwirq)映射到Linux中断号(irq)。GIC驱动注册 irq_domain 时,根据中断类型建立不同的映射策略。

/* GICv3 irq_domain 注册示例(drivers/irqchip/irq-gic-v3.c) */
static int gicv3_populate_rdist(struct device_node *node)
{
    /* 为每个CPU Interface分配GICR基地址 */
    /* 注册PPI/SGI的irq_domain */
    domain = irq_domain_add_linear(node, 16 + 16, /* 16 SGI + 16 PPI */
                                   &gicv3_ppi_sgi_domain_ops, NULL);
    /* 注册SPI的irq_domain */
    domain = irq_domain_add_linear(node, gic_data.rdists.spi_nr,
                                   &gicv3_spi_domain_ops, NULL);
    /* 注册ITS的irq_domain(用于MSI/LPI) */
    domain = irq_domain_add_tree(node, &gicv3_its_domain_ops, NULL);
}
映射关系速查:
  • SGI: hwirq 0~15 → Linux irq 通常从 32 开始(取决于平台)
  • PPI: hwirq 16~31 → Linux irq 紧接SGI之后
  • SPI: hwirq 32~1019 → Linux irq 从 32+16+16=64 开始(典型值)
  • LPI: hwirq 8192+ → 通过ITS动态分配,使用irq_domain_tree

2.7 中断触发类型与配置

GIC支持四种中断触发类型,驱动在设备树或 irq_set_type 中配置。错误配置会导致中断丢失或误触发。

触发类型 设备树标志 GIC配置寄存器 适用场景 注意事项
电平高有效 IRQ_TYPE_LEVEL_HIGH (4) ICDICFRn 配置为电平触发 共享中断线、需要电平检测的外设 必须确保中断处理完成前外设释放电平,否则死锁
电平低有效 IRQ_TYPE_LEVEL_LOW (8) ICDICFRn 配置为电平触发 + 极性反转 低电平有效的外设(如某些GPIO控制器) 同上,电平释放时机关键
边沿上升沿 IRQ_TYPE_EDGE_RISING (1) ICDICFRn 配置为边沿触发 按键、网络包到达、DMA完成 边沿触发不会锁存,丢失后无法恢复
边沿下降沿 IRQ_TYPE_EDGE_FALLING (2) ICDICFRn 配置为边沿触发 + 极性反转 下降沿有效的外设 同上,边沿触发需确保信号完整性
电平触发陷阱: 电平触发中断在 handle_level_irq 中会 屏蔽中断线 直到处理完成。如果驱动在中断上下文中调用 disable_irq_nosync 后未正确重新使能,会导致该中断线永久关闭。边沿触发则使用 handle_edge_irq,不会屏蔽中断线,但可能丢失快速连续边沿。

2.8 中断优先级与抢占

GIC支持256级优先级(GICv2为16级,GICv3扩展为256级)。高优先级中断可以抢占低优先级中断的处理。驱动通过 irq_set_priority 设置优先级,但Linux内核默认将所有中断优先级设为相同值(通常为0xA0),关闭抢占以简化并发模型。

/* 设置中断优先级示例(ARM64汇编级操作) */
static void gic_set_priority(struct irq_data *d, u32 priority)
{
    /* GICv3: 写入ICC_PMR_EL1 或 GICR_IPRIORITYR */
    if (gic_irq(d)->type == SPI) {
        write_gicreg(priority, GICD_IPRIORITYR + d->hwirq * 4);
    } else {
        /* PPI/SGI 使用GICR_IPRIORITYR */
        write_gicreg(priority, GICR_IPRIORITYR + (d->hwirq & 0x1F) * 4);
    }
}
驱动建议: 除非有严格的实时性要求(如PREEMPT_RT),否则不要修改默认优先级。修改优先级可能导致中断嵌套,增加并发竞态风险。如果必须使用,请配合 raw_spin_lock 保护共享数据。

2.9 本章小结

  • GIC架构 是中断子系统的硬件基础,Distributor负责路由,CPU Interface负责向CPU递交中断。
  • SPI/PPI/SGI 三种中断类型决定了中断的路由范围、ID分配和驱动配置方式。
  • 中断路由 在GICv3中通过亲和性寄存器实现动态分发,驱动可使用 irq_set_affinity 实现负载均衡。
  • irq_domain 是连接硬件中断ID与Linux中断号的桥梁,理解映射关系是正确注册中断的前提。
  • 触发类型与优先级 的配置直接影响中断的可靠性和并发行为,电平触发需特别注意释放时机。