关于 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 的启动入口,可以看到这里初始化了 a0
和 a1
以外的通用寄存器。
同时设置 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
- 配置
mideleg
和medeleg
, 具体转发的中断和异常见注释。 - 配置
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
会处理如下的异常和中断:
-
S 态 ECALL:
- 提供操作串口的接口
- 提供设置
MTIMECMP
CSR 寄存器的接口 - 剩下的功能均没有实现
-
M 态 时钟中断
- 清除
MTIP
并设置STIP
- 清除
-
Invalid Instruction 异常
- 处理
RDTIME
和RDTIMEH
指令未实现的情况
- 处理
其余情况则会 panic。
clint.rs
这里提供了获取 mtime
, 修改 mtimecmp
, 设置和清除软件中断的接口(没有用到)。
serial.rs
这里提供了 UART 16550 串口的访问接口,实现了输入输出函数和输出宏。因此大家可以在 rbl 中调用 println! 宏打印调试输出。