From 2a70234342505ac9cda2b6d49ba141efd568ca0d Mon Sep 17 00:00:00 2001 From: Groot <1219671600@qq.com> Date: Wed, 14 Jun 2023 10:49:23 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E6=AC=A1=E6=8F=90=E4=BA=A4sb?= =?UTF-8?q?i=20introduction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20230612-introduction-to-riscv-sbi.md | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 articles/20230612-introduction-to-riscv-sbi.md diff --git a/articles/20230612-introduction-to-riscv-sbi.md b/articles/20230612-introduction-to-riscv-sbi.md new file mode 100644 index 0000000..e88e7f8 --- /dev/null +++ b/articles/20230612-introduction-to-riscv-sbi.md @@ -0,0 +1,218 @@ +> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.1 - [spaces tables]
+> Author: groot +> Date: 2023/06/12 +> Revisor: ABC +> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux) +> Proposal: [RISC-V Linux 内核 SBI 调用技术分析](https://gitee.com/tinylab/riscv-linux/issues/I64YC4) +> Sponsor: PLCT Lab, ISCAS + +# RISC-V SBI 概述 + +## RISCV-V SBI + +### 什么是 SBI? + +SBI 全称 Supervisor Binary Interface,是 RISC-V execution environment interface (EEI) 之一 [^1],目的是使处于 supervisor-sode (S-mode 或者 VS-mode) 的程序能够很方便的移植到实现不同扩展指令集的 RISC-V 架构的处理器上。提供 SBI 接口给监管模式软件的更高特权软件被称为 SBI 实现或 Supervisor Execution Environmen(监管执行环境 SEE)。 + +### 为什么要有 SBI + +![sbi](images/introduction-to-riscv-sbi/sbi2.svg) + +如果没有 SBI(如上图右侧),针对实现的扩展指令集不同的 RISC-V 微架构,可能要采用不同的方式才能够使操作系统内核触发 M-mode 的动作。而有了 SBI 之后,只要在扩展指令集不同的 RISC-V 微架构中实现统一的向上的 SBI 接口,上层的操作系统就可以不再关注具体的微架构细节,而是专注实现 SBI 接口提供的功能即可,大大提升了处于 supervisor-mode 的程序的可移植性。 +这其实就是计算机中的一个很重要的哲学——抽象。通过将底层的具体实现屏蔽,向上提供统一的接口,使上层应用不需关注过多底层细节,大大简化了程序的开发难度。 +通俗地,我们将 SBI 比作手机充电器接口。曾经市场上可能有非常多种类的充电器接口,如果 A 的手机接口和 B 的手机接口不一样,那么他们没办法互相使用对方的充电器,但是如果我们将充电器接口全部统一为 type-c 接口,这种尴尬地场景就不会再发生了,大大的方便了用户。 + +### SBI spec + +> TODO: +> +> - [ ] SBI Spec 在哪里下载,由谁定义,发布周期和评审节奏等。 +> - [ ] 介绍 SBI Spec 经历的几次大的版本发布,每个版本的主要变更。可以列表。 +> - [ ] 每一个版本支持的服务或函数列表单独列出来。可以在后续阅读代码的时候进行比对,看看实际代码是否进行了相应的支持。 + +### SBI 的作用 + +![sbi2](images/introduction-to-riscv-sbi/sbi2.svg) + +SBI 的第一个作用我们开头已经讲过了(图一左)。 +除此之外,如上图所示,SBI 也可能在 hypervisor-mode(HS-mode)下作为虚拟机管理程序实现。 +从更高一级的特权模式来看,SBI implementation 为 supervisor-mode 软件分配物理执行单元(HARTs)。 +因此,从 SBI implementation 的角度来看,S-mode 的 HART 被称为虚拟 HART(图一)。而如果实现是一个虚拟机管理程序(图二),那么虚拟 HART 则表示 VS-mode 的虚拟 HART。 + +### SBI Implementations + +理论上说,因为 SBI spec 是开源的,只要能够按照 spec 说明实现其功能就可以称为 BI Implementation。 +不过当前经过 RISC-V 官方认证的 Implementation 有如下几个 [^2]: + +| ImplementationID | Name | support SBI version | Update | +|------------------|----------------------------|---------------------|--------| +| 0 | Berkeley Boot Loader (BBL) | | | +| 1 | OpenSBI | | | +| 2 | Xvisor | | | +| 3 | KVM | | | +| 4 | RustSBI | | | +| 5 | Diosix | | | +| 6 | Coffer | | | + +### SBI 固件分析 + +> TODO + +## OpenSBI + +### 什么是 OpenSBI + +OpenSBI 是 SBI spec 的一个 RISC-V SBI C 语言参考实现。它由 Western Digital 公司发起,并且在 2019 年进行了开源。 + +### 编译 OpenSBI + +> 这里已经默认用户安装好 QEMU 和 U-Boot + +1. 下载 OpenSBI 源码 + +```sh +git clone https://github.com/riscv/opensbi.git +``` + +2. 进入 OpenSBI 文件夹 + +```sh +cd qemu-opensbi +``` + +3. 新建文件夹并进入 + +```sh +mkdir build +cd build +``` + +3. 编译 + +```sh +make -C $(pwd)/.. PLATFORM=generic CROSS_COMPILE=riscv64-Linux-gnu- FW_PAYLOAD_PATH=path/to/u-boot.bin +``` + +### 启动 OpenSBI + +1. 在 `qemu-opensbi` 文件夹中执行下面的命令 + +```sh +qemu-system-riscv64 -M virt -m 256 -nographic -bios build/platform/generic/firmware/fw_payload.el +``` + +2. 显示输出 + ![alt img](images/introduction-to-riscv-sbi/img.png) + 此时,OpenSBI 成功启动,并且引导进了 u-boot + +## Linux Kernel SBI 代码分析 + +下面分析 Linux 源码中的 SBI 代码: + +### ECALL 指令 + +`ECALL` 指令用于向执行环境发出请求,在不同的特权等级中执行 `ECALL` 指令有不同的效果:在 User Mode 中会引发 environment-call-from-U-mode 异常,在 Supervisor Mode 中会引发 environment-call-from-S-mode 异常,而在 Machine Mode 中会引发 environment-call-from-M-mode 异常 [^3]。 + +### Linux 内核 SBI 代码 + +`ECALL` 指令在 Linux 中用于系统调用,如下为 `arch/riscv/kernel/sbi.c` 中的部分代码。 +`sbi_ecall` 指令接受 8 个参数,分别是 + +- `ext`: SBI extension ID (EID) +- `fid`: SBI function ID (FID) +- `arg0-arg5`: SBI 函数调用参数 + +```c +struct sbiret sbi_ecall(int ext, int fid, unsigned long arg0, + unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, + unsigned long arg5) +{ + struct sbiret ret; + + register uintptr_t a0 asm ("a0") = (uintptr_t)(arg0); + register uintptr_t a1 asm ("a1") = (uintptr_t)(arg1); + register uintptr_t a2 asm ("a2") = (uintptr_t)(arg2); + register uintptr_t a3 asm ("a3") = (uintptr_t)(arg3); + register uintptr_t a4 asm ("a4") = (uintptr_t)(arg4); + register uintptr_t a5 asm ("a5") = (uintptr_t)(arg5); + register uintptr_t a6 asm ("a6") = (uintptr_t)(fid); + register uintptr_t a7 asm ("a7") = (uintptr_t)(ext); + asm volatile ("ecall" + : "+r" (a0), "+r" (a1) + : "r" (a2), "r" (a3), "r" (a4), "r" (a5), "r" (a6), "r" (a7) + : "memory"); + ret.error = a0; + ret.value = a1; + + return ret; +} +``` + +> 使用 `ECALL` 指令时,将异常类型写在 a7 寄存器,参数写在 a0-a5 寄存器,后面会根据异常类型的不同调用不同的异常处理函数 +> `register` 关键字表明后面的变量直接存储在寄存器中 +> `asm ("ax")` 表明将后面的变量与 `ax` 寄存器进行绑定 +> `asm volatile` 表明嵌入汇编代码进入 C 代码中,并且将 `a0` 和 `a1` 寄存器既作为输入寄存器又作为输出寄存器传给 `ECALL` 指令,而 `a2` - `a6` 寄存器作为输入寄存器传递给 `ECALL` +> `ECALL` 函数返回两个值 `a0` 和 `a1`,`sbi_ecall` 函数将这两个值作为错误和值返回给调用它的函数 + +比如实现一个 putchar 函数用于打印一个字符到系统控制台上,就如下通过调用 `sbi_ecall` 来实现: + +```c +void sbi_console_putchar(int ch) +{ + sbi_ecall(SBI_EXT_0_1_CONSOLE_PUTCHAR, 0, ch, 0, 0, 0, 0, 0); +} +``` + +然后我们进入 `arch/riscv/include/sbi.h`,观察宏定义: + +```c +enum sbi_ext_id { +#ifdef CONFIG_RISCV_SBI_V01 + SBI_EXT_0_1_SET_TIMER = 0x0, + SBI_EXT_0_1_CONSOLE_PUTCHAR = 0x1, + SBI_EXT_0_1_CONSOLE_GETCHAR = 0x2, + SBI_EXT_0_1_CLEAR_IPI = 0x3, + SBI_EXT_0_1_SEND_IPI = 0x4, + SBI_EXT_0_1_REMOTE_FENCE_I = 0x5, + SBI_EXT_0_1_REMOTE_SFENCE_VMA = 0x6, + SBI_EXT_0_1_REMOTE_SFENCE_VMA_ASID = 0x7, + SBI_EXT_0_1_SHUTDOWN = 0x8, +#endif + SBI_EXT_BASE = 0x10, + SBI_EXT_TIME = 0x54494D45, + SBI_EXT_IPI = 0x735049, + SBI_EXT_RFENCE = 0x52464E43, + SBI_EXT_HSM = 0x48534D, + SBI_EXT_SRST = 0x53525354, + SBI_EXT_PMU = 0x504D55, + + /* Experimentals extensions must lie within this range */ + SBI_EXT_EXPERIMENTAL_START = 0x08000000, + SBI_EXT_EXPERIMENTAL_END = 0x08FFFFFF, + + /* Vendor extensions must lie within this range */ + SBI_EXT_VENDOR_START = 0x09000000, + SBI_EXT_VENDOR_END = 0x09FFFFFF, +}; + +``` + +观察到 `SBI_EXT_0_1_CONSOLE_PUTCHAR` 定义为 `0x1`。 + +### Linux 如何兼容不同的 SBI 版本 + +> TODO + +## 小结 + +这篇文章介绍了 RISC-V 架构下的 SBI(Supervisor Binary Interface)概念、作用和实现方法,并提供了开源实现 OpenSBI 的编译和启动方法。SBI 的作用是使处于 supervisor-mode 的程序能够方便地移植到不同的 RISC-V 微架构处理器上,实现了底层统一接口的抽象,使开发者不需关注底层细节,大大简化了程序的开发难度。 +此外,文章还分析了在 Linux Kernel 中,SBI 的实现方法,包括对 `ECALL` 指令的处理、对 SBI 异常的处理、对 SBI 扩展功能的处理等,这些对于深入理解 SBI 在 Linux Kernel 中的作用和实现方法非常有帮助。本文简洁明了地讲述了 SBI 的概念,提高了读者对于 SBI 的理解,在某种程度上为 SBI 的应用提供了指导。 + +## 参考资料 + +[^1]: Volume I: RISC-V Unprivileged ISA V20191214-draft + +[^2]: RISC-V Supervisor Binary Interface Specification Version -v2.0-rc1, 2023-06-01: Draft + +[^3]: Volume II: RISC-V Privileged Architectures V1.12-draft -- Gitee