ARM GIC 架构:GIC v2/v3 基本概念、Distributor 与 CPU Interface、中断类型(PPI、SPI、LPI)
好,咱们接着聊中断。上一章我们把中断控制器在 SoC 里的位置讲清楚了。这一章,咱们深入 ARM 官方的中断控制器——GIC。
GIC 全称 Generic Interrupt Controller,通用中断控制器。说白了,它就是 ARM 体系里管理中断的“总管家”。你想想看,一个芯片里可能有几十个外设,每个外设都可能产生中断。谁先响应?谁被屏蔽?谁该发给哪个 CPU 核?这些事,GIC 全包了。
我个人习惯把 GIC 理解成一个“智能路由器”。它不负责具体业务,只负责把中断信号高效地分发到目标 CPU 上。
GIC v2 与 v3:两代架构的差异
先说说版本。目前嵌入式领域最常见的是 GIC v2 和 GIC v3。v2 是经典款,v3 是升级款。
GIC v2 主要用在 Cortex-A7、A9、A15 这些老一点的核上。它最多支持 8 个 CPU 核,中断号范围是 0~1019。嗯,这个数字够用,但不算宽裕。
GIC v3 是跟着 ARMv8 架构一起推出来的。它支持更多的 CPU 核(理论上无上限),中断号扩展到 16~16,777,215。你想想看,这个范围有多大?
我在项目中遇到过一个问题:某款新芯片用了 GIC v3,但驱动里还按 v2 的方式配置中断亲和性,结果中断死活不按预期分发。后来查了手册才发现,v3 的亲和性路由机制完全变了。
核心区别一句话总结:
- GIC v2:适合 4~8 核,中断号有限,配置简单
- GIC v3:适合多核(16核以上),中断号海量,支持虚拟化增强
Distributor 与 CPU Interface:两个核心模块
GIC 内部有两个关键模块:Distributor(分发器)和 CPU Interface(CPU 接口)。
Distributor 负责全局管理。它接收所有外设发来的中断信号,然后根据优先级、使能状态、目标 CPU 等配置,决定这个中断该往哪送。
你可以把 Distributor 想象成一个“总调度台”。它手里有一张表,记录着每个中断的配置信息。比如:
- 这个中断使能了吗?
- 它的优先级是多少?
- 它应该发给哪个 CPU?
- 它是边沿触发还是电平触发?
CPU Interface 则是每个 CPU 核私有的模块。它负责和具体的 CPU 核打交道。当 Distributor 决定把中断发给 CPU0 时,CPU0 的 CPU Interface 就会收到信号,然后通知 CPU0 去处理。
我打个比方:Distributor 是快递分拣中心,CPU Interface 是每个小区的快递柜。分拣中心决定包裹该送哪个小区,快递柜负责通知业主来取件。
避坑指南: 我曾经在调试时发现,中断能到达 Distributor,但 CPU 就是收不到。查了半天,原来是 CPU Interface 的使能位没配。记住:Distributor 和 CPU Interface 的使能位要分开配,缺一不可。
中断类型:PPI、SPI、LPI
GIC 把中断分成了几类。这个分类很重要,因为设备树里配中断时,你得知道你的设备属于哪一类。
| 类型 | 全称 | 特点 | 典型用途 |
|---|---|---|---|
| PPI | Private Peripheral Interrupt | 每个 CPU 私有,中断号固定 | 本地定时器、PMU |
| SPI | Shared Peripheral Interrupt | 全局共享,可路由到任意 CPU | UART、I2C、GPIO |
| LPI | Locality-specific Peripheral Interrupt | 基于消息的中断,GIC v3 引入 | PCIe MSI、大量外设场景 |
PPI 是每个 CPU 核私有的。比如每个核都有自己的本地定时器,这个定时器的中断就是 PPI。PPI 的中断号是固定的,比如 ARM 规定 PPI 的 ID 范围是 16~31。
SPI 是全局共享的。比如你板子上只有一个 UART,它产生的中断可以发给 CPU0,也可以发给 CPU1,甚至同时发给两个核(不过一般不这么干)。SPI 的 ID 范围是 32~1019(v2)或更大(v3)。
LPI 是 GIC v3 才有的新类型。它不像 PPI 和 SPI 那样有物理中断线,而是通过写内存来触发中断。说白了,外设往某个内存地址写一个数据,GIC 就收到一个中断。这种方式特别适合 PCIe 的 MSI/MSI-X 中断。
我记得有一次调试 NVMe 硬盘,中断死活不触发。后来发现 NVMe 用的是 LPI,而我在设备树里配成了 SPI。嗯,类型搞错了,GIC 根本不认。
设备树中的中断描述
说了这么多概念,咱们看看设备树里怎么配。以 GIC v2 为例:
gic: interrupt-controller@f0001000 {
compatible = "arm,cortex-a15-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xf0001000 0x1000>,
<0xf0002000 0x2000>;
};
这里 #interrupt-cells = <3> 表示每个中断描述需要 3 个 cell。这三个 cell 分别表示:
- 第一个 cell:中断类型(0 表示 SPI,1 表示 PPI)
- 第二个 cell:中断号(对于 SPI,从 32 开始算;对于 PPI,从 16 开始算)
- 第三个 cell:触发方式(0 表示电平触发,1 表示边沿触发)
举个例子,一个 UART 设备使用 SPI 中断,中断号 38,电平触发:
uart0: serial@f0003000 {
compatible = "arm,pl011";
reg = <0xf0003000 0x1000>;
interrupts = <0 6 4>;
};
注意:这里的第二个 cell 是 6,但实际中断号是 32 + 6 = 38。嗯,这个偏移量容易搞混,我刚开始也栽过跟头。
特别提醒: 不同厂家的 GIC 实现,中断号的偏移可能不同。有些厂家从 0 开始算,有些从 32 开始算。一定要看芯片手册里的中断映射表,别想当然。
GIC 中断处理流程
最后,咱们用一张图把整个流程串起来。我画了个 SVG,帮你理解中断从产生到 CPU 处理的完整路径。
这张图展示了最简化的流程。实际项目中,Distributor 内部还有优先级比较、中断屏蔽、运行优先级寄存器等复杂逻辑。但核心思想不变:外设 -> Distributor -> CPU Interface -> CPU Core。
好了,这一章的内容就到这。GIC 的架构其实不复杂,但细节很多。尤其是设备树里的中断描述,稍不注意就会配错。下一章咱们会讲中断控制器在设备树中的具体绑定方式,到时候会结合更多实际案例。
公众号:蓝海资料掘金营,微信deep3321