GICv3架构解析:从GICv2到GICv3的演进
做中断管理,绕不开GIC(Generic Interrupt Controller)。在TI Jacinto平台上,GICv3是标配。说实话,我第一次从GICv2切到v3时,也被Distributor和Redistributor这套新概念搞晕过。今天咱们就把这块彻底理清楚。
GICv3与GICv2的核心区别
先看一张对比表,直观感受下差异:
| 对比项 | GICv2 | GICv3 |
|---|---|---|
| CPU接口 | 专用引脚(nIRQ/nFIQ) | 系统寄存器(ICC_*_ELx) |
| 中断路由 | Distributor集中路由 | Redistributor分布式路由 |
| 中断类型 | SPI/PPI/SGI | SPI/PPI/SGI/LPI |
| 亲和性 | 仅支持8个PE | 支持多达256个PE |
| 虚拟化 | 硬件支持有限 | 原生虚拟化支持(GICv3+) |
| 中断号范围 | 0~1019 | 0~8191(SPI/PPI/SGI)+ 16M(LPI) |
我个人觉得,最大的变化是CPU接口从硬件引脚变成了系统寄存器。这意味着什么?说白了,中断处理不再依赖固定的物理连接,而是通过读写寄存器来完成。这在多核、多集群的场景下,灵活性提升了一大截。
关键点:GICv3的CPU接口寄存器(ICC_*_ELx)在EL1/EL2/EL3下都有对应的视图。我建议你在初始化时,务必确认当前异常等级,否则写错寄存器会导致中断完全收不到。
Distributor与Redistributor
GICv3把原来GICv2的Distributor拆成了两部分:Distributor(GICD)和Redistributor(GICR)。这个拆分是有道理的——每个CPU核心都有自己的Redistributor,中断路由变得更灵活。
Distributor(GICD)
Distributor是全局的,负责管理所有SPI中断。它的主要职责包括:
- 中断使能/禁能(GICD_ISENABLERn / GICD_ICENABLERn)
- 中断优先级配置(GICD_IPRIORITYRn)
- 中断目标路由(GICD_IROUTERn)——这是v3新增的,每个SPI可以独立指定目标PE
- 中断触发方式(GICD_ICFGRn)
避坑指南:我曾经在配置SPI路由时,忘记设置GICD_IROUTERn的Affinity字段,结果中断一直不触发。查了两天才发现,默认值是0,但我的PE编号不是0。记住:GICv3里SPI必须显式指定目标PE。
Redistributor(GICR)
每个PE(Processing Element)都有一个对应的Redistributor。它负责管理:
- PPI中断(每个PE私有)
- SGI中断(软件触发中断)
- LPI中断(如果支持ITS)
- 唤醒和电源管理
Redistributor的基地址可以通过GICR_TYPER寄存器中的Affinity值计算出来。嗯,这里有个小技巧:遍历Redistributor时,用GICR_TYPER.Last位来判断是否到了最后一个。
中断类型详解:SPI/PPI/SGI/LPI
GICv3支持四种中断类型。我按中断号范围给你列出来:
| 中断类型 | 中断号范围 | 目标 | 触发方式 | 典型用途 |
|---|---|---|---|---|
| SGI | 0~15 | 任意PE | 软件触发 | 核间通信(IPI) |
| PPI | 16~31 | 私有(单个PE) | 电平/边沿 | 本地定时器、PMU |
| SPI | 32~1019 | 全局(任意PE) | 电平/边沿 | 外设中断(UART、I2C等) |
| LPI | 8192~ | 任意PE | 电平(消息型) | PCIe MSI/MSI-X |
SGI(Software Generated Interrupt)
SGI是软件触发的,通常用于核间通信。在GICv3里,SGI的触发方式变了——不再写GICD_SGIR,而是通过ICC_SGI1R_EL1寄存器。你想想看,这样每个PE都能独立触发SGI,不需要经过Distributor,延迟更低。
注意:GICv3的SGI支持16个中断ID,但每个ID可以携带一个中断组属性(Group 0/1)。我遇到过一个问题:两个核用同一个SGI ID,但一个配置成Group 0,另一个配置成Group 1,结果中断路由乱套了。建议统一管理SGI的Group配置。
PPI(Private Peripheral Interrupt)
PPI是每个PE私有的,比如本地定时器、PMU事件。在GICv3里,PPI的配置寄存器在Redistributor中,而不是Distributor。这意味着每个PE可以独立配置自己的PPI,互不干扰。
SPI(Shared Peripheral Interrupt)
SPI是全局中断,外设(如UART、I2C)通常使用SPI。GICv3最大的改进是:每个SPI可以独立指定目标PE,而不是像v2那样只能按组路由。这在多核负载均衡时非常有用。
LPI(Locality-specific Peripheral Interrupt)
LPI是GICv3新增的,专门为PCIe MSI/MSI-X设计。它通过ITS(Interrupt Translation Service)来管理。LPI的中断号范围很大(16M),但配置方式完全不同——不是通过内存映射寄存器,而是通过中断配置表(Interrupt Configuration Table)。
个人经验:LPI的配置表在内存中,需要保证内存一致性。我在Jacinto平台上调试PCIe NVMe时,LPI一直不触发,最后发现是配置表所在的缓存没有刷新。记得在更新配置表后,执行DSB指令确保写完成。
GICv3中断路由流程
下面这张图展示了GICv3的中断路由路径:
GICv3初始化要点
在Jacinto平台上初始化GICv3,我总结了几步关键操作:
- 确认GIC版本:读取GICD_PIDR2寄存器,确认是GICv3还是GICv4。
- 初始化Distributor:设置GICD_CTLR使能Group 0和Group 1。
- 初始化Redistributor:遍历所有Redistributor,设置GICR_WAKER使能。
- 配置中断路由:对SPI设置GICD_IROUTERn,指定目标PE。
- 配置CPU接口:设置ICC_SRE_ELx启用系统寄存器访问,配置ICC_PMR_ELx设置优先级掩码。
- 使能中断:设置ICC_IGRPEN1_EL1使能Group 1中断。
核心提示:GICv3的初始化顺序很重要。我建议先初始化Redistributor,再初始化Distributor,最后配置CPU接口。顺序错了,中断可能无法正确路由。
总结
GICv3相比GICv2,最大的变化是分布式架构和系统寄存器接口。Distributor管SPI,Redistributor管PPI/SGI/LPI,各司其职。四种中断类型各有用途,LPI是PCIe MSI的标配。理解这些,你就能在Jacinto平台上灵活管理中断了。