diff --git a/articles/20230728-sbi-firemware-analyze-1.md b/articles/20230728-sbi-firemware-analyze-1.md new file mode 100644 index 0000000000000000000000000000000000000000..6420950adba7c19dd6942adc000b18a64926b865 --- /dev/null +++ b/articles/20230728-sbi-firemware-analyze-1.md @@ -0,0 +1,1105 @@ +> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc1 - [spaces tables pangu]
+> Author: groot
+> Date: 2023/07/28
+> Revisor: Falcon [falcon@tinylab.org](mailto:falcon@tinylab.org)
+> 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 + +# OpenSBI 固件代码分析(一) + +## 前言 + +之前的文章中给大家介绍了一下 SBI 和 OpenSBI,不过并没有特别深入的分析 OpenSBI 的整个流程。接下来为大家,我将带领读者从源码开始剖析,分析 OpenSBI 的启动流程,逐渐深入的学习 OpenSBI。 + +该篇文章首先带领读者阅读 OpenSBI 的固件源码部分,我们将从 `firmware/fw_base.S` 开始,探索 OpenSBI 的世界。 + +## 三种固件类型 + +该内容链接到 [《QEMU 启动方式分析(4): OpenSBI 固件分析与 SBI 规范的 HSM 扩展》][003] 的第四部分,这里不做过多的赘述。 + +简而言之就是下面的内容: + +* FW_PAYLOAD 类型固件打包了二进制文件和固件,适用于无法同时加载 OpenSBI 和 Runtime 的下一引导阶段。 +* FW_JUMP 类型固件能够跳转到下一引导阶段的入口,需要在编译时指定下一引导阶段要加载的地址。 +* FW_DYNAMIC 类型固件可以从上一引导阶段获得 Runtime 的下一个入口地址,无需在编译时指定。使用 struct fw_dynamic_info 结构体提供信息。 + +如果想指定某个启动方式,可以在编译的时候使用 `PLATFORM` 参数进行指定。 + +```bash +make PLATFORM= +``` + +如果使用 `FW_PAYLOAD` 方式,可以在编译的时候使用 `FW_PAYLOAD_PATH` 进行指定。 + +```shell +make PLATFORM= FW_PAYLOAD_PATH= +``` + +同时,选择 `FW_OPTIONS` 来控制 `OpenSBI` 在运行时是否输出,具体的 `options` 支持可以在文件 `include/sbi/sbi_scratch.h` 代码 `enum sbi_scratch_options` 中查看。 + +```bash +make PLATFORM= FW_OPTIONS= +``` + +## RISC-V 汇编预知识 + +### 通用寄存器 + +risc-v 有 32 个通用寄存器(简写 reg),标号为 `x0` - `x31` + +| 寄存器 | 编程接口名称(ABI) | 描述 | 使用 | +|--------|-------------------|---------------------------------|------------------------------------| +| x0 | zero | Hard-wired zero | 硬件零 | +| x1 | ra | Return address | 常用于保存(函数的)返回地址 | +| x2 | sp | Stack pointer | 栈顶指针 | +| x3 | gp | Global pointer | — | +| x4 | tp | Thread pointer | — | +| x5-7 | t0-2 | Temporary | 临时寄存器 | +| x8 | s0/fp | Saved Register/ Frame pointer | (函数调用时)保存的寄存器和栈顶指针 | +| x9 | s1 | Saved register | (函数调用时)保存的寄存器 | +| x10-11 | a0-1 | Function argument/ return value | (函数调用时)的参数/函数的返回值 | +| x12-17 | a2-7 | Function argument | (函数调用时)的参数 | +| x18-27 | s2-11 | Saved register | (函数调用时)保存的寄存器 | +| x28-31 | t3-6 | Temporary | 临时寄存器 | + +### 指令格式 + +RISC-V 指令具有六种基本指令格式: + +* R 类型指令:用于寄存器 - 寄存器操作; +* I 类型指令:用于短立即数和访存 load 操作; +* S 类型指令:用于访存 store 操作; +* B 类型指令:用于条件跳转操作; +* U 类型指令:用于长立即数操作; +* J 类型指令:用于无条件操作; +![RISC-V 指令格式](images/sbi-firmware/instruction-spec.png) + +### 常见指令 + +这里不再花费篇幅给大家介绍常见指令,大家可以通过互联网学习相关内容。 + +对于初学者,我推荐可以先从 [《rvbook》][001] 开始学起。 + +### 汇编指示符 + +RISC-V 的汇编指示符和作用如下 + +| 指示符 | 作用 | +|:----------------|:----------------------------------------------------------------------------------| +| .text | 代码段,之后跟的符号都在.text 内 | +| .data | 数据段,之后跟的符号都在.data 内 | +| .bss | 未初始化数据段,之后跟的符号都在.bss 中 | +| .section .foo | 自定义段,之后跟的符号都在.foo 段中,.foo 段名可以做修改 | +| .align n | 按 2 的 n 次幂字节对齐 | +| .balign n | 按 n 字节对齐 | +| .globl sym | 声明 sym 未全局符号,其它文件可以访问 | +| .string “str” | 将字符串 str 放入内存 | +| .byte b1,…,bn | 在内存中连续存储 n 个单字节 | +| .half w1,…,wn | 在内存中连续存储 n 个半字(2 字节) | +| .word w1,…,wn | 在内存中连续存储 n 个字(4 字节) | +| .dword w1,…,wn | 在内存中连续存储 n 个双字(8 字节) | +| .float f1,…,fn | 在内存中连续存储 n 个单精度浮点数 | +| .double d1,…,dn | 在内存中连续存储 n 个双精度浮点数 | +| .option rvc | 使用压缩指令 (risc-v c) | +| .option norvc | 不压缩指令 | +| .option relax | 允许链接器松弛(linker relaxation,链接时多次扫描代码,尽可能将跳转两条指令替换为一条) | +| .option norelax | 不允许链接松弛 | +| .option pic | 与位置无关代码段 | +| .option nopic | 与位置有关代码段 | +| .option push | 将所有.option 设置存入栈 | +| .option pop | 从栈中弹出上次存入的.option 设置 | + +## fw_base + +该文件中的代码是整个 OpenSBI 的起点,我们理解 OpenSBI 的代码就从这里开始吧! + +首先给大家介绍文件中的一部分代码,将这些代码的作用从宏观上给大家介绍一下,然后再深入代码,将注释打在代码中,方便大家理解。 + +### 代码意义 + +该段代码首先执行启动函数,在这个过程中通过之前规定的启动方式(fw_payload, fw_jump, fw_dynamic)找到一个启动核。如果规定了 `FW_PIC = y`,意味着将生成位置无关的固件映像,代码将进行一些重定位工作。这一部分读者暂且先不用关心。 + +该文件 RISC-V 处理器的引导程序,用于启动操作系统或其他固件。 + +代码以汇编语言和宏定义为主,用于初始化处理器,设置运行时环境,并将控制权转移到操作系统或其他固件。下面将解释代码的主要部分: + +- 起始部分:该部分定义了代码的许可证、版权信息和作者。 + +- 宏定义:在这部分,定义了一些宏指令,用于简化代码中的重复操作,例如 MOV_3R 和 MOV_5R 宏用于将寄存器中的值复制到另一个寄存器。 + +- _start:这是代码的主要入口点。它开始执行时会查找首选的启动核心(preferred boot HART id)。然后,它调用 fw_boot_hart 函数,该函数会尝试选择要启动的核心,如果未指定,它将选择一个核心来执行。启动核心将进行一系列初始化操作,然后根据指定的启动参数跳转到适当的代码段。 + +- _try_lottery:这段代码在多核情况下使用。在多个核同时启动时,只有最先执行原子加指令的核心会获得"lottery",其他核心将等待启动核心完成初始化。 + +- _relocate:如果启动核心的加载地址和链接地址不同,将进行重定位。它将把代码从加载地址复制到链接地址,以便在链接地址上运行。这部分代码是用于位置无关执行(FW_PIC=y)的情况,使 OpenSBI 能够在不同的地址上正确运行。 + +- _relocate_done:在重定位完成后,将在这里标记重定位完成。这对于其他核心等待重定位完成非常重要。 + +- _scratch_init:在这里设置了每个 HART(核心)的 scratch 空间,该空间用于保存处理器的运行时信息。 + +- _start_warm:在这里进行非启动核心的初始化,等待启动核心完成初始化。非启动核心会等待启动核心在主要初始化工作完成后,再进行自己的初始化。 + +- _hartid_to_scratch:用于将 HART ID 映射到 scratch 空间的函数。 + +- 其他一些功能:还包括处理中断、初始化 FDT(Flattened Device Tree)等功能。 + +这段代码是一个用于 RISC-V 平台的引导程序,用于初始化处理器和运行时环境,并最终将控制权转移到操作系统或其他固件。它支持多核处理器,并确保每个核心都能正确初始化和运行。 + +### 代码解析 + +```asm +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019 Western Digital Corporation or its affiliates. + * + * Authors: + * Anup Patel + */ + +#include +#include +#include +#include +#include +#include + +#define BOOT_STATUS_RELOCATE_DONE 1 +#define BOOT_STATUS_BOOT_HART_DONE 2 + +.macro MOV_3R __d0, __s0, __d1, __s1, __d2, __s2 + add \__d0, \__s0, zero + add \__d1, \__s1, zero + add \__d2, \__s2, zero +.endm + +.macro MOV_5R __d0, __s0, __d1, __s1, __d2, __s2, __d3, __s3, __d4, __s4 + add \__d0, \__s0, zero + add \__d1, \__s1, zero + add \__d2, \__s2, zero + add \__d3, \__s3, zero + add \__d4, \__s4, zero +.endm + +/* + * If __start_reg <= __check_reg and __check_reg < __end_reg then + * jump to __pass + */ +.macro BRANGE __start_reg, __end_reg, __check_reg, __jump_lable + blt \__check_reg, \__start_reg, 999f + bge \__check_reg, \__end_reg, 999f + j \__jump_lable +999: +.endm + + .section .entry, "ax", %progbits + .align 3 + .globl _start + .globl _start_warm + +// 开始 +_start: + /* Find preferred boot HART id */ +# 伪代码,见第 26 行 + MOV_3R s0, a0, s1, a1, s2, a2 +# 调用 fw_boot_hart 函数 +# fw_payload 和 fw_jump 返回 -1 +# fw_danymic 返回指定数字,设置为指定核心启动 + call fw_boot_hart + add a6, a0, zero +# 伪代码,见第 20 行 + MOV_3R a0, s0, a1, s1, a2, s2 +# 下面的 66 - 70 行代码以多核的角度看 +# 每个核都要执行这些所有代码,没有符合条件的核心进入 _wait_relocate_copy_done 等待 +# 判断 a6 是否是 -1 +# 如果是 -1,调用 _try_lottery 函数随机产生一个启动核心 + li a7, -1 + beq a6, a7, _try_lottery + /* Jump to relocation wait loop if we are not boot hart */ +# 如果不是 -1 意味着指定了启动核心, + bne a0, a6, _wait_relocate_copy_done + +# ,前面的 _try_lottery 只能有一个核心获得 lottery,其他没有获取的 +_try_lottery: + /* Jump to relocation wait loop if we don't get relocation lottery */ + lla a6, _relocate_lottery + li a7, 1 +# 原子操作 +# a6 指向的地址上的值(_relocate_lottery 的地址)做原子加 1,_relocate_lottery 的老值写入 a6。 + amoadd.w a6, a7, (a6) +# _relocate_lottery 不等于 0,就跳到 boot hart 做完重定位的地方。 +# 如果多个核一起启动执行,只有最先执行上面原子加指令的核的 a6(_relocate_lottery 初始值)是 0, +# 所以,后续执行到这里的核都是从核,直接跳到重定位完成的地址。 + bnez a6, _wait_relocate_copy_done + + /* Save load address */ + lla t0, _load_start + lla t1, _fw_start + REG_S t1, 0(t0) +# "FW_PIC=y" 生成位置无关的可执行固件映像。 +# OpenSBI 可以在任意地址上以适当的对齐方式运行。 +# 因此,原始的重定位机制("FW_PIC=n")将被跳过。 +# 换句话说,OpenSBI 将直接在加载地址上运行,而不需要进行任何代码移动。 +# 这个选项需要支持 PIE(位置无关执行)的工具链,并且默认情况下开启。 +# 下面的代码一直到 #endif 都不需要特别关注 +#ifdef FW_PIC + /* relocate the global table content */ + lla t0, _link_start + REG_L t0, 0(t0) + /* t1 shall has the address of _fw_start */ + sub t2, t1, t0 + lla t3, _runtime_offset + REG_S t2, (t3) + lla t0, __rel_dyn_start + lla t1, __rel_dyn_end + beq t0, t1, _relocate_done +2: + REG_L t5, REGBYTES(t0) /* t5 <-- relocation info:type */ + li t3, R_RISCV_RELATIVE /* reloc type R_RISCV_RELATIVE */ + bne t5, t3, 3f + REG_L t3, 0(t0) + REG_L t5, (REGBYTES * 2)(t0) /* t5 <-- addend */ + add t5, t5, t2 + add t3, t3, t2 + REG_S t5, 0(t3) /* store runtime address to the GOT entry */ + j 5f + +3: + lla t4, __dyn_sym_start + +4: + srli t6, t5, SYM_INDEX /* t6 <--- sym table index */ + andi t5, t5, 0xFF /* t5 <--- relocation type */ + li t3, RELOC_TYPE + bne t5, t3, 5f + + /* address R_RISCV_64 or R_RISCV_32 cases */ + REG_L t3, 0(t0) + li t5, SYM_SIZE + mul t6, t6, t5 + add s5, t4, t6 + REG_L t6, (REGBYTES * 2)(t0) /* t0 <-- addend */ + REG_L t5, REGBYTES(s5) + add t5, t5, t6 + add t5, t5, t2 /* t5 <-- location to fix up in RAM */ + add t3, t3, t2 /* t3 <-- location to fix up in RAM */ + REG_S t5, 0(t3) /* store runtime address to the variable */ + +5: + addi t0, t0, (REGBYTES * 3) + blt t0, t1, 2b + j _relocate_done +_wait_relocate_copy_done: + j _wait_for_boot_hart +#else + /* Relocate if load address != link address */ +_relocate: + lla t0, _link_start + REG_L t0, 0(t0) + lla t1, _link_end + REG_L t1, 0(t1) + lla t2, _load_start + REG_L t2, 0(t2) + beq t0, t2, _relocate_done + sub t3, t1, t0 + add t3, t3, t2 + lla t4, _relocate_done + sub t4, t4, t2 + add t4, t4, t0 + blt t2, t0, _relocate_copy_to_upper +_relocate_copy_to_lower: + ble t1, t2, _relocate_copy_to_lower_loop + lla t3, _relocate_lottery + BRANGE t2, t1, t3, _start_hang + lla t3, _boot_status + BRANGE t2, t1, t3, _start_hang + lla t3, _relocate + lla t5, _relocate_done + BRANGE t2, t1, t3, _start_hang + BRANGE t2, t1, t5, _start_hang + BRANGE t3, t5, t2, _start_hang +_relocate_copy_to_lower_loop: + REG_L t3, 0(t2) + REG_S t3, 0(t0) + add t0, t0, __SIZEOF_POINTER__ + add t2, t2, __SIZEOF_POINTER__ + blt t0, t1, _relocate_copy_to_lower_loop + jr t4 +_relocate_copy_to_upper: + ble t3, t0, _relocate_copy_to_upper_loop + lla t2, _relocate_lottery + BRANGE t0, t3, t2, _start_hang + lla t2, _boot_status + BRANGE t0, t3, t2, _start_hang + lla t2, _relocate + lla t5, _relocate_done + BRANGE t0, t3, t2, _start_hang + BRANGE t0, t3, t5, _start_hang + BRANGE t2, t5, t0, _start_hang +_relocate_copy_to_upper_loop: + add t3, t3, -__SIZEOF_POINTER__ + add t1, t1, -__SIZEOF_POINTER__ + REG_L t2, 0(t3) + REG_S t2, 0(t1) + blt t0, t1, _relocate_copy_to_upper_loop + jr t4 +_wait_relocate_copy_done: + lla t0, _fw_start + lla t1, _link_start + REG_L t1, 0(t1) + beq t0, t1, _wait_for_boot_hart + lla t2, _boot_status + lla t3, _wait_for_boot_hart + sub t3, t3, t0 + add t3, t3, t1 +1: + /* waitting for relocate copy done (_boot_status == 1) */ + li t4, BOOT_STATUS_RELOCATE_DONE + REG_L t5, 0(t2) + /* Reduce the bus traffic so that boot hart may proceed faster */ + nop + nop + nop + bgt t4, t5, 1b + jr t3 +#endif +# 重定位结束 +_relocate_done: + + /* + * Mark relocate copy done + * Use _boot_status copy relative to the load address + */ +# 加载 _boot_status 的地址到 t0 + lla t0, _boot_status +#ifndef FW_PIC + lla t1, _link_start + REG_L t1, 0(t1) + lla t2, _load_start + REG_L t2, 0(t2) + sub t0, t0, t1 + add t0, t0, t2 +#endif +# 改变 t0 为 BOOT_STATUS_RELOCATE_DONE,这个是个宏,被定义为 1 + li t1, BOOT_STATUS_RELOCATE_DONE + REG_S t1, 0(t0) +# 确保以上的访存操作已经做完 + fence rw, rw + + /* At this point we are running from link address */ + + /* Reset all registers for boot HART */ + li ra, 0 +# 将所有寄存器清零 + call _reset_regs + +# 将 _bss_start 和 _bss_end 分别加载到 s4 和 s5 + /* Zero-out BSS */ + lla s4, _bss_start + lla s5, _bss_end +# 循环将所有 bss 段内的内容清零 +_bss_zero: +# 向 s4 寄存器所指的内存中写 0,也就是清零 s4 寄存器所指内存的值 + REG_S zero, (s4) +# s4 所指的地址 +4,指向下一个地址 + add s4, s4, __SIZEOF_POINTER__ +# 如果 s4 的值小于 s5,也就是还没到 _bss_end ,跳至 _bss_zero + blt s4, s5, _bss_zero + +# 设置一些临时使用的中断 + /* Setup temporary trap handler */ + lla s4, _start_hang + csrw CSR_MTVEC, s4 +# 设置一些临时使用的中断栈 + /* Setup temporary stack */ + lla s4, _fw_end + li s5, (SBI_SCRATCH_SIZE * 2) + add sp, s4, s5 + +# 保存一些信息,不同的启动类型有不同的操作 + /* Allow main firmware to save info */ + MOV_5R s0, a0, s1, a1, s2, a2, s3, a3, s4, a4 + call fw_save_info + MOV_5R a0, s0, a1, s1, a2, s2, a3, s3, a4, s4 + +# 如果定义了设备树的地址,将它加载进来 +#ifdef FW_FDT_PATH + /* Override previous arg1 */ + lla a1, fw_fdt_bin +#endif + +# 针对特定平台,进行初始化 +# 如果没有在 fw_platform_init 函数中对 FDT 进行修改 +# 返回值不变 + /* + * Initialize platform + * Note: The a0 to a4 registers passed to the + * firmware are parameters to this function. + */ + MOV_5R s0, a0, s1, a1, s2, a2, s3, a3, s4, a4 + call fw_platform_init + add t0, a0, zero + MOV_5R a0, s0, a1, s1, a2, s2, a3, s3, a4, s4 + add a1, t0, zero + + /* Preload HART details + * s7 -> HART Count + * s8 -> HART Stack Size + * s9 -> Heap Size + * s10 -> Heap Offset + */ +# 将平台相关的数据结构地址加载进 a4 寄存器 + lla a4, platform +# 根据寄存器 a4 将平台相关的数据加载进来 +#if __riscv_xlen > 32 + lwu s7, SBI_PLATFORM_HART_COUNT_OFFSET(a4) + lwu s8, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(a4) + lwu s9, SBI_PLATFORM_HEAP_SIZE_OFFSET(a4) +#else + lw s7, SBI_PLATFORM_HART_COUNT_OFFSET(a4) + lw s8, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(a4) + lw s9, SBI_PLATFORM_HEAP_SIZE_OFFSET(a4) +#endif + +# tp 是 RISC-V 中的一个特殊寄存器,用于指向临时工作区域(scratch space)。 +# 将 _fw_end 地址加载进 tp, 在用 s7,s8 计算出 scratch space, 再加上 tp, 调整 scratch space 的位置 + /* Setup scratch space for all the HARTs */ + lla tp, _fw_end + mul a5, s7, s8 + add tp, tp, a5 +# 原理同上 +# 调整 heap 基址 + /* Setup heap base address */ + lla s10, _fw_start + sub s10, tp, s10 + add tp, tp, s9 + /* Keep a copy of tp */ + add t3, tp, zero + /* Counter */ + li t2, 1 + /* hartid 0 is mandated by ISA */ + li t1, 0 +_scratch_init: + /* + * The following registers hold values that are computed before + * entering this block, and should remain unchanged. + * + * t3 -> the firmware end address + * s7 -> HART count + * s8 -> HART stack size + * s9 -> Heap Size + * s10 -> Heap Offset + */ +# 找到 scratch space 的基址 + add tp, t3, zero + sub tp, tp, s9 +# t1 首次是 0,计算出来的 a5 也等于 0, +# 这个 t1 是 hart 的编号,s8 是每个核的栈大小 +# 所以,a5 是每个 hart 的栈的偏移。 + mul a5, s8, t1 + sub tp, tp, a5 + li a5, SBI_SCRATCH_SIZE + sub tp, tp, a5 + + /* Initialize scratch space */ + /* Store fw_start and fw_size in scratch space */ + lla a4, _fw_start + sub a5, t3, a4 + REG_S a4, SBI_SCRATCH_FW_START_OFFSET(tp) + REG_S a5, SBI_SCRATCH_FW_SIZE_OFFSET(tp) + + /* Store R/W section's offset in scratch space */ + lla a4, __fw_rw_offset + REG_L a5, 0(a4) + REG_S a5, SBI_SCRATCH_FW_RW_OFFSET(tp) + + /* Store fw_heap_offset and fw_heap_size in scratch space */ + REG_S s10, SBI_SCRATCH_FW_HEAP_OFFSET(tp) + REG_S s9, SBI_SCRATCH_FW_HEAP_SIZE_OFFSET(tp) + +# 设置函数:加载下一个阶段的参数 1 的地址 + /* Store next arg1 in scratch space */ + MOV_3R s0, a0, s1, a1, s2, a2 + call fw_next_arg1 + REG_S a0, SBI_SCRATCH_NEXT_ARG1_OFFSET(tp) + MOV_3R a0, s0, a1, s1, a2, s2 +# 设置函数:加载下一个阶段的可执行文件的地址 的地址 + /* Store next address in scratch space */ + MOV_3R s0, a0, s1, a1, s2, a2 + call fw_next_addr + REG_S a0, SBI_SCRATCH_NEXT_ADDR_OFFSET(tp) + MOV_3R a0, s0, a1, s1, a2, s2 +# 设置函数:设置下一个阶段的特权等级 的地址 + /* Store next mode in scratch space */ + MOV_3R s0, a0, s1, a1, s2, a2 + call fw_next_mode + REG_S a0, SBI_SCRATCH_NEXT_MODE_OFFSET(tp) + MOV_3R a0, s0, a1, s1, a2, s2 +# 设置启动函数地址 + /* Store warm_boot address in scratch space */ + lla a4, _start_warm + REG_S a4, SBI_SCRATCH_WARMBOOT_ADDR_OFFSET(tp) + +# 将特定平台的数据结构加载进来 + /* Store platform address in scratch space */ + lla a4, platform + REG_S a4, SBI_SCRATCH_PLATFORM_ADDR_OFFSET(tp) + +# 将 hartid-to-scratch 函数的地址存入 scratch space + /* Store hartid-to-scratch function address in scratch space */ + lla a4, _hartid_to_scratch + REG_S a4, SBI_SCRATCH_HARTID_TO_SCRATCH_OFFSET(tp) + +# 将 trap-exit 函数的地址存入 scratch space + /* Store trap-exit function address in scratch space */ + lla a4, _trap_exit + REG_S a4, SBI_SCRATCH_TRAP_EXIT_OFFSET(tp) + /* Clear tmp0 in scratch space */ + REG_S zero, SBI_SCRATCH_TMP0_OFFSET(tp) + /* Store firmware options in scratch space */ + MOV_3R s0, a0, s1, a1, s2, a2 + +# FW_OPTIONS 禁用 OpenSBI 启动时打印信息 +#ifdef FW_OPTIONS + li a0, FW_OPTIONS +#else + call fw_options +#endif + REG_S a0, SBI_SCRATCH_OPTIONS_OFFSET(tp) + MOV_3R a0, s0, a1, s1, a2, s2 + /* Move to next scratch space */ +# 再将 t1 + 1,检查 t1 是否小于 s7(HART_COUNT) +# 如果小于,说明还有其他核的 scratch_space 没有初始化完成 +# 继续进行其他核心的初始化工作 + add t1, t1, t2 + blt t1, s7, _scratch_init + + /* + * Relocate Flatened Device Tree (FDT) + * source FDT address = previous arg1 + * destination FDT address = next arg1 + * + * Note: We will preserve a0 and a1 passed by + * previous booting stage. + */ +# a1 = 0,意味着不需要进行 _fdt_reloc +# a1 的值见 +# 279: lla a1, fw_fdt_bin + beqz a1, _fdt_reloc_done + /* Mask values in a4 */ + li a4, 0xff + /* t1 = destination FDT start address */ + MOV_3R s0, a0, s1, a1, s2, a2 +# 加载下一个阶段的参数 1 + call fw_next_arg1 + add t1, a0, zero + MOV_3R a0, s0, a1, s1, a2, s2 + beqz t1, _fdt_reloc_done + beq t1, a1, _fdt_reloc_done + /* t0 = source FDT start address */ + add t0, a1, zero + /* t2 = source FDT size in big-endian */ +#if __riscv_xlen == 64 + lwu t2, 4(t0) +#else + lw t2, 4(t0) +#endif + /* t3 = bit[15:8] of FDT size */ + add t3, t2, zero + srli t3, t3, 16 + and t3, t3, a4 + slli t3, t3, 8 + /* t4 = bit[23:16] of FDT size */ + add t4, t2, zero + srli t4, t4, 8 + and t4, t4, a4 + slli t4, t4, 16 + /* t5 = bit[31:24] of FDT size */ + add t5, t2, zero + and t5, t5, a4 + slli t5, t5, 24 + /* t2 = bit[7:0] of FDT size */ + srli t2, t2, 24 + and t2, t2, a4 + /* t2 = FDT size in little-endian */ + or t2, t2, t3 + or t2, t2, t4 + or t2, t2, t5 + /* t2 = destination FDT end address */ + add t2, t1, t2 + /* FDT copy loop */ + ble t2, t1, _fdt_reloc_done +_fdt_reloc_again: + REG_L t3, 0(t0) + REG_S t3, 0(t1) + add t0, t0, __SIZEOF_POINTER__ + add t1, t1, __SIZEOF_POINTER__ + blt t1, t2, _fdt_reloc_again +_fdt_reloc_done: + +# 启动核表明自己启动完成 + /* mark boot hart done */ + li t0, BOOT_STATUS_BOOT_HART_DONE + lla t1, _boot_status + REG_S t0, 0(t1) + fence rw, rw + j _start_warm + +# 非启动核等待启动核热启动完成 + /* waiting for boot hart to be done (_boot_status == 2) */ +_wait_for_boot_hart: + li t0, BOOT_STATUS_BOOT_HART_DONE + lla t1, _boot_status + REG_L t1, 0(t1) + /* Reduce the bus traffic so that boot hart may proceed faster */ + nop + nop + nop + bne t0, t1, _wait_for_boot_hart + +# 热启动 +# 在这里进行非启动核心的初始化,等待启动核心完成初始化。非启动核心会等待启动核心在主要初始化工作完成后,再进行自己的初始化。 +_start_warm: + /* Reset all registers for non-boot HARTs */ + li ra, 0 + call _reset_regs + + /* Disable all interrupts */ + csrw CSR_MIE, zero + + /* Find HART count and HART stack size */ + lla a4, platform +#if __riscv_xlen == 64 + lwu s7, SBI_PLATFORM_HART_COUNT_OFFSET(a4) + lwu s8, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(a4) +#else + lw s7, SBI_PLATFORM_HART_COUNT_OFFSET(a4) + lw s8, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(a4) +#endif + REG_L s9, SBI_PLATFORM_HART_INDEX2ID_OFFSET(a4) + +# 使用 CSR(Control and Status Register)指令,将机器模式下的 HART ID(处理器 ID)读取到 s6 寄存器中。 +# CSR_MHARTID 是一个特定的寄存器控制编码,用于获取当前 HART 的 ID。 + /* Find HART id */ + csrr s6, CSR_MHARTID + + /* Find HART index */ + beqz s9, 3f + li a4, 0 +1: +#if __riscv_xlen == 64 + lwu a5, (s9) +#else + lw a5, (s9) +#endif + beq a5, s6, 2f + add s9, s9, 4 + add a4, a4, 1 + blt a4, s7, 1b + li a4, -1 +2: add s6, a4, zero +3: bge s6, s7, _start_hang + +# 经过下面的操作,可以找到符合上面条件的 HART 的 scratch space 的 end 位置 + /* Find the scratch space based on HART index */ + lla tp, _fw_end + mul a5, s7, s8 + add tp, tp, a5 + mul a5, s8, s6 + sub tp, tp, a5 + li a5, SBI_SCRATCH_SIZE + sub tp, tp, a5 + +# 将上面的值写入 CSR_MSCRATCH 寄存器 + /* update the mscratch */ + csrw CSR_MSCRATCH, tp + + /* Setup stack */ + add sp, tp, zero + + /* Setup trap handler */ + lla a4, _trap_handler +# 如果架构是 32 位的,做一些特殊操作 +#if __riscv_xlen == 32 + csrr a5, CSR_MISA + srli a5, a5, ('H' - 'A') + andi a5, a5, 0x1 + beq a5, zero, _skip_trap_handler_rv32_hyp + lla a4, _trap_handler_rv32_hyp +_skip_trap_handler_rv32_hyp: +#endif + csrw CSR_MTVEC, a4 + +#if __riscv_xlen == 32 + /* Override trap exit for H-extension */ + csrr a5, CSR_MISA + srli a5, a5, ('H' - 'A') + andi a5, a5, 0x1 + beq a5, zero, _skip_trap_exit_rv32_hyp + lla a4, _trap_exit_rv32_hyp + csrr a5, CSR_MSCRATCH + REG_S a4, SBI_SCRATCH_TRAP_EXIT_OFFSET(a5) +_skip_trap_exit_rv32_hyp: +#endif + +# 正式进入 SBI 运行时环境 + /* Initialize SBI runtime */ + csrr a0, CSR_MSCRATCH + call sbi_init + + /* We don't expect to reach here hence just hang */ + j _start_hang + + .data + .align 3 +#ifdef FW_PIC +_runtime_offset: + RISCV_PTR 0 +#endif +_relocate_lottery: + RISCV_PTR 0 +_boot_status: + RISCV_PTR 0 +_load_start: + RISCV_PTR _fw_start +_link_start: + RISCV_PTR FW_TEXT_START +_link_end: + RISCV_PTR _fw_reloc_end +__fw_rw_offset: + RISCV_PTR _fw_rw_start - _fw_start + + .section .entry, "ax", %progbits + .align 3 + .globl _hartid_to_scratch +_hartid_to_scratch: + /* + * a0 -> HART ID (passed by caller) + * a1 -> HART Index (passed by caller) + * t0 -> HART Stack Size + * t1 -> HART Stack End + * t2 -> Temporary + */ + lla t2, platform +#if __riscv_xlen == 64 + lwu t0, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(t2) + lwu t2, SBI_PLATFORM_HART_COUNT_OFFSET(t2) +#else + lw t0, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(t2) + lw t2, SBI_PLATFORM_HART_COUNT_OFFSET(t2) +#endif + sub t2, t2, a1 + mul t2, t2, t0 + lla t1, _fw_end + add t1, t1, t2 + li t2, SBI_SCRATCH_SIZE + sub a0, t1, t2 + ret + + .section .entry, "ax", %progbits + .align 3 + .globl _start_hang +_start_hang: + wfi + j _start_hang + + .section .entry, "ax", %progbits + .align 3 + .weak fw_platform_init +fw_platform_init: + add a0, a1, zero + ret + + /* Map implicit memcpy() added by compiler to sbi_memcpy() */ + .section .text + .align 3 + .globl memcpy + +# 下面许多都是类似 c 语言中的函数,这里不再赘述 +memcpy: + tail sbi_memcpy + + /* Map implicit memset() added by compiler to sbi_memset() */ + .section .text + .align 3 + .globl memset +memset: + tail sbi_memset + + /* Map implicit memmove() added by compiler to sbi_memmove() */ + .section .text + .align 3 + .globl memmove +memmove: + tail sbi_memmove + + /* Map implicit memcmp() added by compiler to sbi_memcmp() */ + .section .text + .align 3 + .globl memcmp +memcmp: + tail sbi_memcmp + +.macro TRAP_SAVE_AND_SETUP_SP_T0 + /* Swap TP and MSCRATCH */ + csrrw tp, CSR_MSCRATCH, tp + + /* Save T0 in scratch space */ + REG_S t0, SBI_SCRATCH_TMP0_OFFSET(tp) + + /* + * Set T0 to appropriate exception stack + * + * Came_From_M_Mode = ((MSTATUS.MPP < PRV_M) ? 1 : 0) - 1; + * Exception_Stack = TP ^ (Came_From_M_Mode & (SP ^ TP)) + * + * Came_From_M_Mode = 0 ==> Exception_Stack = TP + * Came_From_M_Mode = -1 ==> Exception_Stack = SP + */ + csrr t0, CSR_MSTATUS + srl t0, t0, MSTATUS_MPP_SHIFT + and t0, t0, PRV_M + slti t0, t0, PRV_M + add t0, t0, -1 + xor sp, sp, tp + and t0, t0, sp + xor sp, sp, tp + xor t0, tp, t0 + + /* Save original SP on exception stack */ + REG_S sp, (SBI_TRAP_REGS_OFFSET(sp) - SBI_TRAP_REGS_SIZE)(t0) + + /* Set SP to exception stack and make room for trap registers */ + add sp, t0, -(SBI_TRAP_REGS_SIZE) + + /* Restore T0 from scratch space */ + REG_L t0, SBI_SCRATCH_TMP0_OFFSET(tp) + + /* Save T0 on stack */ + REG_S t0, SBI_TRAP_REGS_OFFSET(t0)(sp) + + /* Swap TP and MSCRATCH */ + csrrw tp, CSR_MSCRATCH, tp +.endm + +.macro TRAP_SAVE_MEPC_MSTATUS have_mstatush + /* Save MEPC and MSTATUS CSRs */ + csrr t0, CSR_MEPC + REG_S t0, SBI_TRAP_REGS_OFFSET(mepc)(sp) + csrr t0, CSR_MSTATUS + REG_S t0, SBI_TRAP_REGS_OFFSET(mstatus)(sp) + .if \have_mstatush + csrr t0, CSR_MSTATUSH + REG_S t0, SBI_TRAP_REGS_OFFSET(mstatusH)(sp) + .else + REG_S zero, SBI_TRAP_REGS_OFFSET(mstatusH)(sp) + .endif +.endm + +.macro TRAP_SAVE_GENERAL_REGS_EXCEPT_SP_T0 + /* Save all general regisers except SP and T0 */ + REG_S zero, SBI_TRAP_REGS_OFFSET(zero)(sp) + REG_S ra, SBI_TRAP_REGS_OFFSET(ra)(sp) + REG_S gp, SBI_TRAP_REGS_OFFSET(gp)(sp) + REG_S tp, SBI_TRAP_REGS_OFFSET(tp)(sp) + REG_S t1, SBI_TRAP_REGS_OFFSET(t1)(sp) + REG_S t2, SBI_TRAP_REGS_OFFSET(t2)(sp) + REG_S s0, SBI_TRAP_REGS_OFFSET(s0)(sp) + REG_S s1, SBI_TRAP_REGS_OFFSET(s1)(sp) + REG_S a0, SBI_TRAP_REGS_OFFSET(a0)(sp) + REG_S a1, SBI_TRAP_REGS_OFFSET(a1)(sp) + REG_S a2, SBI_TRAP_REGS_OFFSET(a2)(sp) + REG_S a3, SBI_TRAP_REGS_OFFSET(a3)(sp) + REG_S a4, SBI_TRAP_REGS_OFFSET(a4)(sp) + REG_S a5, SBI_TRAP_REGS_OFFSET(a5)(sp) + REG_S a6, SBI_TRAP_REGS_OFFSET(a6)(sp) + REG_S a7, SBI_TRAP_REGS_OFFSET(a7)(sp) + REG_S s2, SBI_TRAP_REGS_OFFSET(s2)(sp) + REG_S s3, SBI_TRAP_REGS_OFFSET(s3)(sp) + REG_S s4, SBI_TRAP_REGS_OFFSET(s4)(sp) + REG_S s5, SBI_TRAP_REGS_OFFSET(s5)(sp) + REG_S s6, SBI_TRAP_REGS_OFFSET(s6)(sp) + REG_S s7, SBI_TRAP_REGS_OFFSET(s7)(sp) + REG_S s8, SBI_TRAP_REGS_OFFSET(s8)(sp) + REG_S s9, SBI_TRAP_REGS_OFFSET(s9)(sp) + REG_S s10, SBI_TRAP_REGS_OFFSET(s10)(sp) + REG_S s11, SBI_TRAP_REGS_OFFSET(s11)(sp) + REG_S t3, SBI_TRAP_REGS_OFFSET(t3)(sp) + REG_S t4, SBI_TRAP_REGS_OFFSET(t4)(sp) + REG_S t5, SBI_TRAP_REGS_OFFSET(t5)(sp) + REG_S t6, SBI_TRAP_REGS_OFFSET(t6)(sp) +.endm + +.macro TRAP_CALL_C_ROUTINE + /* Call C routine */ + add a0, sp, zero + call sbi_trap_handler +.endm + +.macro TRAP_RESTORE_GENERAL_REGS_EXCEPT_A0_T0 + /* Restore all general regisers except A0 and T0 */ + REG_L ra, SBI_TRAP_REGS_OFFSET(ra)(a0) + REG_L sp, SBI_TRAP_REGS_OFFSET(sp)(a0) + REG_L gp, SBI_TRAP_REGS_OFFSET(gp)(a0) + REG_L tp, SBI_TRAP_REGS_OFFSET(tp)(a0) + REG_L t1, SBI_TRAP_REGS_OFFSET(t1)(a0) + REG_L t2, SBI_TRAP_REGS_OFFSET(t2)(a0) + REG_L s0, SBI_TRAP_REGS_OFFSET(s0)(a0) + REG_L s1, SBI_TRAP_REGS_OFFSET(s1)(a0) + REG_L a1, SBI_TRAP_REGS_OFFSET(a1)(a0) + REG_L a2, SBI_TRAP_REGS_OFFSET(a2)(a0) + REG_L a3, SBI_TRAP_REGS_OFFSET(a3)(a0) + REG_L a4, SBI_TRAP_REGS_OFFSET(a4)(a0) + REG_L a5, SBI_TRAP_REGS_OFFSET(a5)(a0) + REG_L a6, SBI_TRAP_REGS_OFFSET(a6)(a0) + REG_L a7, SBI_TRAP_REGS_OFFSET(a7)(a0) + REG_L s2, SBI_TRAP_REGS_OFFSET(s2)(a0) + REG_L s3, SBI_TRAP_REGS_OFFSET(s3)(a0) + REG_L s4, SBI_TRAP_REGS_OFFSET(s4)(a0) + REG_L s5, SBI_TRAP_REGS_OFFSET(s5)(a0) + REG_L s6, SBI_TRAP_REGS_OFFSET(s6)(a0) + REG_L s7, SBI_TRAP_REGS_OFFSET(s7)(a0) + REG_L s8, SBI_TRAP_REGS_OFFSET(s8)(a0) + REG_L s9, SBI_TRAP_REGS_OFFSET(s9)(a0) + REG_L s10, SBI_TRAP_REGS_OFFSET(s10)(a0) + REG_L s11, SBI_TRAP_REGS_OFFSET(s11)(a0) + REG_L t3, SBI_TRAP_REGS_OFFSET(t3)(a0) + REG_L t4, SBI_TRAP_REGS_OFFSET(t4)(a0) + REG_L t5, SBI_TRAP_REGS_OFFSET(t5)(a0) + REG_L t6, SBI_TRAP_REGS_OFFSET(t6)(a0) +.endm + +.macro TRAP_RESTORE_MEPC_MSTATUS have_mstatush + /* Restore MEPC and MSTATUS CSRs */ + REG_L t0, SBI_TRAP_REGS_OFFSET(mepc)(a0) + csrw CSR_MEPC, t0 + REG_L t0, SBI_TRAP_REGS_OFFSET(mstatus)(a0) + csrw CSR_MSTATUS, t0 + .if \have_mstatush + REG_L t0, SBI_TRAP_REGS_OFFSET(mstatusH)(a0) + csrw CSR_MSTATUSH, t0 + .endif +.endm + +.macro TRAP_RESTORE_A0_T0 + /* Restore T0 */ + REG_L t0, SBI_TRAP_REGS_OFFSET(t0)(a0) + + /* Restore A0 */ + REG_L a0, SBI_TRAP_REGS_OFFSET(a0)(a0) +.endm + + .section .entry, "ax", %progbits + .align 3 + .globl _trap_handler + .globl _trap_exit +_trap_handler: + TRAP_SAVE_AND_SETUP_SP_T0 + + TRAP_SAVE_MEPC_MSTATUS 0 + + TRAP_SAVE_GENERAL_REGS_EXCEPT_SP_T0 + + TRAP_CALL_C_ROUTINE + +_trap_exit: + TRAP_RESTORE_GENERAL_REGS_EXCEPT_A0_T0 + + TRAP_RESTORE_MEPC_MSTATUS 0 + + TRAP_RESTORE_A0_T0 + + mret + +#if __riscv_xlen == 32 + .section .entry, "ax", %progbits + .align 3 + .globl _trap_handler_rv32_hyp + .globl _trap_exit_rv32_hyp +_trap_handler_rv32_hyp: + TRAP_SAVE_AND_SETUP_SP_T0 + + TRAP_SAVE_MEPC_MSTATUS 1 + + TRAP_SAVE_GENERAL_REGS_EXCEPT_SP_T0 + + TRAP_CALL_C_ROUTINE + +_trap_exit_rv32_hyp: + TRAP_RESTORE_GENERAL_REGS_EXCEPT_A0_T0 + + TRAP_RESTORE_MEPC_MSTATUS 1 + + TRAP_RESTORE_A0_T0 + + mret +#endif + + .section .entry, "ax", %progbits + .align 3 + .globl _reset_regs +_reset_regs: + + /* flush the instruction cache */ + fence.i + /* Reset all registers except ra, a0, a1 and a2 */ + li sp, 0 + li gp, 0 + li tp, 0 + li t0, 0 + li t1, 0 + li t2, 0 + li s0, 0 + li s1, 0 + li a3, 0 + li a4, 0 + li a5, 0 + li a6, 0 + li a7, 0 + li s2, 0 + li s3, 0 + li s4, 0 + li s5, 0 + li s6, 0 + li s7, 0 + li s8, 0 + li s9, 0 + li s10, 0 + li s11, 0 + li t3, 0 + li t4, 0 + li t5, 0 + li t6, 0 + csrw CSR_MSCRATCH, 0 + + ret + +#ifdef FW_FDT_PATH + .section .rodata + .align 4 + .globl fw_fdt_bin +fw_fdt_bin: + .incbin FW_FDT_PATH +#ifdef FW_FDT_PADDING + .fill FW_FDT_PADDING, 1, 0 +#endif +#endif + +``` + +## 小结 + +通过分析 `fw_base.S` 中的源码,我们已经知道了 OpenSBI 的启动流程的最前面一段是如何完成的。之后我们会对 `fw_payload.S`、`fw_jump.S` 和 `fw_dynamic.S` 这三个文件进行解析,带领读者体会其中的差异。 + +## 参考资料 + +- OpenSBI 源代码 +- RISC-V 手册 +- [Lingrui98/RISC-V-book/blob/master/rvbook.pdf][001] +- [OpenSBI 官方仓库固件部分文档][002] +- [opensbi-firmware-and-sbi-hsm][003] + +[001]: https://github.com/Lingrui98/RISC-V-book/blob/master/rvbook.pdf +[002]: https://github.com/riscv-software-src/opensbi/tree/master/docs/firmware +[003]: https://tinylab.org/opensbi-firmware-and-sbi-hsm/ diff --git a/articles/images/sbi-firmware/instruction-spec.png b/articles/images/sbi-firmware/instruction-spec.png new file mode 100644 index 0000000000000000000000000000000000000000..e8adbab57331f39254f798ca05f72691b99742d7 Binary files /dev/null and b/articles/images/sbi-firmware/instruction-spec.png differ