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 处理的完整路径。

GIC 中断处理流程 外设 (SPI/PPI) Distributor CPU Interface CPU Core 中断信号流向 各模块职责: • 外设:产生中断信号(电平或边沿) • Distributor:接收中断,查表决定目标 CPU 和优先级 • CPU Interface:通知 CPU 核,提供中断 ID 和优先级 • CPU Core:响应中断,执行中断服务程序

这张图展示了最简化的流程。实际项目中,Distributor 内部还有优先级比较、中断屏蔽、运行优先级寄存器等复杂逻辑。但核心思想不变:外设 -> Distributor -> CPU Interface -> CPU Core。

好了,这一章的内容就到这。GIC 的架构其实不复杂,但细节很多。尤其是设备树里的中断描述,稍不注意就会配错。下一章咱们会讲中断控制器在设备树中的具体绑定方式,到时候会结合更多实际案例。


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