0 参考
The RISC-V APLIC’s New Features – Stephen Marz
本博客系列涉及此处编写的代码:https://github.com/sgmarz/riscv_msi。
APLIC规范(仍处于草案阶段)是高级中断架构(AIA)规范的一部分,保存在此处:https://github.com/riscv/riscv-aia。
1 介绍
高级平台级中断控制器(APLIC)是SiFive公司的PLIC的高级版本。其主要提升在于**支持通过消息发送中断。**因此,当与IMSIC配对时,APLIC可以像任何其他硬件设备一样发送消息。
原始PLIC的主要目的是聚合、优先处理和发送硬件中断信号。它为操作系统提供了一种声明、处理和完成中断请求的方法。APILC通过 “外部中断” 引脚向特定的HART(硬件线程,即CPU核心)发送通知。然后,该HART通过从特定的PLIC寄存器(称为 claim 寄存器)读取来确定中断的来源。这将返回一个标识设备的数字。通常,这个数字指向连接硬件设备和PLIC的特定线路。
2 APLIC
新的APLIC与SiFive PLIC不兼容。其中一些概念和特性是相同的,但寄存器文件和映射是不同的。从技术上讲,可以实现一个没有完整APLIC的系统,但AIA文档明确指出:“ 完全符合高级中断架构需要APLIC ”。
首先,APLIC寄存器布局如下:
1 | struct Aplic { |
下面介绍一些重点寄存器:domaincfg/sourcecfg/setip/clrip/genmsi/target。
2.1 Domain Configuration Register (domaincfg)

域配置寄存器(32位)该寄存器有三个可用字段:
- 中断使能(IE,第8位)
- 传递模式(DM,第2位)
- 大小端模式(BE,第0位)
首先的 8 位是一个字节顺序标记,目的在于测试寄存器是大端序还是小端序。它被设置为 0x80,以便我们可以测试寄存器的字节顺序。例如,如果我们执行一个 “加载字”( lw 指令在RISC-V中),并且在第一个字节中得到 0x80,那意味着机器是大端序。如果没有得到 0x80,则是小端序,并且加载的字节包含传输模式和大端序位。
中断使能位(Interrupt enable bit)用于使 APLIC 能够发送中断信号(1 = 使能,0 = 禁用)。这并不意味着中断一定会被接收到,而仅仅表示 APLIC 可以通过触发一个挂起位来发送中断信号。
中断投递模式(DM)位允许配置 APLIC 以发送常规中断,类似于旧的PLIC,或者以消息形式发送中断(MSI)。如果 DM 位设置为 0,则会发送直接中断,类似于旧的 APLIC。如果 DM 位设置为 1,则会发送 MSIs。
大端位允许 APLIC 以大端模式(BE = 1)或以小端模式(BE = 0)写入消息。但是,BE位还会影响多字节域配置寄存器的顺序。最高有效字节被故意设置为 0x80,作为一种字节顺序标记。
我们可以写一个Rust函数来设置域注册。该注册函数中的所有参数都是布尔类型。
1 | impl Aplic { |
2.2 Source Configuration Registers (sourcecfg[u32; 1023])

If bit 10 (delegate) is 0, SM is in bits 2:0. If D=1, bits 9:0 mark the child index.
如果位10(代理)为0,则SM位于位 2:0。如果D=1,则位 9:0 标记子索引。
每个可能的中断都有一个 sourcecfg 寄存器。请记住,中断0是不可能的,所以中断 1 的源配置寄存器在 sourcecfg[0] 中。这就是为什么我提供的 Rust 代码示例会从中断源的值中减去 1 的原因。
委托位 D(位10)可以读取以确定所给中断是否已被委托。该位是可读写的。如果我们将1写入该字段,则将其委托给子域。如果该特定源没有子域,该位将始终被读取为0。
源模式位 SM(位2:0)控制着对于那些未委派的中断如何触发。如果一个中断被委派(D=1),位9:0描述了它被委派给的子中断的索引。如果中断未被委派(D=0),那么每个中断源可以通过以下方式之一配置为触发 “挂起” 中断。
| 3-bit “SM” value | Register Name | Description |
|---|---|---|
| 0 | Inactive | The interrupt source cannot generate an interrupt and is inactive. |
| 1 | Detached | The interrupt can only be generated by writing directly to the APLIC. |
| 2, 3 | – | Reserved |
| 4 | Edge1 | The interrupt is asserted on a rising edge (from 0 to 1). |
| 5 | Edge0 | The interrupt is asserted on a falling edge (from 1 to 0). |
| 6 | Level1 | The interrupt is asserted when high (device IRQ pin is asserted). |
| 7 | Level0 | The interrupt is asserted when low (device IRQ pin is deasserted). |
一个不活动的中断源无法通过APLIC产生中断,我们也无法通过写入中断挂起寄存器来手动触发中断。一个分离的中断源无法被设备触发,但是我们可以手动写入MMIO APLIC寄存器来触发中断。
1 |
|
2.3 Set/Clear Pending/Enable Interrupt (setip/clrip) Registers
这些寄存器控制着中断是否处于挂起状态。当启用的中断被触发时,APLIC将自动设置这些位;然而,我们也可以手动将中断设置为挂起中断。
这些寄存器与IMSIC寄存器不同。有一组寄存器用于设置待处理中断,另一组寄存器用于清除待处理中断。
setipnum 和 clripnum 寄存器的功能与以下使用 setip/clrip 寄存器的 Rust 函数类似。
1 | impl Aplic |
中断使能寄存器 setie/clrie 的作用类似于中断挂起寄存器,但它们允许中断被信号化。如果一个中断未被使能,则该中断会被屏蔽,无法被触发。
1 | impl Aplic { |
2.4 Generate MSI Register (genmsi)

genmsi 寄存器中有两个读/写字段和一个只读字段。即使直接写入IMSIC更高效,但可以使用 genmsi 寄存器来触发MSI写入。
HART Index是您想要发送MSI的目标HART,EIID(外部中断标识符)是要写入IMSIC的值。通常,EIID与您想要触发的中断号相同。
2.5 Target Control Registers (target[u32; 1032])
目标控制寄存器根据 APLIC 的配置方式有两种不同形式。如果 APLIC 配置为**直接传递模式,**则寄存器包含一个 HART Index和一个优先级。较小的优先级表示较高,因此 10 的优先级高于 20。

1 | impl Aplic { |
**在MSI交付模式下,**寄存器包含一个HART索引、客户索引和外部中断标识符(EIID)。

1 | impl Aplic { |
2.6 Interrupt Delivery Control
正如我之前提到的,APLIC可以配置为MSI模式或直接模式:
- 在MSI模式下,消息由输入MSI控制器(IMSIC)处理;
- 然而,在直接模式下,APLIC本身将控制中断。这是通过APLIC的中断传递控制(IDC)部分来实现的。
对于QEMU上的 virt 虚拟机,中断传递控制寄存器 IDC 被内存映射到0x地址。寄存器的布局如下。
| Offset | Size (bytes) | Register Name |
|---|---|---|
| 0 | 4 | idelivery |
| 4 | 4 | iforce |
| 8 | 4 | ithreshold |
| 24 | 4 | topi |
| 28 | 4 | claimi |
每个IDC用于一个独立的HART,长度为32字节。因此,HART #0的寄存器从偏移量0开始,HART #1的寄存器从偏移量32开始,以此类推。
idelivery 寄存器启用了APLIC的直接传递模式。如果该寄存器设置为1,则APLIC可以传递中断,否则,如果该寄存器设置为0,则APLIC不传递中断。

The idelivery register
中断强制寄存器强制 APLIC 提交中断 #0。如果我们向该寄存器写入1,并且 APLIC 的中断已启用,则这将发出一个中断信号。

The iforce register
中断阈值寄存器设置了能够被监听到的中断优先级的阈值。阈值为0意味着所有中断都可以被监听到。如果值非零,则该值或更高优先级的任何中断都会被屏蔽,因此无法被监听到。在这种情况下,与其他所有情况一样,数字越小,优先级越高。不要被0所迷惑,它只是一个特殊值,可以解除所有优先级的屏蔽。然而,阈值为1意味着所有中断都会被屏蔽,而阈值为2意味着只有优先级1的中断可以被监听到。

The ithreshold register
topi 寄存器保存的是最高优先级且已启用的中断号。该寄存器分为两部分:
25:16位保存中断号7:0位保存中断优先级

The topi and claim registers
申请寄存器与顶级中断寄存器相同,只是它表示我们正在申请顶级中断。当我们从该寄存器读取时,给定寄存器的挂起位将被清零。
我制作的仓库上的操作系统只使用MSI传递模式,因为这种传递控制方式与旧的PLIC非常相似。
3 结论
QEMU的 virt 虚拟机将UART连接到外部IRQ#10,因此我们可以在UART接收器有数据时使用APLIC发送消息。这要求我们使用上述寄存器来设置APLIC。
在下面的代码中,我在M/S模式之间进行了分割,并将UART委派给S模式APLIC(索引0)。
1 | pub fn aplic_init() { |
当UART发送中断时,我们现在可以在IMSIC中编写UART处理程序。
1 | pub fn imsic_handle(pm: PrivMode) { |
上面的代码将任何消息#10转发到 console_irq,而 console_irq 则从UART设备的接收缓冲寄存器中弹出一个值。
一切都说完了,我们可以开始接收UART消息了。

最后整理于 2025-11-11