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+) |
Distributor 与 CPU Interface:门卫的分工
GIC 内部有两个核心模块:Distributor 和 CPU 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 的完整路径。
从这张图你能看到,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