跳转至

关于 SBI

在 RISC-V 指令集下,uCore 运行在 S 态,下面的 M 态中还有一个软件为它提供执行环境,二者通过名为 SBI 的接口进行交互。目前社区中 SBI 的主流实现是 OpenSBI,最新的 QEMU 模拟器中也内置了 OpenSBI 固件并作为默认使用。

uCore 下层的 M 态也使用 OpenSBI。但是,OpenSBI 的实现比较复杂,并且使用了大量 CPU 和板级特性,导致很难在自己编写的 CPU 上运行。为此,我们提供了一个极小的 SBI 实现 —— rbl(仓库地址),它可以直接替换 OpenSBI 在 QEMU 上运行。(rbl 使用 Rust 语言实现,最早由 @jiegec 编写,@wangrunji0408 在此基础上进行了魔改。)

编译 rbl

方便起见,我们已经把预编译好的 rbl 可执行文件放在了 uCore 仓库中,所以一般不需要手动编译。

只有在你的 CPU 在 M 态发生了一些状况,需要修改 M 态程序代码或者反汇编 rbl 寻找错误原因时才可能需要自行编译 rbl。

首先安装 Rust 工具链:https://rustup.rs

从以下仓库中拉取代码:https://github.com/wangrunji0408/rbl

$ cd rbl
$ rustup target add riscv32i-unknown-none-elf
$ cargo build --release

结果位于 target/riscv32i-unknown-none-elf/release/rbl

rbl 的功能

boot.S

这里是 rbl 的启动入口,可以看到这里初始化了 a0a1 以外的通用寄存器。

同时设置 mtvec 到 rbl 的异常处理入口。

在进入 boot.S 之前 a0 会被设置为 mhartid, a1 会被设置为 dtb 也就是设备树的地址。

在 try 部分,将会初始化栈指针到 bootstacktop,同时将其存入到 mscratch 当中。

然后若为 0 号核心,则进入启动初始化的流程 boot_first_hart,否则将会执行 wfi 指令,一直等待中断到来(死循环)。

因此,若 mhartid 配置错误,则可能导致 CPU 卡死,因此请将 mhartid 设置为 0,并保证其不会被修改。

main.rs

boot_first_hart 函数在这里实现:

  • 初始化操作系统的入口地址为 0x80400000
  • 配置 midelegmedeleg, 具体转发的中断和异常见注释。
  • 配置 MIE 开启 M 态和 S 态的时钟中断。
  • 配置 mepc 为入口地址
  • mret 进入操作系统

panic 函数为 rbl 发生不可处理的异常之后来到的地方,可以看到这个函数会输出错误信息,同时进入死循环。如果 rbl 运行到这里,说明实现有误,可以参考报错进行调试,也可以修改这个函数以提供更多信息。

trap.S

这里为中断异常的处理入口

注意到 M 态完全由 rbl 控制,而在初始化 sp 为栈地址之前 (运行 boot.S 的代码时) 不会发生中断或异常。因此在遇到 sp 为 0 且进入 M 态异常处理入口时,直接执行 WFI 指令卡住 CPU。

否则,这里将会将寄存器存到 rbl 的栈上,并将栈指针作为参数,调用真正的 trap_handler 函数。

当异常处理完成之后,函数会返回异常的返回地址。因此将其置于 mepc 中,恢复通用寄存器和栈指针,mret 返回即可。

trap.rs

这里有 trap_handler 函数的实现。

trap_handler 会处理如下的异常和中断:

  1. S 态 ECALL:

    • 提供操作串口的接口
    • 提供设置 MTIMECMP CSR 寄存器的接口
    • 剩下的功能均没有实现
  2. M 态 时钟中断

    • 清除 MTIP 并设置 STIP
  3. Invalid Instruction 异常

    • 处理 RDTIMERDTIMEH 指令未实现的情况

其余情况则会 panic。

clint.rs

这里提供了获取 mtime, 修改 mtimecmp, 设置和清除软件中断的接口(没有用到)。

serial.rs

这里提供了 UART 16550 串口的访问接口,实现了输入输出函数和输出宏。因此大家可以在 rbl 中调用 println! 宏打印调试输出。


最后更新: 2022年11月26日
作者:Jiajie Chen (1.1%), cuibst (98.9%)