你提供的这两个函数是 PLIC 控制器中中断使能位(IE, Interrupt Enable)的核心读写接口,负责精准定位并操作指定上下文、指定中断块的 PLIC 使能寄存器,我会从功能、地址计算逻辑、参数含义、使用场景四个维度拆解,帮你彻底理解:
一、整体功能总结
plic_get_ie:读取 PLIC 中「指定上下文 + 指定中断块」的中断使能寄存器值;plic_set_ie:向 PLIC 中「指定上下文 + 指定中断块」的中断使能寄存器写入值;
这两个函数是fdt_plic_context_save/restore的底层实现依赖——保存上下文时调用plic_get_ie读取使能位,恢复时调用plic_set_ie写回使能位。
二、核心逻辑:PLIC 使能寄存器地址计算
PLCI 使能寄存器的地址是基址 + 上下文偏移 + 中断块偏移,代码中通过以下公式计算:
plic_ie = plic->addr (PLIC基址) + PLIC_ENABLE_BASE (使能寄存器块起始偏移) + PLIC_ENABLE_STRIDE * cntxid (上下文偏移) + 4 * word_index (中断块偏移)逐部分解析地址构成:
| 地址组成部分 | 含义 |
|---|---|
plic->addr | PLIC 控制器在 SoC 中的物理基地址(如 QEMU 中的0xC000000),由设备树解析得到 |
PLIC_ENABLE_BASE | PLIC 使能寄存器块的起始偏移(标准 PLIC 规范中为0x2000,和你之前翻译的文档一致) |
PLIC_ENABLE_STRIDE * cntxid | 每个上下文(Context)对应的使能寄存器块偏移: - PLIC_ENABLE_STRIDE:单个上下文的使能寄存器块大小(标准为0x80,对应 32 个中断块×4字节);- cntxid:上下文ID(如 Hart0 的 M 态上下文=0,S 态上下文=1) |
4 * word_index | 单个上下文内,按 32 个中断为 1 块的偏移: - word_index:中断块编号(如 0=irq031,1=irq3263);- 4 是因为每个中断块对应 32 位(4字节)寄存器 |
地址计算示例(贴合你之前翻译的文档):
假设:
- PLIC 基址
plic->addr = 0xC000000; PLIC_ENABLE_BASE = 0x2000;PLIC_ENABLE_STRIDE = 0x80;- 上下文ID
cntxid = 0(Hart0 M 态); - 中断块
word_index = 0(irq0~31);
则最终地址:
0xC000000 + 0x2000 + 0x80*0 + 4*0 = 0xC002000和你之前翻译的文档中「上下文0的中断源#0至#31使能位地址=0x002000」(基址偏移)完全一致。
三、函数参数与返回值解析
1. plic_get_ie 函数
staticu32plic_get_ie(conststructplic_data*plic,u32 cntxid,u32 word_index)| 参数 | 含义 |
|---|---|
plic | PLIC 硬件数据结构体指针,包含基址、总中断数、上下文数等核心信息 |
cntxid | 上下文ID(区分不同 Hart/不同特权级:如 Hart0 M 态=0,Hart0 S 态=1) |
word_index | 中断块编号(按 32 个中断为 1 块,范围 0 ~ (总中断数/32 - 1)) |
| 返回值 | 对应中断块的使能寄存器值(32位,每1位对应1个中断的使能状态:1=使能,0=禁用) |
2. plic_set_ie 函数
staticvoidplic_set_ie(conststructplic_data*plic,u32 cntxid,u32 word_index,u32 val)| 参数 | 含义 |
|---|---|
plic/cntxid/word_index | 和plic_get_ie完全一致,定位要写入的使能寄存器 |
val | 要写入的 32 位值(每1位设置对应中断的使能状态:1=使能,0=禁用) |
四、典型使用场景(结合上下文保存/恢复)
// 示例:保存 Hart0 S 态的 irq0~31 使能位structplic_data*plic=plic_get_hart_data_ptr(scratch);u32 cntxid=plic_get_hart_scontext(scratch);// 获取S态上下文IDu32 word_index=0;// irq0~31 中断块// 读取使能位(保存到缓冲区)u32 enable_val=plic_get_ie(plic,cntxid,word_index);enable_buf[word_index]=enable_val;// 示例:恢复 Hart0 S 态的 irq0~31 使能位u32 restore_val=enable_buf[word_index];plic_set_ie(plic,cntxid,word_index,restore_val);五、关键注意点
- 地址对齐:
4 * word_index保证了地址按 4 字节对齐(RISC-V 32位寄存器要求),避免非对齐访问异常; - volatile 关键字:
volatile void *plic_ie防止编译器优化寄存器读写(PLIC 是硬件寄存器,必须实时读写); - 上下文ID 映射:
cntxid不是直接的 Hart ID,而是 PLIC 为每个 Hart 的 M/S 态分配的上下文编号(如 Hart0 M 态=0,S 态=1;Hart1 M 态=2,S 态=3)。
| 关键点 | plic_get_ie | plic_set_ie |
|---|---|---|
| 核心动作 | 计算地址 → 读取 32 位使能寄存器值 | 计算地址 → 写入 32 位使能寄存器值 |
| 地址计算核心 | 基址 + 上下文偏移 + 中断块偏移 | 同 get_ie,保证地址精准匹配 |
| 底层依赖 | readl(读取32位内存映射寄存器) | writel(写入32位内存映射寄存器) |
| 上层调用场景 | PLIC 上下文保存(fdt_plic_context_save) | PLIC 上下文恢复(fdt_plic_context_restore) |
六、cntxid 分配规则
6.1、通用分配规则(最常见的场景)
绝大多数 PLIC 实现中,上下文ID 按「Hart 编号 + 特权级」线性分配,规则为:
cntxid = 2 × hart_id + (特权级标识)- 特权级标识:M 态 = 0,S 态 = 1;
- 示例:
- Hart0 M 态 →
2×0 + 0 = 0; - Hart0 S 态 →
2×0 + 1 = 1; - Hart1 M 态 →
2×1 + 0 = 2; - Hart1 S 态 →
2×1 + 1 = 3; - Hart2 M 态 →
2×2 + 0 = 4; - Hart2 S 态 →
2×2 + 1 = 5;
- Hart0 M 态 →
这和你理解的「Hart1 M=2、S=3」完全一致,是 OpenSBI/QEMU/Linux 中最主流的分配方式。
6.2、特殊情况:上下文ID 分配的灵活性
PLIC 规范仅定义「每个上下文对应独立的使能/阈值寄存器」,但未强制上下文ID 的分配规则,部分场景会有差异:
- 仅支持 M 态的 PLIC:
嵌入式极简 PLIC 可能只支持 M 态,此时上下文ID 直接等于 Hart ID(如 Hart0=0、Hart1=1、Hart2=2),无 S 态上下文。 - 虚拟化场景(H 扩展):
支持虚拟化的 PLIC 会为 VS/VU 态分配额外上下文,规则变为:cntxid = 4 × hart_id + (特权级标识)- Hart0 M=0、S=1、VS=2、VU=3;
- Hart1 M=4、S=5、VS=6、VU=7;
- 厂商自定义分配:
部分 SoC 厂商会按「特权级优先」分配(如所有 M 态上下文在前,S 态在后):- Hart0 M=0、Hart1 M=1、Hart2 M=2;
- Hart0 S=3、Hart1 S=4、Hart2 S=5;
6.3、代码中如何确认实际的 cntxid 映射
在 OpenSBI 中,无需硬编码,可通过以下函数获取当前 Hart 对应特权级的 cntxid:
structsbi_scratch*scratch=sbi_scratch_thishart_ptr();// 获取当前 Hart M 态的 cntxidu32 m_cntxid=plic_get_hart_mcontext(scratch);// 获取当前 Hart S 态的 cntxidu32 s_cntxid=plic_get_hart_scontext(scratch);- 对于 Hart0,
m_cntxid通常返回 0,s_cntxid返回 1; - 对于 Hart1,
m_cntxid通常返回 2,s_cntxid返回 3;
这也是你之前看到的fdt_plic_context_save/restore中,通过smode选择plic_get_hart_scontext/plic_get_hart_mcontext的原因——固件已封装好 cntxid 映射,无需开发者手动计算。
小结
| 关键点 | 通用规则(主流) | 特殊场景 |
|---|---|---|
| Hart1 M 态 cntxid | 2(符合你的理解) | 1(仅M态PLIC)/4(虚拟化场景) |
| Hart1 S 态 cntxid | 3(符合你的理解) | 无(仅M态PLIC)/5(虚拟化场景) |
| 核心建议 | 无需硬编码,通过plic_get_hart_*context获取实际 cntxid | 避免依赖固定数值,以固件提供的接口为准 |
简单来说:在无虚拟化、支持 M/S 态的标准 PLIC 中,Hart1 M=2、S=3 是完全正确的;若涉及特殊场景,以代码中plic_get_hart_*context的返回值为准即可。