4. GICv3中断控制器配置:GICv3与GICv2的区别、ITS节点配置、LPI的使能
好,咱们今天聊聊GICv3。说实话,这玩意儿刚出来的时候,我也觉得头大。GICv2用了那么多年,大家都习惯了,突然来个v3,改动还不小。但等你真正用上,就会发现——嗯,真香。
4.1 GICv3与GICv2的核心区别
先说说最直观的区别。GICv2时代,CPU接口是直接挂在芯片内部的,每个核有自己的GIC CPU接口。但到了GICv3,架构变了——它把CPU接口和分发器彻底分离了。
我个人习惯用一张表来对比,这样最清楚:
| 特性 | GICv2 | GICv3 |
|---|---|---|
| CPU接口 | 内存映射,每个核独立 | 系统寄存器方式(ICC_*_ELx) |
| 中断号范围 | 最多1020个SPI | 支持16K SPI,支持LPI(可达16M) |
| 亲和性路由 | 不支持 | 支持多级亲和性(Affinity路由) |
| LPI支持 | 不支持 | 通过ITS支持消息中断 |
| 虚拟化支持 | 基础支持 | 硬件加速虚拟化(LPI直接注入VM) |
你想想看,GICv2的中断路由是固定的——每个中断绑定到特定CPU。但GICv3不一样,它引入了亲和性路由的概念。说白了,就是中断可以灵活地发给某个集群、某个核,甚至可以在运行时动态调整。
我在项目中遇到过一个问题:某款服务器芯片有128个核,用GICv2的话,中断路由表会爆炸。换成GICv3后,用亲和性路由轻松搞定。这就是为什么大型ARM服务器都强制要求GICv3。
4.2 ITS节点配置
ITS,全称Interrupt Translation Service,翻译过来就是中断翻译服务。这玩意儿是GICv3的灵魂。
为什么要ITS?因为LPI(Local Peripheral Interrupt)不是直接发到GIC的,而是通过写内存的方式触发。设备往内存里写一个数据包,ITS负责把这个数据包翻译成中断信号,再路由到目标CPU。
来看一个典型的ITS设备树节点:
its: interrupt-controller@2c010000 {
compatible = "arm,gic-v3-its";
reg = <0x0 0x2c010000 0x0 0x20000>;
msi-controller;
#msi-cells = <1>;
soc: soc {
#address-cells = <2>;
#size-cells = <2>;
...
};
};
这里有几个关键点,我重点说一下:
- compatible:必须是"arm,gic-v3-its",别写错了。我见过有人写成"arm,gic-its",结果内核根本不认。
- reg:ITS的寄存器基地址。注意,这个地址要和GIC分发器的地址空间分开。
- msi-controller:这个属性告诉内核,这个节点是一个MSI控制器。没有它,PCIe设备发不了MSI中断。
- #msi-cells:通常设为1,表示每个MSI需要一个32位的标识符。
4.3 LPI的使能
LPI,全称Local Peripheral Interrupt。它和传统的SPI、PPI不一样——LPI是基于消息的中断。
怎么理解呢?传统中断是设备拉一根物理信号线到GIC,GIC检测到电平变化就触发中断。但LPI不是这样——设备往内存里写一个特定的数据包,ITS收到后解析这个包,然后生成中断。
使能LPI需要做几件事:
- 配置GICR寄存器:每个CPU核都有一个GIC Redistributor(GICR),LPI的使能位就在GICR中。
- 设置LPI配置表:LPI的优先级、使能状态等信息存储在内存中的配置表里。
- 初始化ITS:ITS需要先完成初始化,才能接收和翻译LPI消息。
设备树中使能LPI的典型配置:
gic: interrupt-controller@2c000000 {
compatible = "arm,gic-v3";
reg = <0x0 0x2c000000 0x0 0x10000>, // GICD
<0x0 0x2c100000 0x0 0x200000>, // GICR
<0x0 0x2c300000 0x0 0x100000>; // GICR(第二个CPU的)
interrupt-controller;
#interrupt-cells = <4>;
#address-cells = <2>;
#size-cells = <2>;
its: interrupt-controller@2c010000 {
compatible = "arm,gic-v3-its";
reg = <0x0 0x2c010000 0x0 0x20000>;
msi-controller;
#msi-cells = <1>;
};
};
嗯,这里要注意一点:LPI的使能不是一次性完成的。你需要先确保ITS正常工作,然后才能使能LPI。顺序搞反了,LPI会一直pending,但永远不会被处理。
我曾经在调试NVMe SSD驱动时遇到过这个问题。设备发了MSI中断,但CPU就是收不到。查了两天,最后发现是ITS还没初始化完成,我就先把LPI使能了。结果LPI消息到了ITS,ITS还没准备好,直接丢弃了。从那以后,我每次写LPI使能代码,都会先检查ITS的状态寄存器。
4.4 避坑指南
最后,分享几个我踩过的坑:
- GICv3的#interrupt-cells是4,不是3。GICv2用3个cell就够了,但GICv3需要4个——分别是中断类型、中断号、触发类型、亲和性信息。我见过有人直接复制GICv2的配置,结果中断路由全乱了。
- ITS的reg地址不能和GICD重叠。虽然它们都是GIC的一部分,但地址空间是独立的。重叠的话,内核会报地址冲突。
- LPI配置表必须放在非cacheable内存。因为ITS是通过DMA方式访问配置表的,cache一致性处理不好会出问题。我习惯在设备树里用"no-map"属性保留一段内存给LPI配置表用。
好了,GICv3的基础配置就讲到这里。下一节我们会深入ITS的翻译机制,看看它到底是怎么把设备消息变成中断信号的。到时候我会带一个实际案例,保证让你彻底搞懂。