1. 设置trap入口
1.1 设置M模式trap入口
RISCV架构发生中断和异常时不会像arm那样自动切换栈指针,所以在trap入口需要先设置好栈地址,我们将__stack_start加上第九个页面地址作为M模式的trap栈地址,如下所示。
# M模式中断和异常入口. .globl machine_trap_entry .align 4 machine_trap_entry: # 设置M模式异常处理时栈地址 la sp, __stack_start li a0, 4096 li a1, 9 mul a0, a0, a1 add sp, sp, a0 # 调用C入口 call machine_trap mret
栈设置完成后直接跳转到C代码执行,C代码处理目前很简单,就是将中断或者异常原因打印出来,如下所示。
void machine_trap(void) { unsigned long cause = csr_read(mcause); unsigned long mepc = csr_read(mepc); unsigned long tval = csr_read(mtval); int is_int = (cause & (1l << 63l)) ? 1 : 0; int mcode = cause & 0xff; if (mcode >= 16) { pr_err("mtrap %s : %s.\n", is_int ? "Interrupt" : "Exception", "Unknown code"); return; } if (is_int) { pr_debug("mtrap Interrupt : %s.\n", interrupt_cause[mcode]); } else { pr_debug("mtrap Exception : %s.\n", exception_cause[mcode]); switch (mcode) { case ILLEGAL_INSTRUCTION: pr_debug("mtval = %p\n", tval); pr_debug("mepc = %p\n", mepc); break; } csr_write(mepc, mepc + 4); } return; }
最后需要的就是将trap入口地址设置到相应的系统寄存器中,并使能相应的中断,如下所示:
static void machine_trap_init(void) { unsigned long status = csr_read(mstatus); // 设置M模式中断和异常入口. csr_write(mtvec, (unsigned long)machine_trap_entry); // 使能M模式全局中断 status |= MSTATUS_MIE; csr_write(mstatus, status); // 使能M模式和S模式外部中断 csr_write(mie, MIE_MEIE | MIE_SEIE); }
由于我们不使用软件中断和定时器中断,所以只使能外部中断也就是中断控制器的中断。
1.2 设置S模式trap入口
S模式的处理基本和M模式类似,只是S模式的tarp使用的是第十个栈页面地址,如下所示:
# S模式中断和异常入口. .globl supervisor_trap_entry .align 4 supervisor_trap_entry: # 设置S模式异常处理时栈地址 la sp, __stack_start li a0, 4096 li a1, 10 mul a0, a0, a1 add sp, sp, a0 # 调用C入口 call supervisor_trap sret
2. 模式切换
由于M模式不使用MMU,MMU是在S模式下使用的,所以我们需要将处理器从M模式切换到S模式。基本原理就是我们假装是从S模式切换到M模式的,所以需要构造好相应的环境,具体的就是设置好mstatus中的MPP位为S模式,设置好mepc返回地址为S模式入口函数地址,最后通过mret指令切换到S模式,如下所示:
static void machine_switchto_supervisor(void) { // 设置MPP为S模式,这样在执行mret之后处理器就切换为S模式. unsigned long status = csr_read(mstatus); status &= ~MSTATUS_MPP_MASK; status |= MSTATUS_MPP_S; csr_write(mstatus, status); // 设置异常返回地址,mret执行后跳转到此地址执行 // 需要 gcc -mcmodel=medany 参数 csr_write(mepc, (unsigned long)sinit); // 关闭MMU csr_write(satp, 0); // 托管异常和中断到S模式处理 csr_write(medeleg, 0xb1ff); csr_write(mideleg, 0x222); // 配置物理内存保护,以让S模式访问所有物理内存 csr_write(pmpaddr0, 0x3fffffffffffffull); csr_write(pmpcfg0, 0xf); // 切换到S模式. asm volatile("mret"); }
这其中还设置了中断和异常托管,我们将所有中断托管到S模式,将大部分异常也都托管到S模式处理,只有S模式环境调用异常和M模式环境调用异常这两个异常还是给M模式处理。
3. 测试
我们通过ecall环境调用指令来测试M模式和S模式的trap处理是否正常。首先对ecall指令做出如下封装:
#define RISCV_ECALL(which, arg0, arg1, arg2) ({ \ register unsigned long a0 asm ("a0") = (unsigned long)(arg0); \ register unsigned long a1 asm ("a1") = (unsigned long)(arg1); \ register unsigned long a2 asm ("a2") = (unsigned long)(arg2); \ register unsigned long a7 asm ("a7") = (unsigned long)(which); \ asm volatile ("ecall" \ : "+r" (a0) \ : "r" (a1), "r" (a2), "r" (a7) \ : "memory"); \ a0; \ }) #define RISCV_ECALL_0(which) RISCV_ECALL(which, 0, 0, 0)
然后在S模式时执行就会进入M模式trap处理,如果有M模式的打印就表示是正常的:
static void sinit(void) { pr_debug("M switch to S success.\n"); supervisor_trap_init(); #ifdef KERNEL_TEST RISCV_ECALL_0(0); #endif while(1); }
测试S模式trap需要修改代码,将S模式的环境调用异常也托管给S模式处理,然后再次运行就会看到S模式的trap打印:
评论