Linux GICv3 ITS 翻译表:从 DeviceID 到 LPI 的一次完整走读
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
一块 NVMe 盘开满 8 个队列需要 8 个 MSI 向量,而 SPI 中断号最多只有 992 个,且路由基本绑死在固件上;设备一多、队列一开,中断号就不够分了。Linux 内核里 GICv3 的 ITS(Interrupt Translation Service,中断翻译服务)就是为此设计的:它把每条 MSI 消息翻译成 LPI(Locality-specific Peripheral Interrupt),并动态路由到目标 CPU。下面按"翻译原理 → 内核关键路径 → 设备树 → 运行时调优"的顺序,把 Linux GICv3 ITS 从头到尾讲一遍,读完你可以直接对照自己的设备树和源码排查问题。
📌 问题:为什么 SPI 撑不住了
GICv3 本身支持四类中断:
- SGI(Software Generated Interrupt,软件生成中断):CPU 之间互发,编号 0–15
- PPI(Private Peripheral Interrupt,私有外设中断):每个 CPU 一份私有
- SPI(Shared Peripheral Interrupt,共享外设中断):外设共享,编号 32–1023
- LPI(Locality-specific Peripheral Interrupt):靠 MSI 消息触发,从 8192 起步
LPI 的存在本身就是答案。旧方案和它的对比一目了然:
| 传统 SPI 路由 | ITS + LPI | |
|---|---|---|
| 可用中断编号 | 992 个(32–1023),全系统共享 | 从 8192 到 2^32-1,规模由硬件 IDBITS 字段决定 |
| 中断到 CPU 的绑定 | 固件阶段配死,运行时改动麻烦 | 每条 LPI 可单独改亲和性 |
数字锚点:LPI 编号起点 8192 来自源码里的硬编码(SGI/PPI/SPI 占用前 8192 个编号);优先级寄存器 8 位,共 256 级。
🧭 翻译怎么发生:三个步骤
1. 消息到达
设备发的 MSI 本质上是一条指向 ITS 的 TLP 消息,目标地址就是 ITS 的 Translater 寄存器(GITS_TRANSLATER,偏移 0x10040)。这条消息里藏着两个字段:DeviceID 和 EventID。
2. 查表翻译
ITS 内部依次走三张表,内核初始化时负责把它们填好:
| 表 | 索引起点 | 表项指向 | 内核对应命令 |
|---|---|---|---|
| Device Table | DeviceID | 该设备的中断翻译表(ITT)基址 | MAPD |
| Interrupt Translation Table | EventID | 一个 LPI 中断号 | MAPTI |
| Collection | LPI 中断号 | 目标 CPU 的亲和性 | MAPC / MOVI |
举个具体数值:DeviceID=0x21、EventID=3 的消息进来,Device Table 第 0x21 条指向 ITT,ITT 第 3 条读出 LPI 中断号 8196,Collection 表说 8196 归 CPU 5。
3. 路由到目标 CPU
重分布器(Redistributor,每个 CPU 一个)按 Collection 指向把这条 LPI 投递出去,目标核跑它的处理程序。翻译完成之后,MSI 的后续处理完全在硬件里完成,不占 CPU 周期。
翻译表是内核建起来的,靠的是 ITS 命令队列:
命令队列的固定顺序是 MAPD → MAPC → MAPTI → MOVI → SYNC:先绑设备、再绑 Collection、然后填 EventID 映射、需要时改亲和性,最后 SYNC 使缓存失效。队列满了内核会等它排空再写,这一段逻辑全部在 drivers/irqchip/irq-gic-v3-its.c 里。
🔍 内核关键路径:从探测到 MSI domain
入口是 drivers/irqchip/irq-gic-v3-its.c 里的its_init():它按设备树或 ACPI 遍历所有 ITS 节点,逐个走its_probe_one()——分配命令队列页、调its_alloc_tables()建三张表、写 CBASER 挂上队列、置位 GITS_CTLR_ENABLE 使能硬件;随后allocate_lpi_tables()全局分配 LPI 属性表,最后给每个 ITS 注册一个 MSI domain。
值得单独看的是注册这一步:父 domain 指回 GICv3 主 domain,子 domain 承载 MSI 向量分配逻辑。没有这一段,设备树里所有msi-parent指向 ITS 的设备都拿不到 MSI 向量。
static int its_init_domain(struct its_node *its) { struct irq_domain_info dom_info = { .fwnode = its->fwnode_handle, .ops = &its_domain_ops, .domain_flags = its->msi_domain_flags, .parent = its_parent, }; struct msi_domain_info *info; info = kzalloc_obj(*info); if (!info) return -ENOMEM; info->ops = &its_msi_domain_ops; info->data = its; dom_info.host_data = info; return msi_create_parent_irq_domain(&dom_info, &gic_v3_its_msi_parent_ops) ? 0 : -ENOMEM; }its_msi_domain_ops挂在一组操作后面:设备pci_enable_msi_range()要 N 个向量时,内核从 LPI 号池切一段连续的 LPI 给它,逐条填好翻译表,设备驱动全程只用标准 MSI 接口,对 ITS 无感知。
🧩 设备树里怎么写 ITS 节点
GICv3 节点和 ITS 节点的完整写法可以对照 Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml 的官方示例。精简版如下:
gic: interrupt-controller@2cf00000 { compatible = "arm,gic-v3"; #interrupt-cells = <3>; #address-cells = <1>; #size-cells = <1>; ranges; interrupt-controller; reg = <0x2f000000 0x10000>, /* GICD 分发器 */ <0x2f100000 0x200000>; /* GICR 重分布器基址 */ /* ITS 作为子节点,每个实例一个 */ its0: msi-controller@2c200000 { compatible = "arm,gic-v3-its"; msi-controller; /* 标记它是 MSI 控制器 */ #msi-cells = <1>; /* 恒为 1:设备号 */ reg = <0x2c200000 0x20000>; /* ITS 寄存器空间 */ }; }; nvme: nvme@01000000 { compatible = "pci-nvme"; msi-parent = <&its0>; /* MSI 请求交给 ITS */ interrupts-msi = <1 0>; /* 1 个向量,EventID 从 0 起 */ };逐段说明:compatible = "arm,gic-v3-its"是内核its_of_probe()匹配的钥匙;#msi-cells必须写 1,因为 MSI 父数据只有一个 DeviceID;外设不再用interrupts,改用msi-parent加interrupts-msi,向量数量由 PCI 枚举阶段协商。属性细节(比如mbi-ranges、redistributor-stride的写法)以官方绑定文档为准,链接见上文。
⚙️ 跑起来之后还能调什么
- 中断亲和性:
echo "3" > /proc/irq/<lpi号>/smp_affinity把中断挪到 CPU 0、1。需要它的场景:某个核被某设备的中断打满、邻居核却闲着时。 - 优先级:GICv3 的优先级寄存器 8 位、256 级,数值越小越优先。LPI 的优先级在启动时统一设置,SPI 可在 DTS 中断单元格第三位逐条指定。需要它的场景:把关键设备(如存储控制器)的中断压过大量低优先级设备的噪声。
- 多 ITS 实例:并列写多个 ITS 节点即可,内核会把设备分摊到不同实例。需要它的场景:设备数上万的平台,用多个实例分摊命令队列和翻译表压力;小设备数的单板 SoC 通常一个 ITS 足够。
一句话收尾
Linux GICv3 ITS 做的事情很克制:把 DeviceID + EventID 翻译成 LPI,再把这条 LPI 动态指向目标 CPU,其余交给硬件。源码入口 drivers/irqchip/irq-gic-v3-its.c,设备树写法看 Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml,寄存器与命令定义以 ARM 官方 GICv3/v4 架构规范(IHI 0069)为准。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考