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中断路由流程 外设(SPI) 本地定时器(PPI) 软件(SGI) PCIe设备(LPI) Distributor GICD(SPI路由) Redistributor GICR(PPI/SGI/LPI) ITS(LPI翻译) CPU核心(PE) ICC_*_ELx寄存器 说明: SPI经Distributor路由到目标PE;PPI/SGI/LPI经Redistributor直接送达PE。 LPI需要ITS进行中断号翻译和路由。

GICv3初始化要点

在Jacinto平台上初始化GICv3,我总结了几步关键操作:

  1. 确认GIC版本:读取GICD_PIDR2寄存器,确认是GICv3还是GICv4。
  2. 初始化Distributor:设置GICD_CTLR使能Group 0和Group 1。
  3. 初始化Redistributor:遍历所有Redistributor,设置GICR_WAKER使能。
  4. 配置中断路由:对SPI设置GICD_IROUTERn,指定目标PE。
  5. 配置CPU接口:设置ICC_SRE_ELx启用系统寄存器访问,配置ICC_PMR_ELx设置优先级掩码。
  6. 使能中断:设置ICC_IGRPEN1_EL1使能Group 1中断。

核心提示:GICv3的初始化顺序很重要。我建议先初始化Redistributor,再初始化Distributor,最后配置CPU接口。顺序错了,中断可能无法正确路由。

总结

GICv3相比GICv2,最大的变化是分布式架构和系统寄存器接口。Distributor管SPI,Redistributor管PPI/SGI/LPI,各司其职。四种中断类型各有用途,LPI是PCIe MSI的标配。理解这些,你就能在Jacinto平台上灵活管理中断了。