diff --git a/articles/20230726-riscv-pmp-1.md b/articles/20230726-riscv-pmp-1.md new file mode 100644 index 0000000000000000000000000000000000000000..07ac5f5f62b9b116846196c35dab5df237102b29 --- /dev/null +++ b/articles/20230726-riscv-pmp-1.md @@ -0,0 +1,115 @@ +> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.1 - [codeinline tables urls pangu autocorrect]
+> Author: Kepontry
+> Date: 2023/7/26
+> Revisor: Falcon ; Bin Meng
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Proposal: [VisionFive 2 开发板软硬件评测及软件 gap 分析](https://gitee.com/tinylab/riscv-linux/issues/I64ESM)
+> Sponsor: PLCT Lab, ISCAS + +# RISC-V 物理内存保护(PMP)机制探究 + +## 前言 + +本文是物理内存保护(Physical Memory Protection,PMP)系列的第一篇文章,主要从官方数据手册出发,介绍 PMP 机制的原理与设置。 + +物理内存保护(即限制 hart 上软件可以访问的物理地址范围)可以提供安全处理和故障隔离功能,对现代处理器来说是非常重要的。PMP 机制适用于所有特权模式为 S 或 U 的指令和数据访问,通过在 M 态下修改每个 hart 对应的控制寄存器,可以指定每个物理内存区域的读、写和执行等访问权限。此外,PMP 机制也可用于 S 态中的页表访问。违反 PMP 机制的访存将被处理器捕获并触发异常。 + +## 物理内存保护机制 + +在 [RISC-V 特权指令集手册][001] 的第 3.7 节中介绍了内存保护机制。每一个 PMP 项定义了一个访问控制区域。一个 PMP 项包括一个 8 位控制寄存器和一个地址寄存器,仅可在 M 态下访问,处理器支持最多 64 个 PMP 项。PMP 访问控制区域的大小是可设置的,最小可支持 4 字节大小的区域。 + +### PMP 控制寄存器 + +在处理器实现中,为了最小化上下文切换时间,PMP 项中的控制寄存器被打包存储在 CSR 寄存器中。在 RV32 中,每个 CSR 寄存器为 32 位,能存储 4 个 PMP 控制寄存器,共有 16 个 CSR 寄存器(pmpcfg0-pmpcfg15),包含 64 个 PMP 控制寄存器(pmp0cfg-pmp63cfg)。在 RV64 中,每个 CSR 寄存器能存储 8 个 PMP 控制寄存器,只需要 8 个 CSR 寄存器即可实现相同功能。为了与 RV32 保持一致,降低软件支持成本,RV64 中的 CSR 寄存器编号均为偶数,即 pmpcfg0,pmpcfg2,...,pmpcfg14,奇数编号的寄存器是非法的。 + +如下表所示,PMP 控制寄存器共包括 L、A、X、W 和 R 共五个字段,R、W 和 X 位表示对应的地址区域是否具有读、写和指令执行权限。其中,R=0 且 W=1 表示该内存区域被保留。L 位用于锁定区域,上锁后,对相应控制和地址寄存器的写入将被忽略。已锁定的 PMP 区域只能通过系统复位解锁。A 字段用于指定地址区域的计算方式,将在“地址区域计算规则”一节中介绍。 + +| 7 | 6-5 | 4-3 | 2 | 1 | 0 | +|---|-----|-----|---|---|---| +| L | 0 | A | X | W | R | + +尝试从没有执行权限的 PMP 区域中提取指令,从没有读取权限的 PMP 区域中读取数据,向没有写入权限的 PMP 区域执行写入、条件写入或 AMO 指令,都将将引发异常。 + +### PMP 地址寄存器 + +每个 PMP 地址寄存器对应一个 CSR 寄存器,命名为 pmpaddr0-pmpaddr63。在 RV32 中,寄存器存储 34 位物理地址的第 2 至第 33 位(使用 Sv32 分页机制)。在 RV64 中,寄存器存储 56 位物理地址的第 2 至第 55 位(使用 Sv39 或 Sv48 分页机制)。 + +### 地址区域计算规则 + +PMP 地址寄存器中存放区域的基地址,而 PMP 控制寄存器中的 A 字段用于确定地址计算规则,从而确定区域的范围。 + +* 当 A=0 时,此 PMP 条目被禁用,不匹配任何地址。 +* 当 A=1 时,计算规则为“紧邻边界”(Top of Range,TOR)即区域 X 的结束地址紧邻下一区域的基地址寄存器 pmpaddrX 的值。该地址区域可表示为 [pmpaddr(X-1), pmpaddrX)。特别的,PMP 条目 0 表示的地址区域为 [0, pmpaddr0)。如果 pmpaddri−1 ≥ pmpaddri,则 PMP 条目 i 不匹配任何地址,即区域为空。 +* 当 A=2 时,区域大小固定为 4 字节(Naturally aligned four-byte region,NA4)。 +* 当 A=3 时,区域大小可变,为 2 的幂次方大小区域(Naturally aligned power-of-two region,NAPOT)。 + +| A | 名称 | 描述 | +|---|-------|------------------------------| +| 0 | OFF | 区域为空(禁用) | +| 1 | TOR | 紧邻下一边界 | +| 2 | NA4 | 4 字节大小区域 | +| 3 | NAPOT | 2 的幂次方大小区域,大小≥8 字节 | + +在 NAPOT 规则中,PMP 地址寄存器的低位来用来表示区域的大小,高位表示以 4 字节为单位的基址,中间用一个 0 隔开。如下表所示,如果用 G 表示 0 的下标,则 PMP 访问控制区域大小为 2^(G+3) 字节。 + +| pmpaddr | pmpcfg.A | Match type and size | +|---------------|-----------|-------------------------------| +| yyyy...yyyy | NA4 | 4-byte NAPOT range | +| yyyy...yyy0 | NAPOT | 8-byte NAPOT range | +| yyyy...yy01 | NAPOT | 16-byte NAPOT range | +| yyyy...y011 | NAPOT | 32-byte NAPOT range | +| . . . . . . . | . . . . . | . . . . . . . . . . . . . . . | + +### 权限检查 + +如果一个 PMP 条目匹配访问的所有字节,则由 L、R、W 和 X 位确定访问是成功还是失败。除了锁定 PMP 条目外,L 位还指示对 M 模式访问是否执行 R/W/X 权限检查。如果 L 位被清除,且访问的特权模式为 M,则访问成功。否则,如果 L 位被设置或访问的特权模式为 S 或 U,则只有与访问类型对应的 R、W 或 X 位被设置时访问才成功。 + +如果没有 PMP 条目与 M 模式访问匹配,则访问成功。如果没有 PMP 条目与 S 模式或 U 模式访问匹配,但至少实现了一个 PMP 条目,则访问失败。如果实现了至少一个 PMP 条目,但所有 PMP 条目的 A 字段都被设置为 OFF,则所有 S 模式和 U 模式内存访问都将失败。 + +### 优先级机制 + +PMP 条目间采用静态优先级,当区间重叠时,编号小的条目优先级更高。匹配到访问中任何字节的最低编号的 PMP 条目决定本次访问是成功还是失败。 + +### 原子性 + +匹配的 PMP 条目必须包含访问的所有字节,否则无论 L、R、W 和 X 位的值是什么,访问都将失败。例如,如果一个 PMP 条目被配置为匹配 4 字节区域 0xC–0xF,且为最高优先级条目,那么对区域 0x8–0xF 的 8 字节访问将失败。 + +需要注意的是,单条指令可能会生成多次访问,这些访问可能不是相互原子的。如果其中一个访问未通过 PMP 检查并产生异常,该指令的其他访问可能通过 PMP 检查、成功执行并最终可见,从而带来潜在危害。例如在某些实现中,不对齐的读取、写入和指令获取可能被分解成多个访问。 + +### SiFive U7 系列 CPU 实现 + +在 SiFive 提供的 [U74 数据手册][002] 中介绍了 U7 系列 CPU 的物理内存保护机制实现,它共有 8 个地址寄存器,打包存储在一个 CSR 寄存器 pmpcfg0 中,支持 8 个访问控制区域。 + +该系列 CPU 支持的最小物理内存保护区域为 4KB,所以不支持 NA4 地址计算规则,NAPOT 地址计算规则也受此限制。通过分析 [OpenSBI 源码][003] 可以发现,在设置物理内存保护功能时,首先需要获取当前处理器支持的 PMP 粒度 `pmp_gran_log2`,并判断即将设置的区域大小 `reg->order` 是否大于等于它(二者都已取为以 2 为底的对数)。如果满足条件,则执行 `pmp_set` 函数设置 PMP 寄存器,否则设置失败。 + +```C +// lib/sbi/sbi_hart.c:392 +int sbi_hart_pmp_configure(struct sbi_scratch *scratch) +{ + ... + pmp_gran_log2 = log2roundup(sbi_hart_pmp_granularity(scratch)); + ... + pmp_addr = reg->base >> PMP_SHIFT; + if (pmp_gran_log2 <= reg->order && pmp_addr < pmp_addr_max) + pmp_set(pmp_idx++, pmp_flags, reg->base, reg->order); + else { + sbi_printf("Can not configure pmp for domain %s", dom->name); + sbi_printf(" because memory region address %lx or size %lx is not in range\n", + reg->base, reg->order); + } + ... +} +``` + +## 总结 + +本文简要介绍了 RISC-V 中的物理内存保护机制,后续将基于 QEMU 与开发板,展开进一步的实验与测试。 + +## 参考资料 + +- [The RISC-V Instruction Set Manual (Volume II: Privileged Architecture)][001] +- [SiFive U74 手册][002] + +[001]: https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf +[002]: https://starfivetech.com/uploads/u74_core_complex_manual_21G1.pdf +[003]: https://github.com/riscv-software-src/opensbi/