2. 中断控制器:GIC(通用中断控制器)架构介绍、中断类型(SPI、PPI、SGI)、中断号与中断线

在 ARM 多核处理器系统中,GIC(Generic Interrupt Controller,通用中断控制器) 是中断管理的核心硬件单元。它负责接收来自外设、处理器核间通信以及软件触发的中断请求,经过优先级仲裁、分发和路由,最终将中断信号送达指定的 CPU 核心。理解 GIC 的架构是编写正确中断处理驱动的基石。

2.1 GIC 架构概览

GIC 从逻辑上分为两个主要模块:分发器(Distributor) 和 CPU 接口(CPU Interface)。在 GICv2 及后续版本中,还引入了 软件生成的中断(SGI) 和 虚拟化支持。下图展示了 GIC 在系统级芯片(SoC)中的典型位置与内部结构。

架构要点: 分发器负责全局中断管理(使能、优先级、目标 CPU),而每个 CPU 核心通过独立的 CPU 接口接收已分发的中断。
GIC 通用中断控制器架构框图 GIC 通用中断控制器架构框图 外设中断 (SPI) 软件触发中断 (SGI) 私有外设中断 (PPI) 分发器 中断使能/禁能 优先级配置 目标 CPU 路由 中断分组 (Group 0/1) 中断状态管理 CPU 接口 0 CPU 接口 1 IRQ IRQ 说明: • 分发器是全局单元,所有中断源首先到达分发器。 • 每个 CPU 核心拥有独立的 CPU 接口,接收分发器路由来的中断信号。 • GICv2 最多支持 8 个 CPU 接口(即 8 核)。

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 中断。

GIC 中断类型与路由关系图 中断类型与路由关系 SGI (ID0-15) 软件写寄存器 PPI (ID16-31) 私有外设 SPI (ID32-1019) 共享外设 分发器 路由决策 优先级比较 目标 CPU 掩码 CPU 接口 0 CPU 接口 1 CPU 接口 2 注意:PPI 不经过分发器路由,直接连接到对应的 CPU 接口;SGI 和 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,映射过程如下:

  1. 内核解析设备树,获取外设节点的 interrupts 属性。
  2. 调用 irq_domain_alloc_irqs() 或 irq_create_mapping(),在 irq_domain 中为硬件中断号分配一个 Linux IRQ 号。
  3. 驱动通过 platform_get_irq() 获取映射后的 Linux IRQ 号。
  4. 驱动使用该 Linux IRQ 号调用 request_irq() 注册中断处理函数。
硬件中断号到 Linux IRQ 号映射流程 硬件中断号 → Linux IRQ 号映射流程 设备树解析 interrupts = <...> irq_domain 映射 irq_create_mapping() 获取 Linux IRQ platform_get_irq() 注册中断 request_irq() 关键数据结构: • irq_domain:维护硬件中断号 → Linux IRQ 号的映射关系。 • irq_desc:每个 Linux IRQ 号对应一个 irq_desc 结构体,包含中断处理函数链表、状态、芯片操作等。

2.4 中断触发方式与配置

GIC 支持多种中断触发方式,在设备树中通过 interrupts 属性的第三个字段指定。常见的触发方式如下:

触发方式 宏定义 说明
上升沿触发 IRQ_TYPE_EDGE_RISING 在信号从低到高跳变时触发一次
下降沿触发 IRQ_TYPE_EDGE_FALLING 在信号从高到低跳变时触发一次
双边沿触发 IRQ_TYPE_EDGE_BOTH 在上升沿和下降沿均触发
高电平触发 IRQ_TYPE_LEVEL_HIGH 在信号为高电平时持续触发
低电平触发 IRQ_TYPE_LEVEL_LOW 在信号为低电平时持续触发
电平 vs 边沿选择原则: 电平触发适合共享中断线(需要确认中断源),边沿触发适合单次事件(如按键按下)。在 GIC 中,SPI 和 PPI 通常配置为电平触发,SGI 固定为边沿触发。

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 或中断号配置错误。