ARM GIC 架构详解:GIC v2/v3 控制器结构、Distributor与CPU Interface、中断ID与中断号映射

好,咱们今天来聊聊 ARM 的中断控制器——GIC。这东西,说白了就是 CPU 的「门卫」。外设想跟 CPU 说话,都得先经过它。我刚开始接触嵌入式时,总觉得中断就是 GPIO 电平跳一下,后来发现完全不是那么回事。GIC 的复杂程度,足够让一个新手折腾好几天。

我个人习惯,拿到一块新板子,第一件事就是看它的 GIC 版本。是 v2 还是 v3?这决定了后面所有中断代码怎么写。你想想看,如果连门卫是哪个公司的都不知道,你怎么跟它打交道?

GIC v2 与 v3:两代门卫的区别

GIC v2 是 ARM 在 Cortex-A9/A15 时代主推的。它最多支持 8 个 PE(处理单元),每个 PE 有 256 个中断。嗯,这个数字在当年够用,但现在动辄几十核的服务器芯片,256 个中断就捉襟见肘了。

GIC v3 呢?它把中断数扩展到了 2^32 个。你没看错,是 32 位的中断 ID。而且它引入了 Redistributor 的概念,每个 PE 都有自己的 Redistributor,中断分发效率高了不少。我在项目中遇到过,v2 的广播中断在 16 核系统上延迟明显,换成 v3 后,延迟直接降了一半。

特性 GIC v2 GIC v3
最大中断数 256 (ID 0-255) 2^32 (ID 0-0xFFFFFFFF)
PE 支持 最多 8 个 无限制(通过 Redistributor)
中断路由 Distributor 统一分发 Distributor + Redistributor 两级
虚拟化支持 有限 原生支持(GICv3+)
注意:GIC v2 和 v3 的寄存器地址空间完全不同。移植内核时,设备树里的 compatible 属性一定要写对。我曾经见过有人把 "arm,gic-v3" 写成了 "arm,gic-v2",结果中断死活不响应,查了两天才发现是这里的问题。

Distributor 与 CPU Interface:门卫的分工

GIC 内部有两个核心模块:DistributorCPU Interface。它们的分工很明确:

  • Distributor:负责接收所有外设的中断信号,然后根据优先级、目标 CPU 等配置,决定把中断发给谁。
  • CPU Interface:每个 CPU 核都有一个。它负责从 Distributor 那里接收中断,然后通知 CPU 去处理。

说白了,Distributor 是「调度中心」,CPU Interface 是「本地代理」。我在调试一个多核系统时,发现某个核始终收不到中断。后来一查,是 Distributor 里那个核的 CPU Interface 使能位没置上。嗯,这种低级错误,犯过一次就记住了。

关键点:Distributor 负责「谁该处理」,CPU Interface 负责「怎么通知 CPU」。两者缺一不可。

在 GIC v3 中,Distributor 的功能被拆分了一部分给 Redistributor。每个 Redistributor 对应一个 PE,它管理着该 PE 的私有中断(PPI)和软件触发中断(SGI)。这样设计的好处是,中断配置可以并行处理,不会因为一个 Distributor 成为瓶颈。

中断 ID 与中断号映射:从硬件到软件的桥梁

这是最容易让人迷糊的地方。硬件上,每个中断源都有一个唯一的 中断 ID。比如 SPI(共享外设中断)的 ID 从 32 开始,PPI(私有外设中断)的 ID 是 16-31,SGI(软件触发中断)的 ID 是 0-15。

但在 Linux 内核里,我们用的是 IRQ 号。这个 IRQ 号是软件层面的概念,它和硬件中断 ID 之间有一个映射关系。怎么映射的?

// GIC v2 中的映射关系(以 ARM 平台为例)
// 硬件中断 ID 32 对应 Linux IRQ 号 32
// 硬件中断 ID 0-15(SGI)通常不映射给普通驱动使用

// GIC v3 中的映射关系
// 硬件中断 ID 32 对应 Linux IRQ 号 32
// 但 GIC v3 支持 LPI(本地私有中断),它们的映射更复杂
// LPI 的 ID 从 8192 开始,Linux 会动态分配 IRQ 号

你可能会问:「为什么不直接用硬件 ID 当 IRQ 号?」原因很简单:Linux 的 IRQ 号是全局的,而硬件中断 ID 可能在不同 GIC 实例中重复。比如一个系统有两个 GIC,每个 GIC 都有 ID 32 的中断,但 Linux 需要两个不同的 IRQ 号来区分它们。

我的经验:在设备树里,interrupts 属性填的是硬件中断 ID。比如 interrupts = <0 33 4> 表示 SPI 类型(第一个参数 0),硬件 ID 33(第二个参数),触发类型为高电平有效(第三个参数 4)。内核在解析设备树时,会自动完成从硬件 ID 到 IRQ 号的映射。

我曾经踩过一个坑:在 GIC v3 平台上,我直接用了硬件 ID 去注册中断,结果 request_irq 返回 -EINVAL。后来才发现,GIC v3 的 LPI 中断需要先通过 irq_domain 分配 IRQ 号,不能直接用硬件 ID。说白了,就是软件和硬件的「翻译官」没找对。

GIC 架构的核心逻辑

为了让你更直观地理解,我画了一张图。这张图展示了 GIC v3 中中断从外设到 CPU 的完整路径。

GIC v3 中断分发流程 外设 (SPI) 外设 (PPI) Distributor 中断优先级 目标 CPU 选择 Redistributor 0 PPI/SGI 管理 Redistributor 1 PPI/SGI 管理 CPU IF 0 CPU IF 1 CPU 0 CPU 1 外设中断源 Distributor Redistributor CPU Interface CPU 核

从这张图你能看到,SPI 中断先到 Distributor,然后根据配置路由到某个 Redistributor,再通过 CPU Interface 通知 CPU。而 PPI 和 SGI 则直接由 Redistributor 管理,不经过 Distributor。这种设计让 GIC v3 在大规模多核系统中表现优异。

中断 ID 的详细分类

最后,我把 GIC v3 的中断 ID 分类整理成了一张表。这张表你在写驱动时一定会用到。

中断类型 ID 范围 说明 典型用途
SGI 0-15 软件触发中断,用于核间通信 IPI(处理器间中断)
PPI 16-31 私有外设中断,每个核独有 本地定时器、PMU
SPI 32-1019 共享外设中断,可路由到任意核 UART、I2C、GPIO
LPI 8192 起 本地私有中断,GIC v3 新增 PCIe MSI/MSI-X

避坑指南:我曾经在移植一个 PCIe 网卡驱动时,发现 MSI 中断不工作。查了半天,原来是 GIC v3 的 LPI 中断需要先通过 ITS(中断翻译服务)配置。如果你用的是 GIC v3,记得在设备树里加上 msi-controller 属性,否则 MSI 中断永远发不出来。

好了,关于 GIC 架构的核心内容就这些。记住一句话:中断 ID 是硬件的身份证,IRQ 号是软件的通行证。搞清楚了这两者的关系,GIC 的移植工作就成功了一半。


公众号:蓝海资料掘金营,微信deep3321