Illegal instruction mret - mret指令返回异常
lesson7
qemu-system-riscv64 --version QEMU emulator version 6.1.0 Copyright (c) 2003-2021 Fabrice Bellard and the QEMU Project developers
执行mret 抛异常 Illegal instruction
qemu-system-riscv64 -machine virt -bios none -kernel kernelimage -m 128M -smp 1 -nographic start 58. Exception : Illegal instruction. tval = 0x0000000000000000 mepc = 0x00000000800000f2
static void machine_switchto_supervisor(void) { // set M Previous Privilege mode to Supervisor, for mret. unsigned long x = mstatus_get(); x &= ~MSTATUS_MPP_MASK;x |= MSTATUS_MPP_S; mstatus_set(x); // set M Exception Program Counter to main, for mret. // requires gcc -mcmodel=medany mepc_set((unsigned long)main); // disable paging for now. satp_set(0); // delegate interrupts and exceptions to supervisor mode. medeleg_set(0xb109); mideleg_set(0x222); printf("%s %d.\r\n", __func__, __LINE__); // switch to supervisor mode and jump to main(). asm volatile("mret"); //抛异常 }
改成
static void machine_switchto_supervisor(void) { // set M Previous Privilege mode to Supervisor, for mret. unsigned long x = mstatus_get(); x &= ~MSTATUS_MPP_MASK; x |= MSTATUS_MPP_M; mstatus_set(x); // set M Exception Program Counter to main, for mret. // requires gcc -mcmodel=medany mepc_set((unsigned long)main); // disable paging for now. satp_set(0); // delegate interrupts and exceptions to supervisor mode. medeleg_set(0xb109); mideleg_set(0x222); printf("%s %d.\r\n", __func__, __LINE__); // switch to supervisor mode and jump to main(). asm volatile("mret"); }
The issue turned out to be RISC-V's Physical Memory Protection (PMP). QEMU will raise an illegal instruction exception when executing an MRET
instruction if no PMP rules have been defined. Adding a PMP entry resolved the issue.
在硬件设计里,PMP (Phsical Memory Protection) 是可选项,但在大部分地方我们都可以见到它的身影。PMP 检查一般用于 hart 在监管者模式或用户模式下的所有访问;或者在 mstatus.MPRV = 1
时的 load 和 store 等情况。一旦触发 PMP 保护,RISC-V 要求产生精确中断并处理。
PMP 允许机器模式指定用户模式下可以访问的内存地址。PMP entry 由一个 8-bit 的 PMP 配置寄存器和一个 32/64 位长的 PMP 地址寄存器组成。整个 PMP 包括若干个(通常为 8 到 16 组)PMP entry 。配置寄存器可以配置读、写和执行权限,地址寄存器用来划定界限。
下两图显示了 PMP 地址寄存器和配置寄存器的布局。pmpxxcfg 表示了 PMP 配置寄存器,pmpxxaddr 表示了 PMP 地址寄存器。
当处于用户模式的处理器尝试 load 或 store 操作时,将地址和所有的 PMP 地址寄存器比较。如果地 址大于等于 PMP 地址 i,但小于 PMP 地址 i+1,则 PMP i+1 的配置寄存器决定该访问是否可以继续,如果不能将会引发访问异常。
R,W,X 位分别指示了 PMP 入口允许读、写、执行权限。A 域解释了 PMP 寄存器的编码情况。
void setup_pmp(void) { // Set up a PMP to permit access to all of memory. // Ignore the illegal-instruction trap if PMPs aren't supported. uintptr_t pmpc = PMP_NAPOT | PMP_R | PMP_W | PMP_X; asm volatile ("la t0, 1f\n\t" "csrrw t0, mtvec, t0\n\t" "csrw pmpaddr0, %1\n\t" "csrw pmpcfg0, %0\n\t" ".align 2\n\t" "1: csrw mtvec, t0" : : "r" (pmpc), "r" (-1UL) : "t0"); }
设置setup_pmp之后,异常解除
void setup_pmp(void) { // Set up a PMP to permit access to all of memory. // Ignore the illegal-instruction trap if PMPs aren't supported. unsigned long pmpc = PMP_NAPOT | PMP_R | PMP_W | PMP_X; asm volatile ("la t0, 1f\n\t" "csrrw t0, mtvec, t0\n\t" "csrw pmpaddr0, %1\n\t" "csrw pmpcfg0, %0\n\t" ".align 2\n\t" "1: csrw mtvec, t0" : : "r" (pmpc), "r" (-1UL) : "t0"); } static void machine_switchto_supervisor(void) { setup_pmp(); // set M Previous Privilege mode to Supervisor, for mret. unsigned long x = mstatus_get(); x &= ~MSTATUS_MPP_MASK; //x |= MSTATUS_MPP_M; x |= MSTATUS_MPP_S; mstatus_set(x); // set M Exception Program Counter to main, for mret. // requires gcc -mcmodel=medany mepc_set((unsigned long)main); // disable paging for now. satp_set(0); // delegate interrupts and exceptions to supervisor mode. medeleg_set(0xb109); mideleg_set(0x222); printf("%s %d.\r\n", __func__, __LINE__); // switch to supervisor mode and jump to main(). asm volatile("mret"); }
qemu-system-riscv64 -machine virt -bios none -kernel kernelimage -m 128M -smp 1 -nographic start 74. machine_switchto_supervisor 67. main 28.