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(如上图右侧),针对实现的扩展指令集不同的 RISC-V 微架构,可能要采用不同的方式才能够使操作系统内核触发 M-mode 的动作。而有了 SBI 之后,只要在扩展指令集不同的 RISC-V 微架构中实现统一的向上的 SBI 接口,上层的操作系统就可以不再关注具体的微架构细节,而是专注实现 SBI 接口提供的功能即可,大大提升了处于 supervisor-mode 的程序的可移植性。
+这其实就是计算机中的一个很重要的哲学——抽象。通过将底层的具体实现屏蔽,向上提供统一的接口,使上层应用不需关注过多底层细节,大大简化了程序的开发难度。
+通俗地,我们将 SBI 比作手机充电器接口。曾经市场上可能有非常多种类的充电器接口,如果 A 的手机接口和 B 的手机接口不一样,那么他们没办法互相使用对方的充电器,但是如果我们将充电器接口全部统一为 type-c 接口,这种尴尬地场景就不会再发生了,大大的方便了用户。
+
+### SBI spec
+
+> TODO:
+>
+> - [ ] SBI Spec 在哪里下载,由谁定义,发布周期和评审节奏等。
+> - [ ] 介绍 SBI Spec 经历的几次大的版本发布,每个版本的主要变更。可以列表。
+> - [ ] 每一个版本支持的服务或函数列表单独列出来。可以在后续阅读代码的时候进行比对,看看实际代码是否进行了相应的支持。
+
+### SBI 的作用
+
+
+
+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. 显示输出
+ 
+ 此时,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