概述
在RISC-V体系架构中,PMP是用于保护物理内存访问权限的机制。PMP机制允许为不同的物理内存区域指定访问权限(读、写、执行)。这种机制使得运行在RISC-V处理器上的软件只能访问被明确授权的物理地址空间,从而提高了系统的安全性和稳定性。
SMEPMP机制是一个增强型的PMP扩展,是为了防止M模式内存访问和执行的PMP增强。因为对于一段内存,PMP机制无法实现只有U/S模式访问,而M模式无法访问的情况,SMEPMP机制就是为了解决这一点。
PMP
在RISC-V体系架构中,M模式权限最高,可以访问全部系统内存资源。在复位时,S/U模式默认没有对内存的访问权限(包括指令预取和数据访问),其需要M模式进行PMP权限配置。当处理器处于S/U模式时,PMP会对每次的访问进行权限检查。另外,如果PMP配置为锁定状态,M模式下软件访问内存也需要进行PMP权限检查。设计者可以配置PMP保护的粒度,但标准的PMP保护区域至少为4个字节。
PMA可以配合PMA一起进行检查和保护。另外如果访问的地址是虚拟地址,也需要进行PMP权限检查。
CSR寄存器
PMP使用配置表项(8位宽,用pmpNcfg表示,N表示表项数)和地址寄存器,定义了一个访问控制区域的配置属性。RISC-V体系架构最大支持64个表项,芯片设计可以根据实际情况实现0个、16个或者64个PMP表项。
以RV32为例,有16个CSR寄存器用于配置PMP,即pmpcfg0~pmpcfg15
,每个CSR寄存器又划分为4个PMP表项,因此总共支持64(16*4)个PMP表项。
每个PMP表项(pmpNcfg)的总共8位,定义如下:
每个字段的含义如下:
字段 | 位 | 说明 |
---|---|---|
R | Bit[0] | 配置读权限 |
W | Bit[1] | 配置写权限 |
X | Bit[2] | 配置执行权限 |
A | Bit[4:3]] | 地址匹配模式,支持OFF/TOR/NA4/NAPOT四种模式 |
L | Bit[7] | 锁定状态,如果为0表示PMP只对S/U模式生效;如果为1针对所有模式,即对M/S/U模式均生效 |
关于地址匹配模式,定义如下:
A | 名称 | 说明 |
---|---|---|
0 | OFF | 不进行PMP权限检查 |
1 | TOR | 地址边界模式 |
2 | NA4 | 固定4字节的地址粒度 |
3 | NAPOT | 2n的地址粒度,最少8字节 |
同样,有64个PMP地址寄存器的CSR,即pmpadr0-pmpaddr63
。以RV32为例,如下所示,使用Bit[31:0]存储访问地址的Bit[33:2]。
锁定和特权模式
如果PMP表项中的L字段置位,再次配置表项寄存器和地址寄存器将无效,直至硬件复位。
另外如果PMP表项中的L字段置位,M模式的访问也需要进行PMP检查;如果L字段清零,M模式的PMP表项均会成功,即不进行PMP检查。
优先级与匹配逻辑
如果一个地址匹配到多个PMP表项,那么编号小的PMP表项优先级最高。另外PMP检查也是基于地址范围的,例如如果PMP配置的地址范围为4字节区域[0xC-0xF],那么一个8字节的访问[0x8-0xF]将会失败。
如果M模式没有匹配到任何PMP表项,其访问均会成功。而对于S/U模式,如果没有匹配到任何PMP表项,但只要设置了一个表项,其访问均会失败。
SMEPMP
RISC-V体系架构通过sstatus
中的SUM
比特和页表中的U
比特提供两种机制:SMAP(Supervisor Memory Access Prevention)和SMEP(Supervisor Memory Execution Prevention)。前者防止S模式访问U模式下的内存数据,后者防止S模式执行U模式下的内存代码。
SMAP和SMEP机制只是针对S模式,而对于M模式,此前的标准没有提供这些特性。SMEPMP机制就是为了给M模式提供类似SMAP/SMEP支持。
为了便于理解,定义了一些术语。
名称 | 说明 |
---|---|
PMP Entry | 一对pmpcfg 、pmpaddr 寄存器 |
PMP Rule | 寄存器pmpcfg 和pmpaddr 中的内容,表示保护的物理内存区域 |
Ignored | 忽略PMP Rule检查规则,允许所有访问请求 |
Enforced | 只允许PMP Rule中声明的访问权限请求,PMP检查失败会引发异常 |
Denied | 忽略PMP Rule检查规则,不允许任何访问请求 |
Locked | PMP表项中的pmpcfg.L 置位 |
PMP reset | 所有PMP配置复位,恢复成默认值 |
SMEPMP机制
新增一个M模式的CSR寄存器mseccfg
(Machine Security Configuration),用于配置不同的安全特性。mseccfg
目前引入三个bit:
- MML:M模式锁定(Machine Mode Lockdown)
- MMWP:M模式白名单策略(Machine-Mode alloWhlist Policy)
- RLB:规则锁定绕过(Rule Locking Bypass)
主要是新增了msecfg.MML
位,其和之前的PMP中的mppcfg.L
位,一起配合使用。先考虑请求的地址匹配到PMP表项(下图上半部分):
- 当
msecfg.MML=0
时,访问检查机制还是和之前没有SMEPMP一样,即下图左半部分。 - 当
msecfg.MML=1
时,即如下图右半部分(先暂时不考虑最后三行的情形):- 如果
mppcfg.L=0
,S/U模式根据PMP表项进行权限检查(Enforced),而禁止M模式访问(Denied) - 如果
mppcfg.L=1
,禁止S/U模式模式访问(Denied&Locked),而M模式据PMP表项进行权限检查(Enforced&Locked)
- 如果
针对最后三行的情形,主要是保证写和执行权限不会同时存在,解决不同特权等级的共享内存问题,因为SMEPMP机制造成M模式与S/U模式之间无法共享内存,会对性能造成影响。
如果mppcfg.L=1
和msecfg.MML=1
,即配置的区域上锁后,S/U模式就不能再次访问该区域。SMEPMP引入一个msecfg.RLB
位,关闭锁定效果,注意RISC-V建议这只能用于调试或者简单的环境。
再考虑请求的地址没有匹配到PMP表项(上图下半部分):
- 当
msecfg.MMWP=1
时,拒绝所有模式的访问 - 当
msecfg.MMWP=0
时:- 如果
mppcfg.MML=0
,拒绝S/U模式访问(Denied),M模式忽略权限检查(Ignored) - 如果
mppcfg.MML=1
,拒绝S/U模式访问(Denied),M模式只允许读写权限(RW Ignored),拒绝执行权限(X Denied)
- 如果
代码
在OpenSBI的初始化代码中,对PMP进行了配置,针对是否具有SMEPMP扩展,有两种方式:只有PMP机制调用sbi_hart_oldpmp_configure
函数,有SMEPMP机制调用sbi_hart_smepmp_configure
函数:
int sbi_hart_pmp_configure(struct sbi_scratch *scratch)
{
int rc;
unsigned int pmp_bits, pmp_log2gran;
unsigned int pmp_count = sbi_hart_pmp_count(scratch);
unsigned long pmp_addr_max;
if (!pmp_count)
return 0;
pmp_log2gran = sbi_hart_pmp_log2gran(scratch);
pmp_bits = sbi_hart_pmp_addrbits(scratch) - 1;
pmp_addr_max = (1UL << pmp_bits) | ((1UL << pmp_bits) - 1);
if (sbi_hart_has_extension(scratch, SBI_HART_EXT_SMEPMP))
rc = sbi_hart_smepmp_configure(scratch, pmp_count,
pmp_log2gran, pmp_addr_max);
else
rc = sbi_hart_oldpmp_configure(scratch, pmp_count,
pmp_log2gran, pmp_addr_max);
/*
* As per section 3.7.2 of privileged specification v1.12,
* virtual address translations can be speculatively performed
* (even before actual access). These, along with PMP traslations,
* can be cached. This can pose a problem with CPU hotplug
* and non-retentive suspend scenario because PMP states are
* not preserved.
* It is advisable to flush the caching structures under such
* conditions.
*/
if (misa_extension('S')) {
__asm__ __volatile__("sfence.vma");
/*
* If hypervisor mode is supported, flush caching
* structures in guest mode too.
*/
if (misa_extension('H'))
__sbi_hfence_gvma_all();
}
return rc;
}
PMP
下面是前一种PMP机制的实现:
static int sbi_hart_oldpmp_configure(struct sbi_scratch *scratch,
unsigned int pmp_count,
unsigned int pmp_log2gran,
unsigned long pmp_addr_max)
{
struct sbi_domain_memregion *reg;
struct sbi_domain *dom = sbi_domain_thishart_ptr();
unsigned int pmp_idx = 0;
unsigned int pmp_flags;
unsigned long pmp_addr;
sbi_domain_for_each_memregion(dom, reg) {
if (pmp_count <= pmp_idx)
break;
pmp_flags = 0;
/*
* If permissions are to be enforced for all modes on
* this region, the lock bit should be set.
*/
if (reg->flags & SBI_DOMAIN_MEMREGION_ENF_PERMISSIONS)
pmp_flags |= PMP_L;
if (reg->flags & SBI_DOMAIN_MEMREGION_SU_READABLE)
pmp_flags |= PMP_R;
if (reg->flags & SBI_DOMAIN_MEMREGION_SU_WRITABLE)
pmp_flags |= PMP_W;
if (reg->flags & SBI_DOMAIN_MEMREGION_SU_EXECUTABLE)
pmp_flags |= PMP_X;
pmp_addr = reg->base >> PMP_SHIFT;
if (pmp_log2gran <= 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 because"
" memory region address 0x%lx or size 0x%lx "
"is not in range.\n", dom->name, reg->base,
reg->order);
}
}
return 0;
}
上面代码根据Domain域中定义的内存区域进行PMP表项配置,即权限标志和地址范围,如果所有模式(M/S/U)都需要进行PMP权限检查,需要将PMP的L比特置位pmpcfg.L=1
。
SMEPMP
如果有SMEPMP机制,实现如下:
static int sbi_hart_smepmp_configure(struct sbi_scratch *scratch,
unsigned int pmp_count,
unsigned int pmp_log2gran,
unsigned long pmp_addr_max)
{
struct sbi_domain_memregion *reg;
struct sbi_domain *dom = sbi_domain_thishart_ptr();
unsigned int pmp_idx, pmp_flags;
/*
* Set the RLB so that, we can write to PMP entries without
* enforcement even if some entries are locked.
*/
csr_set(CSR_MSECCFG, MSECCFG_RLB);
/* Disable the reserved entry */
pmp_disable(SBI_SMEPMP_RESV_ENTRY);
/* Program M-only regions when MML is not set. */
pmp_idx = 0;
sbi_domain_for_each_memregion(dom, reg) {
/* Skip reserved entry */
if (pmp_idx == SBI_SMEPMP_RESV_ENTRY)
pmp_idx++;
if (pmp_count <= pmp_idx)
break;
/* Skip shared and SU-only regions */
if (!SBI_DOMAIN_MEMREGION_M_ONLY_ACCESS(reg->flags)) {
pmp_idx++;
continue;
}
pmp_flags = sbi_hart_get_smepmp_flags(scratch, dom, reg);
if (!pmp_flags)
return 0;
sbi_hart_smepmp_set(scratch, dom, reg, pmp_idx++, pmp_flags,
pmp_log2gran, pmp_addr_max);
}
/* Set the MML to enforce new encoding */
csr_set(CSR_MSECCFG, MSECCFG_MML);
/* Program shared and SU-only regions */
pmp_idx = 0;
sbi_domain_for_each_memregion(dom, reg) {
/* Skip reserved entry */
if (pmp_idx == SBI_SMEPMP_RESV_ENTRY)
pmp_idx++;
if (pmp_count <= pmp_idx)
break;
/* Skip M-only regions */
if (SBI_DOMAIN_MEMREGION_M_ONLY_ACCESS(reg->flags)) {
pmp_idx++;
continue;
}
pmp_flags = sbi_hart_get_smepmp_flags(scratch, dom, reg);
if (!pmp_flags)
return 0;
sbi_hart_smepmp_set(scratch, dom, reg, pmp_idx++, pmp_flags,
pmp_log2gran, pmp_addr_max);
}
/*
* All entries are programmed.
* Keep the RLB bit so that dynamic mappings can be done.
*/
return 0;
}
首先调用csr_set(CSR_MSECCFG, MSECCFG_RLB)
:配置规则锁定绕过,即使一些PMP表项被锁定,还是可以进行重配
然后调用pmp_disable(SBI_SMEPMP_RESV_ENTRY)
:由于SMEPMP机制使能,会导致M模式也无法访问S/U模式的内存,因此OpenSBI保留第一个PMP表项,并在启动时关闭PMP检查,实现如下:
int pmp_disable(unsigned int n)
{
int pmpcfg_csr, pmpcfg_shift;
unsigned long cfgmask, pmpcfg;
if (n >= PMP_COUNT)
return SBI_EINVAL;
#if __riscv_xlen == 32
pmpcfg_csr = CSR_PMPCFG0 + (n >> 2);
pmpcfg_shift = (n & 3) << 3;
#elif __riscv_xlen == 64
pmpcfg_csr = (CSR_PMPCFG0 + (n >> 2)) & ~1;
pmpcfg_shift = (n & 7) << 3;
#else
# error "Unexpected __riscv_xlen"
#endif
/* Clear the address matching bits to disable the pmp entry */
cfgmask = ~(0xffUL << pmpcfg_shift);
pmpcfg = (csr_read_num(pmpcfg_csr) & cfgmask);
csr_write_num(pmpcfg_csr, pmpcfg);
return SBI_OK;
}
当需要在M与S/U模式之间共享内存,首先调用sbi_hart_map_saddr
函数配置PMP表项地址区域为读写权限,由于sbi_hart_smepmp_configure
会开启msecfg.MML=1
,根据SMEPMP机制图,M模式此时具有读写权限(Enforced & Locked),S/U模式拒绝访问(Denied & Locked),这样之后M模式代码可以访问这块内存区域,函数实现如下:
int sbi_hart_map_saddr(unsigned long addr, unsigned long size)
{
/* shared R/W access for M and S/U mode */
unsigned int pmp_flags = (PMP_W | PMP_X);
unsigned long order, base = 0;
struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
/* If Smepmp is not supported no special mapping is required */
if (!sbi_hart_has_extension(scratch, SBI_HART_EXT_SMEPMP))
return SBI_OK;
if (is_pmp_entry_mapped(SBI_SMEPMP_RESV_ENTRY))
return SBI_ENOSPC;
for (order = MAX(sbi_hart_pmp_log2gran(scratch), log2roundup(size));
order <= __riscv_xlen; order++) {
if (order < __riscv_xlen) {
base = addr & ~((1UL << order) - 1UL);
if ((base <= addr) &&
(addr < (base + (1UL << order))) &&
(base <= (addr + size - 1UL)) &&
((addr + size - 1UL) < (base + (1UL << order))))
break;
} else {
return SBI_EFAIL;
}
}
pmp_set(SBI_SMEPMP_RESV_ENTRY, pmp_flags, base, order);
return SBI_OK;
}
当M模式完成对这块内存区域的读写操作后,需要调用sbi_hart_unmap_saddr
函数,进而调用pmp_disable
函数,将锁定pmpcfg.L=0
,根据SMEPMP机制图,M模式拒绝访问(Denied),S/U模式根据PMP配置权限进行检查(Enforced),这样就实现了在M与S/U模式之间共享内存,同时也保证M模式不能随意访问S/U模式的内存区域:
int sbi_hart_unmap_saddr(void)
{
struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
if (!sbi_hart_has_extension(scratch, SBI_HART_EXT_SMEPMP))
return SBI_OK;
return pmp_disable(SBI_SMEPMP_RESV_ENTRY);
}
然后当msecfg.MML=0
时,配置只有M模式可以访问区域的PMP表项,接着使能csr_set(CSR_MSECCFG, MSECCFG_MML)
,即msecfg.MML=1
,最后配置共享区域和只有S/U模式访问区域的PMP表项。
Qemu支持PMP
由于我们采用的qemu模拟器不支持SMEPMP(最新的版本支持SMEPMP),因此只会使用简单的PMP机制,PMP配置的内存区域,打印输出结果如下:
Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region01 : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region02 : 0x0000000080040000-0x000000008005ffff M: (R,W) S/U: ()
Domain0 Region03 : 0x0000000080000000-0x000000008003ffff M: (R,X) S/U: ()
Domain0 Region04 : 0x000000000c000000-0x000000000fffffff M: (I,R,W) S/U: (R,W)
Domain0 Region05 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address : 0x0000000080200000
Domain0 Next Arg1 : 0x0000000082200000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes
Domain0 SysSuspend : yes