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 类型指令:用于无条件操作;
+
+
+### 常见指令
+
+这里不再花费篇幅给大家介绍常见指令,大家可以通过互联网学习相关内容。
+
+对于初学者,我推荐可以先从 [《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