2. 中断控制器:GIC(通用中断控制器)架构介绍、中断类型(SPI、PPI、SGI)、中断号与中断线
在 ARM 多核处理器系统中,GIC(Generic Interrupt Controller,通用中断控制器) 是中断管理的核心硬件单元。它负责接收来自外设、处理器核间通信以及软件触发的中断请求,经过优先级仲裁、分发和路由,最终将中断信号送达指定的 CPU 核心。理解 GIC 的架构是编写正确中断处理驱动的基石。
2.1 GIC 架构概览
GIC 从逻辑上分为两个主要模块:分发器(Distributor) 和 CPU 接口(CPU Interface)。在 GICv2 及后续版本中,还引入了 软件生成的中断(SGI) 和 虚拟化支持。下图展示了 GIC 在系统级芯片(SoC)中的典型位置与内部结构。
2.2 中断类型:SPI、PPI、SGI
GIC 将中断源划分为三种类型,每种类型在路由方式、触发条件和用途上存在显著差异。驱动开发者在申请中断号时,必须明确中断的类型。
| 中断类型 | 全称 | 中断号范围 (GICv2) | 目标 CPU | 典型用途 |
|---|---|---|---|---|
| SGI | Software Generated Interrupt | ID0 - ID15 (16 个) | 指定一个或多个 CPU | 核间通信 (IPI)、CPU 唤醒 |
| PPI | Private Peripheral Interrupt | ID16 - ID31 (16 个) | 仅绑定到单个 CPU | 本地定时器、性能监视单元 (PMU) |
| SPI | Shared Peripheral Interrupt | ID32 - ID1019 (988 个) | 可路由到任意 CPU | 外部设备中断(UART、I2C、DMA 等) |
interrupts 属性通常包含 <中断类型 中断号 触发方式> 三元组。例如 <GIC_SPI 42 IRQ_TYPE_EDGE_RISING> 表示一个 SPI 中断,硬件中断线号为 42。
2.2.1 SGI(软件生成中断)
SGI 由软件通过向 GIC 的 GICD_SGIR 寄存器写入特定值来触发。它不依赖外部硬件信号,常用于多核处理器之间的 核间中断(IPI)。Linux 内核中,smp_call_function() 和 TLB 刷新等操作均依赖 SGI。
2.2.2 PPI(私有外设中断)
PPI 是每个 CPU 核心私有的中断源,每个核心拥有独立的一份。例如,每个 CPU 的 本地定时器(Local Timer) 中断就是 PPI。PPI 不需要分发器进行路由,直接连接到对应的 CPU 接口,因此延迟更低。
2.2.3 SPI(共享外设中断)
SPI 是 SoC 上大部分外设使用的中断类型。多个 CPU 核心可以共享同一个 SPI 中断源,分发器负责将 SPI 中断路由到预先配置好的目标 CPU 列表。驱动开发中最常打交道的便是 SPI 中断。
2.3 中断号与中断线
在 GIC 语境下,中断号(Interrupt ID) 和 中断线(Interrupt Line) 是两个容易混淆但必须区分的概念。
- 中断号(HW IRQ Number): 硬件中断源在 GIC 分发器中的唯一标识符。对于 SPI,中断号范围是 32~1019;对于 PPI,是 16~31;对于 SGI,是 0~15。驱动中通过
platform_get_irq()或irq_of_parse_and_map()获取的原始值就是硬件中断号。 - 中断线(Linux IRQ Number): Linux 内核维护的虚拟中断号,是软件层面的抽象。内核通过
irq_domain将硬件中断号映射到 Linux IRQ 号。驱动注册中断时使用的request_irq(irq, handler, ...)中的irq参数就是 Linux IRQ 号。
interrupts = <GIC_SPI 42 4> 的 42 是硬件中断号(HW IRQ),不是 Linux IRQ 号。驱动中必须通过 platform_get_irq(pdev, 0) 进行转换,不能直接使用 42 调用 request_irq。
2.3.1 中断映射流程
从硬件中断号到 Linux IRQ 号的映射由 irq_domain 完成。对于 GIC,映射过程如下:
- 内核解析设备树,获取外设节点的
interrupts属性。 - 调用
irq_domain_alloc_irqs()或irq_create_mapping(),在irq_domain中为硬件中断号分配一个 Linux IRQ 号。 - 驱动通过
platform_get_irq()获取映射后的 Linux IRQ 号。 - 驱动使用该 Linux IRQ 号调用
request_irq()注册中断处理函数。
2.4 中断触发方式与配置
GIC 支持多种中断触发方式,在设备树中通过 interrupts 属性的第三个字段指定。常见的触发方式如下:
| 触发方式 | 宏定义 | 说明 |
|---|---|---|
| 上升沿触发 | IRQ_TYPE_EDGE_RISING |
在信号从低到高跳变时触发一次 |
| 下降沿触发 | IRQ_TYPE_EDGE_FALLING |
在信号从高到低跳变时触发一次 |
| 双边沿触发 | IRQ_TYPE_EDGE_BOTH |
在上升沿和下降沿均触发 |
| 高电平触发 | IRQ_TYPE_LEVEL_HIGH |
在信号为高电平时持续触发 |
| 低电平触发 | IRQ_TYPE_LEVEL_LOW |
在信号为低电平时持续触发 |
2.5 实战:查看中断映射
在嵌入式 Linux 系统中,可以通过 /proc/interrupts 文件查看当前系统中所有中断的映射关系和统计信息。以下是一个典型的输出片段:
CPU0 CPU1 CPU2 CPU3
16: 0 0 0 0 GIC-0 29 arch_timer
17: 9876 5432 1234 5678 GIC-0 30 arch_timer
42: 123 0 0 0 GIC-0 42 serial
43: 0 45 0 0 GIC-0 43 mmc0
字段说明:
- 第一列(16、17、42、43): Linux IRQ 号。
- CPU0~CPU3 列: 该中断在各 CPU 上触发的次数。
- GIC-0: 中断控制器名称,
29、30、42、43是硬件中断号。 - 最后一列: 中断对应的设备名称。
/proc/interrupts 中对应的 Linux IRQ 号是否存在,以及触发次数是否增加。如果次数不增加,说明硬件中断信号未到达 GIC 或中断号配置错误。