SimpleOS开发(4)切换到S模式

gewenbin
gewenbin
gewenbin
188
文章
15
评论
2022年5月26日22:06:41 评论 598

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);
}

SimpleOS开发(4)切换到S模式

测试S模式trap需要修改代码,将S模式的环境调用异常也托管给S模式处理,然后再次运行就会看到S模式的trap打印:

SimpleOS开发(4)切换到S模式

gewenbin
  • 本文由 发表于 2022年5月26日22:06:41
  • 转载请务必保留本文链接:http://www.databusworld.cn/10767.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: