拓冰建站拓冰建站
首页 / 资讯中心 / 正文

RISC-V中断架构升级:从PLIC到APLIC/IMSIC的迁移实战

1. 到底为什么要迁移PLIC 的硬件瓶颈与 AIA 的破局思路聊 RISC-V 中断就绕不开 PLICPlatform-Level Interrupt Controller。过去几年几乎所有 RISC-V SoC 的中断方案都是“CLINT 管定时器、PLIC 管外设”这套组合拳在单核、简单多核场景下足够用。但一旦你把目光放到高性能计算、虚拟化、以及大量外设中断的服务器场景PLIC 的短板就会暴露得非常明显。这也是为什么 RISC-V 国际基金会要把 AIAAdvanced Interrupt Architecture推出来并用 APLIC、IMSIC 两套新控制器逐步取代 PLIC 地位的原因。如果你刚接触 RISC-V 中断看到 APLIC、IMSIC 这两个缩写可能会一头雾水。简单说APLIC 是“高级平台级中断控制器”主要负责把外设的 wired 中断信号收进来IMSIC 是“中断消息信号中断控制器”负责把中断作为内存写操作MSI送到指定 hart 的指定特权级。两者配合就是一套比 PLIC 更规范、更高效、也更适合虚拟化的中断分发链路。这篇文章面向的读者是正在做 RISC-V CPU 验证、SoC 中断子系统设计、或者 Linux/RTOS 底层中断驱动的开发者。我会结合自己实际调板子、写驱动、改设备树的经验把从 PLIC 到 APLIC/IMSIC 的迁移路径、寄存器配置、设备树改动、以及各种容易翻车的细节都过一遍。内容不绕弯子尽量给大家能直接抄作业的方案。1.1 PLIC 时代的老三样优先级、支持向量、hart 竞争先回顾一下 PLIC 的工作方式。PLIC 就像一个“大管家”收集所有外设中断源按优先级仲裁后把最高优先级的中断通过一条外部中断线通常是 meip/seip送给某个 hart。PLIC 本身有几组关键寄存器优先级寄存器、中断挂起寄存器、中断使能寄存器、以及最重要的 claim/complete 寄存器。优先级每个中断源有一个可配置的优先级值数值越大优先级越高0 表示禁用。支持/使能每个 hart 有一份中断使能位图控制这个 hart 能接收哪些中断。claim/complete软件通过读 claim 寄存器拿到当前最高优先级中断号处理完后再写 complete 寄存器告知硬件释放这条中断。这套机制在简单场景下没什么问题但它的硬伤在于所有 hart 共享同一个 PLICclaim/complete 操作需要经过总线访问而且中断仲裁和投递都以“一个外部中断线”为单位。这意味着软件拿到中断号后还得去查表、分发多核环境下每个 hart 都要和同一个 claim 寄存器打交道竞争严重时延迟会明显拉高。更麻烦的是PLIC 的优先级和仲裁逻辑在不同厂商实现里差异很大行为无法强一致导致软件适配成本很高。1.2 虚拟化一来PLIC 就露馅了PLIC 最难受的场景是虚拟化。虚拟机监视器hypervisor需要把物理中断注入到客户机guest里传统做法是软件模拟——物理中断来了VMM 捕获后改写客户机的软件中断状态。这个路径的开销非常大而且很难保证实时性。AIA 设计之初就把“硬件中断透传”作为一等公民来考虑IMSIC 天然支持按 hart 独立的中断文件interrupt file每个文件都能配置成直接投递给 V 模式虚拟化模式的客户机。也就是说物理外设中断可以直接变成客户机的虚拟中断VMM 不再需要每条中断都插手。另外PLIC 不支持 MSI。现代 PCIe 设备、高性能网卡几乎全靠 MSI/MSI-X 发中断你要在 RISC-V 平台上挂 PCIe 设备光靠 PLIC 去接 MSI 是非常别扭的——要么把所有 MSI 都路由到固定的 wire 中断要么在软件层做一层转换。AIA 引入 IMSIC 后MSI 变成了原生支持设备直接写一段内存地址就能触发指定 hart 的指定中断路径短、开销小、扩展性好。1.3 AIA 三件套APLIC、IMSIC、AIA CSRsAIA 规范不是只定义了一个硬件模块而是一整套中断体系核心包括三块APLIC负责收集 wired 中断源可以工作在 direct 模式类似 PLIC 的语义或 MSI 模式把中断转成 MSI 消息发出。IMSIC负责接收 MSI 消息并按 hart、按特权级投递中断每个 hart 有独立的一组中断文件每个文件一套寄存器不再共享。新增的一组 CSR 和指令比如 miselect/mireg、stopi/mtopi以及面向虚拟化的 hvien/hvip 等。这些让 CPU 可以直接读中断状态、响应中断很多场景下不需要走内存映射的 claim/complete 流程。学习 AIA 时最容易被绕晕的就是 APLIC 和 IMSIC 的分工。我的理解方式是APLIC 是“源”IMSIC 是“汇”。APLIC 管的是中断从哪来、怎么配优先级IMSIC 管的是中断往哪去、怎么投递给 CPU。在 direct 模式里 APLIC 直接把中断线拉给 CPUIMSIC 不参与在 MSI 模式里 APLIC 会生成一条 MSI 写入IMSIC 收到后再向 CPU 发中断。理解了这条链路后面配寄存器就顺了。2. APLIC 实战拆解从 direct 模式到 MSI 模式的寄存器配置APLIC 的寄存器布局在 AIA 规范里定义得很清楚但实际芯片实现会有细微差异建议以自家 SoC 手册为准。不过核心寄存器的语义是共通的只要抓住几个关键域配置起来并不难。2.1 APLIC 寄存器域的通用结构APLIC 一般按“域”domain划分一个域管理一组中断源域之间可以隔离。每个域有一组独立寄存器DOMAINCFG域配置寄存器bit 0 控制该域是否使能bit 2 控制工作在 direct 还是 MSI 模式。SOURCECFG[i]每个中断源一个配置寄存器主要控制中断触发方式level 还是 edge以及中断源是否有效。SETIP/CLRIP软件置位/清除中断挂起位用于模拟中断或绕过外部信号测试。SETIE/CLRIE软件使能/禁用某个中断源。GENMSGMSI 模式下生成 MSI 消息的控制寄存器。TARGETMSI 模式下指定这条 MSI 消息要发给哪个 hart、哪个中断文件。一个容易忽略的点是APLIC 的“中断源编号”并不一定等于设备树里的中断号。很多 SoC 会在 APLIC 内部对中断源做 offset设备树里看到的 irq number 往往要经过一层映射。后面我会专门讲这个问题。2.2 direct 模式最平滑的 PLIC 替代方案如果你只想快速把 PLIC 换掉APLIC 的 direct 模式是改动最小的路径。direct 模式下APLIC 的仲裁和投递语义和 PLIC 非常像但寄存器更规范化了。配置流程大概是这样的把 DOMAINCFG.DM 设为 0选择 direct 模式。对每个用到的中断源设置 SOURCECFG[i]配置触发方式。通过 SETIE 使能对应中断源。用 SETIP 测试或等待外部信号置位挂起位。CPU 收到外部中断后通过读 APLIC 的 claim 寄存器获取中断号处理完写 complete。看起来流程不复杂但有两点和 PLIC 不同。第一APLIC 的 claim 寄存器读出来的是“中断源编号”还是“设备树中断号”取决于 SoC 实现强烈建议直接看手册确认。第二direct 模式在响应中断时依然要走内存映射操作这也就意味着它不会比 PLIC 快太多只是更规范。真正想发挥 AIA 性能优势的还是要看 MSI 模式。注意direct 模式虽然支持 edge 触发和 level 触发但许多 SoC 实现里 APLIC 的 edge 检测是基于中断源信号的边沿锁存软件处理完必须显式清除挂起位否则会一直重复触发。我见过不少人栽在这上面。2.3 MSI 模式把中断从“电平”变成“消息”MSI 模式下APLIC 不再直接拉中断线给 CPU而是把每个中断源对应到一条 MSI 消息。简单理解就是硬件帮你做了一步中断源有效时APLIC 往 TARGET 寄存器指定的地址写一个值这个写操作会被 IMSIC 接收然后 IMSIC 再向 CPU 产生中断。配置路径大致如下把 DOMAINCFG.DM 设为 1切换到 MSI 模式。设置 TARGET 寄存器一般包含三个信息目标 hart 编号、目标中断文件编号也就是要投递到哪个特权级、目标中断向量编号。使能中断源等待外部信号或软件置位。APLIC 自动生成 MSI 消息CPU 的对应中断文件收到中断。我最开始理解 MSI 模式时有个误区以为 APLIC 和 IMSIC 要互相做“握手确认”其实不需要。APLIC 是“写完就完事了”像发了一封信IMSIC 收信后负责把信拆开、确认收件人、然后敲门通知 CPU。这个解耦设计让中断路径变得很干净也让多核扩展变得非常简单——理论上你可以把不同中断源的 MSI 指向不同 hart 的不同文件实现真正的负载均衡。3. IMSIC 落地按 hart 独立中断文件和 MSI 投递链路IMSIC 是 AIA 里让我觉得最“值钱”的模块。它把过去所有 hart 共享一个中断控制器的模型改成了每个 hart 都有自己独立的一组中断文件。这样做的好处非常直接不需要锁、不需要共享总线仲裁、也不需要在软件层做跨核分发。3.1 IMSIC 的地址布局和中断文件IMSIC 的内存映射是按 hart 展开的。每个 hart 占用一段连续的地址空间里面包含多个“中断文件”interrupt file。中断文件的数量和具体布局由 SoC 定义但常见做法是文件 0 给 M 模式用。文件 1 给 S 模式用。文件 2、3 给虚拟化相关的 VS 模式用。每个中断文件的寄存器基址比较规整比如SETIPNUMoffset 0x00写一个中断向量号相当于软件触发一次 MSI。CLRIPNUMoffset 0x04清除指定向量号的挂起状态。SETIEoffset 0x0C写 1 到对应位来使能某个中断。CLRIEoffset 0x10清除使能位。这套寄存器的意义在于软件可以直接通过内存写来触发或清除中断完全绕开了传统的中断控制器寄存器接口。对 CPU 而言IMSIC 地址空间就是一段普通内存但对中断路径而言一次写操作就是一次完整的中断投递。我调试时喜欢用 SETIPNUM 做自测先手动写一个中断号看 CPU 能不能收到外部中断。如果连这步都不通说明 IMSIC 的地址映射或 CPU 中断使能链路有问题跟外设无关。3.2 一组配置示例默认读取和直接投递实际操作中IMSIC 的初始化步骤通常是这样配置物理地址映射确保 CPU 能访问 IMSIC 的寄存器区域。通过 CSRs比如 miselect/mireg设置 IMSIC 的默认文件、默认中断号等属性。用 SETIE 使能需要处理的中断向量。打开 CPU 的中断使能M 模式开 mie.MEIES 模式开 sie.SEIE。等待中断到来或者用 SETIPNUM 测试。这里最需要注意的是第 2 步。AIA 规范里每个中断文件都有一组“间接寄存器”通过 miselect 选寄存器编号再通过 mireg 读写。很多驱动初始化流程就把中断使能放在这里做如果你布局文件选错、miselect 设置错中断会静默消失非常难查。还有一个常见细节IMSIC 的每个中断文件都有一个“优先级阈值寄存器”如果当前中断的优先级低于阈值IMSIC 不会向 CPU 投递。默认情况下阈值是 0允许所有中断但如果固件或驱动不小心把阈值抬高低优先级中断会一直不生效看起来就像中断丢了。提示配置 IMSIC 时确认每个 hart 的中断文件和你要投递的目标特权级一一对应。比如你想让设备中断直接到 S 模式MSI 的 target 就要指向该 hart 的 S 模式文件而不是文件 0。这个映射如果错了中断会跑到 M 模式固件里应用层什么也收不到。3.3 虚拟化场景下的中断透传IMSIC 支持虚拟化是它对比 PLIC 的最大卖点。在虚拟化场景下IMSIC 可以创建一组“guest interrupt file”由 VMM 配置好让客户机直接接收物理 MSI 中断而不需要 VMM 在每次中断时做软件模拟。这个过程涉及一些新的 CSR比如 hvien、hvip 等。它们的作用是把物理中断直接“注入”到客户机的 VS 模式中断状态。对 RISC-V 虚拟机监视器开发者来说这意味着中断虚拟化的开销从“每次中断都要 trap”变成了“一次配置、后续直通”。这个变化是革命性的也是 AIA 推广的核心动力之一。4. 从 PLIC 迁到 APLIC/IMSIC 的完整改造步骤现在进入真正动手的阶段。假设你的 SoC 原来用 PLIC现在硬件改成了 APLIC IMSIC软件侧尤其是设备树和驱动要怎么改我按自己的实操经验把改动拆成三块设备树、平台驱动初始化、以及中断处理入口。4.1 设备树节点的改法设备树是第一个要动的地方。PLIC 的典型节点长这样plic: interrupt-controllerc000000 { compatible sifive,plic-1.0.0; reg 0x0 0xc000000 0x0 0x4000; interrupt-controller; #interrupt-cells 1; interrupts-extended cpu0_intc 11 cpu0_intc 9; };改成 APLIC 时节点会变成类似这样aplic: interrupt-controllerc000000 { compatible sifive,aplic-1.0.0; reg 0x0 0xc000000 0x0 0x4000; interrupt-controller; #interrupt-cells 2; interrupts-extended cpu0_intc 11 cpu0_intc 9; };注意几个关键差异compatible 从 plic 变成了 aplic#interrupt-cells 可能从 1 变成 2取决于实现常见前一个 cell 是中断源编号后一个 cell 是触发类型。具体值务必看 SoC 手册我用过的一个平台 APLIC 的 cells 是 2另一个平台又是 1非常不统一。IMSIC 的设备树节点更复杂一些因为它要为每个 hart 列出映射关系。典型结构imsics: interrupt-controller24000000 { compatible sifive,imsics-1.0.0; reg 0x0 0x24000000 0x0 0x10000, 0x0 0x24010000 0x0 0x10000; interrupt-controller; #interrupt-cells 1; interrupts-extended cpu0_intc 35 cpu0_intc 11 cpu0_intc 9; };这里的 reg 每个条目对应一个 hart 的 IMSIC 地址区间interrupts-extended 列出该 hart 的 M 模式外部中断号、S 模式外部中断号等。改设备树时最忌讳“想当然”一定要对着 SoC 手册逐个寄存器核对。4.2 驱动初始化流程的改动点设备树改了之后驱动侧要处理的核心改动有三个第一中断控制器驱动要从 PLIC 驱动换成 APLIC/IMSIC 驱动。Linux 内核里这对应drivers/irqchip/irq-plic.c变成irq-aplic.c、irq-imsic.c。如果你的内核版本较老可能根本不认识 aplic/imsic需要自己移植驱动或升级内核。第二中断号映射逻辑要改。PLIC 驱动会把设备树里的中断源编号直接作为 Linux irq numberAPLIC 驱动需要先读取 domain 配置、计算源偏移再映射到全局中断号。这一步出问题的典型症状是设备树里的中断号明明对得上但驱动 request_irq 得不到正确中断。第三中断处理下半部的进入方式可能不同。PLIC 是靠读 claim 寄存器来确认中断源IMSIC 则直接用 CSRs比如读取 stopi/mtopi就能拿到当前优先级最高的中断号不需要访问内存映射的 claim 寄存器。这会让 IRQ 入口的代码路径变短性能和确定性都更好。我建议在迁移初期保留一套 PLIC 兼容驱动作为“对照组”先在 direct 模式下验证外设中断链路再切换到 MSI 模式验证性能收益。一步到位切到 MSI 模式出了问题反而不容易定位。4.3 小技巧保留一个 direct 模式的兼容路径很多 SoC 的 APLIC 硬件本身支持 direct 和 MSI 两种模式只是默认可能配了其中一种。我个人的建议是在 Bring-up 阶段先用 direct 模式跑通所有外设中断因为它的调试路径最短——中断线直接从 APLIC 拉到 CPU没有 MSI 地址映射、没有 IMSIC 文件选择这些变量。一旦 direct 模式全通再切到 MSI 模式这时候变量就只剩下“MSI 消息生成是否正确”和“IMSIC 投递是否正确”排查范围小很多。我当时在某款支持 AIA 的 FPGA 平台上就是这么干的整体调试时间至少缩短了一半。5. 迁移踩坑实录设备树、中断号和地址映射的三种教训最后这部分是我最想写的因为中断子系统一旦出问题往往不像普通功能 bug 那样有 log 可查更多是靠经验和耐心一点一点“试”出来的。下面三个问题是我在迁移过程中真实遇到过的代表性很强。5.1 中断号对不上APLIC 的偏移量与中断源映射第一个坑也是最隐蔽的坑设备树里的中断号不等于 APLIC 内部的中断源编号。PLIC 时代很多 SoC 直接把外设的中断线编号和设备树中断号对应起来简单直接。但 APLIC 不是这样它内部会把中断源分成“domain”每个 domain 有独立的基址偏移。我遇到的情况是UART 的中断线在 SoC 里是第 17 号设备树里也写的 17但 APLIC 的 SOURCECFG[17] 配的却是另一个外设。查了半天发现这家 SoC 的 APLIC 把 0~4 号中断源保留给了内部用途外设中断源从 5 号开始排而设备树的中断号沿用了传统 PLIC 时代的编号。解决方案很朴素逐个核对 SoC 手册里的中断源分配表并在 APLIC 驱动里加上统一偏移处理而不是改每个外设设备树节点。这个教训告诉我们迁移到新中断控制器时“中断号”这个看似铁板一块的概念必须重新审视一遍。5.2 IMSIC 地址不生效物理地址对齐和页表映射第二个坑出在 IMSIC 地址访问上。IMSIC 的寄存器访问要求比较严格物理地址必须按规定对齐中断文件之间的间隔也可能有对齐要求。我遇到的问题是IMSIC 寄存器写操作“吞掉了”SETIPNUM 写了没反应CPU 始终收不到中断。排查过程很痛苦。先查 CPU 是否开了中断使能没问题再查 APLIC 的 MSI target 配置也没问题最后发现是页表映射的问题——IMSIC 所在的物理地址区间没有被正确映射到虚拟地址空间导致访问落到了未定义的 memory region总线直接忽略写操作。这类问题在带 MMU 的 Linux 环境里尤其常见。PLIC 时代中断控制器的地址空间通常固定映射且早就配好IMSIC 作为新设备经常要走 ioremap 或设备树里的 ranges 属性做映射。如果 ranges 没写对或者驱动初始化顺序有误中断路径就会在你完全没料到的环节断掉。经验IMSIC 的映射配置完成后先用一个简单的内存读写工具直接对 SETIPNUM 地址写测试值看 CPU 能否收到中断。这一步通了再上外设中断否则后面排查会非常痛苦。5.3 设备树连接关系错误导致的中断风暴最后一个坑比较“轰烈”中断风暴。现象是系统启动后 CPU 一直进中断根本停不下来完全没法正常跑。我一开始以为是指针错了或者中断号分配冲突后来发现是设备树的 interrupts-extended 配错了。APLIC/IMSIC 的中断控制器节点和 CPU 中断控制器节点之间的连接关系必须严格遵循硬件实际布线。比如某颗 CPU 核的 M 模式外部中断线、S 模式外部中断线、VS 模式外部中断线分别接在 IMSIC 的哪个文件上这些在设备树里都有明确表达。我把 S 模式的中断线错配到了另一个 hart 上结果就是 A hart 的设备中断在 B hart 上触发而 B hart 驱动没初始化中断就一直挂起、不断重触发表现为中断风暴。排查方法倒是很直接关闭所有外设中断逐个使能定位是哪个中断源引发风暴然后用 trace 工具看中断落在了哪个 hart、哪个特权级和设备树一对比就能发现映射错误。这类问题只要心细一点配合工具链其实不难定位但在初期没经验时真的会被绕进去。6. 结合自己项目的一点体会做了几个平台的 RISC-V 中断子系统移植之后我对 AIA 的看法已经从“又一个要适配的标准”变成了“确实该这么设计”。特别是虚拟化场景和 MSI 支持PLIC 的老架构再怎么修修补补也很难达到同样效果。APLIC 加 IMSIC 的组合虽然配置门槛更高但换来的性能和可扩展性是实打实的。如果你也在做类似的迁移我给你三个建议第一设备树和手册必须对着看不要依赖经验想当然第二Bring-up 阶段尽量先走 APLIC direct 模式验证外设中断链路后再切 MSI 模式第三IMSIC 的地址映射和中断文件选择这两个环节值得你多花时间确认绝大多数“中断消失”的问题都出在这里。按照我个人的习惯还会在代码里留几个诊断用的开关——比如用 SETIPNUM 手动触发软件中断、用 mtopi/stopi 直接读当前最高优先级中断号。这些调试手段在关键时刻能帮你快速缩小范围比反复加 log 高效得多。希望这篇文章能让你在从 PLIC 到 APLIC/IMSIC 的迁移路上少踩几个坑。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门