opensbi底层初始化
先进入_start
函数, 将a0-a2 保存到 s0-s2, 启动 fw_boot_hart, a0-a2 是qemu传递给opensbi的参数.
在riscv模式中会将riscv的core称为hart(硬件线程)
代码重定位, 判断 _load_start
与 _link_start
是否一致, 如不一致, 则需要重定位
清除寄存器值, 清除除 a0-a2 外的所有寄存器的值
清空bss, _bss_start
到 _bss_end
范围设置为0
设置临时的 trap_hander 为 _start_hang
设置 sp, 紧挨 bss 段, 大小为 0x1000 * 2, 向上增长, 所以 sp top 为 bss_end + 0x2000位置
读取设备树中的信息:fw_platform_init
(opensbi/platform/generic/platform.c)
a1是设备树地址, 从设备树中解析qemu中设定相关的信息
1 | qemu-system-riscv64 -m 2G -nographic -machine virt -bios /keystone/build/bootrom.build/bootrom.bin -kernel /keystone/build/sm.build/platform/generic/firmware/fw_payload.elf |
fdt重定位
需要把设备树重定位,从而让 uboot 也知道
初始化scratch space, 紧挨着stack, 大小为0x1000
- 存储
_fw_start
和_fw_end
到 scratch space - 存储 next arg1 (FW_JUMP_FDT_ADDR 0x82200000)
- 存储 payload_bin, FW_PAYLOAD_PATH 被填充到 opensbi 预留的指令地址处, 0x80200000, 此处把 kernel 的 bin 填充到这个地方了
- 存储 fw_next_mode, S 模式
- warm_boot address
- platform address
_hartid_to_scratch
_trap_exit
fw_options等
1 | "-DFW_TEXT_START=0x80000000", |
start_warm 除 a0-a2 外的所有寄存器重置, 禁用中断, 读取 hartid(CSR_MHARTID), 根据 hartid 查找对应的 scratch space, 设置对应的 sp, 设置_trap_handler 为最终的陷阱入口函数, 支持的 hardid count 数是通过 qemu-system-riscv64 命令行传入的, 定制到 fdt 中去的 platform.hart_count = hart_count;
进入c函数
sbi_init -> init_coldboot (参数为 scratch, 即前面 scratch space 空间填充的数据, 对应的结构体为 struct sbi_scratch
), nextmode 固定为 S
init_coldboot 做的事情很多, 下面进行详细拆解
sbi_scratch_init
根据 hardid 填充 hartid_to_scratch_table
sbi_domain_init
初始化动态加载的镜像的模块, 应该是当前域 root_domain, root_memregs, 设定内存地址范围, order(位宽), base(基址), 填充 root domain, root domain 的 hardid 同当前唤起的 coldboot 的 hardid, 最后将 root domain 填充到 domidx_to_domain_table
, index 为 hardid
sbi_scratch_alloc_offset
为 scratch space 分配空间, 内容清0
sbi_hsm_init
为 struct sbi_hsm_data
分配空间, 并初始化 SBI_HART_STARTING (当前 hartid), 其他的 hartid 为 SBI_HART_STOPPED
sbi_platform_early_init
平台早期初始化, 调用 plat->early_init, plat 为 scratch 里的 platform_addr 指向(sbi_platform 结构体, 该结构体由前面的 fw_platform_init
过程根据 fdt 设备树内容填充, 来自于 qemu), 而 platform_ops_addr 不是填充的, 而是初始的 Generic 设备的 platform_ops. 可以插桩或自定义实现平台相关的初始化代码.
1 | const struct sbi_platform_operations platform_ops = { |
sbi_hart_init
该核心线程协处理器相关初始化, 处理器支持的特性保存在 hart_features 结构体中, 在 scratch space 中为该结构体分配内存并初始化为0.
接下来需要判定处理器支持哪些 feature.
- pmp 内存访问是否支持
- 是否支持 SCOUNTEREN
- 是否支持 MCOUNTEREN
- 是否支持 CSR_TIME
如 misa_extension 含 ‘H’, With H-extension so, MTVAL2 and MTINST CSRs available
这个地方怎么判断处理器支持哪些 feature 的, 是通过强制写一些协处理器, 并将 mtvec 临时变为
__sbi_expected_trap
或__sbi_expected_trap_hext
(根据 H extension 的状态), 该异常入口会将mepc
,mcause
等寄存器状态保存到 sbi_trap_info 结构体的临时变量中(传递参数为 a3), 如果发生了异常, 对应的 cause 非0, 这种情况就表明不支持该 feature. 反之, 则表明支持这个 feature.
mstatus_init
mstatus 初始化
- D|F enable FPU
- V ENABLE VECTOR 向量扩展, 如支持,
对应位置1 (23|24)
, (如果对应位是0, 执行向量相关的指令或访问 v 寄存器会报异常)- Attempts to execute any vector instruction, or to access the vector CSRs, raise an illegal-instruction exception when the VS field is set to off.
- 启用性能计数器
- 禁用中断
- 禁用S模式mmu
delegate_traps
Send M-mode interrupts and most exceptions to S-mode
设置 CSR_MIDELEG
和 CSR_MEDELEG
寄存器, 在S模式的陷阱中处理M模式的中断和大部分异常
sbi_console_init
这之后, 就能从串口打印log, 还是调用的平台的插桩函数 plat->console_init
sbi_platform_irqchip_init
irq中断初始化, Initialize the platform interrupt controller for current HART, 平台的插桩函数 plat->irqchip_init
sbi_ipi_init
核间通信初始化
- 为核间通信交换数据在 scratch space 中分配空间, 并初始化为0
- ipi 通信可能需要多种唤醒的事件, 如可约定多种核间通信的事件, 保存在 ipi_ops_array 数组中,
struct sbi_ipi_event_ops *ipi_ops_array[SBI_IPI_EVENT_MAX]
, 通过sbi_ipi_event_create
创建一个事件, 可以创建多个事件, 最终都保存到 ipi_ops_array 数组中, 这里注册了 ipi_smode_ops 和 ipi_halt_ops 事件, 分别处理其他核发来的让该核处理 S 模式中断的事件(mip ssip pending 置1)和让该核暂停的事件 - 平台插桩代码, plat->ipi_init, 平台按需实现
- 启用 M 模式中断
sbi_tlb_init
mmu的tlb表的初始化
注册 tlb_ops 事件, 在 scratch space 中分配空间存放 IPI_TLB_SYNC IPI_TLB_FIFO IPI_TLB_FIFO_MEM
数据, 初始化 sbi_fifo
1 | static struct sbi_ipi_event_ops tlb_ops = { |
sbi_timer_init
timer 初始化, 在 scratch space 中分配空间存放 time_delta
平台插桩函数 plat->timer_init, 平台按需实现
如支持 timer, 注册 get_ticks 函数安装到 get_time_val, 否则由平台实现 plat->timer_value
sbi_ecall_init
ecall 初始化, riscv 的系统调用都是 ecall, 如 linux 的 posix 接口在 riscv 上都是用 ecall 实现的, 由 U/S 模式发送 ecall 路由到 M 模式
调用 sbi_ecall_register_extension 注册相应的中断处理函数, 根据 extid_start extid_end 找到对应的 handler. 这个地方用到的频率非常高, 比较重要
sbi_domain_finalize
当前域(root 域)完成初始化
- plat->domains_init (Initialize and populate domains for the platform)
- 遍历 domidx_to_domain_table, 这个时候还只有 root domain, 所以只能遍历一次, 由于是coldboot, 最后只设置了scratch->next_addr, next_mode , next_arg1, 用于启动下一个镜像(这里应该是kernel)
sbi_hart_pmp_configure
设置下一个镜像的pmp, 内存权限
这里用到了前面设置过的 root_memregs
1 | root_memregs[ROOT_FW_REGION].order = log2roundup(scratch->fw_size); |
其中 ROOT_ALL_REGION 就是为下一级镜像准备的, 这里flags是 rwx, order是64, 只占一个pmp entry, 权限为rwx
1 | pmp_set(pmp_idx++, pmp_flags, reg->base, reg->order); |
sbi_platform_final_init
插桩函数, plat->final_init , 平台按需实现
Platform final initialization should be last so that it sees correct domain assignment and PMP configuration.
domain设置完了, pmp也设置了, 这个地方平台的实现能看到这些了
sbi_boot_print_hart
平台启动信息的打印
1 | Boot HART ID : 0 |
wake_coldboot_harts
当前核启动完成, coldboot 完成, 告诉其他核 coldboot 完成了
sbi_hsm_prepare_next_jump
核状态 由SBI_HART_STARTING
-> SBI_HART_STARTED
sbi_hart_switch_mode
启动下一级镜像
1 | sbi_hart_switch_mode(hartid, scratch->next_arg1, scratch->next_addr, |
根据模式以及模式的支持情况( misa_extension 是否支持 S/U 模式), 选择启动到对应的模式, 如不支持则把核 hang 住
mstatus mpp 位置为对应的模式 mpie 置0
mepc 设置为 next_addr, ( 这个地方已经设置为了 kernel 的入口地址 0x82200000)
如下一级为 S 模式, 则 stvec 设置为 next_addr, sscratch 置0 , sie 置0, sip 置0, satp 置0.
传递next_args1 (a1), 0x82200000
mret 跳转到 mepc 的位置, 正式启动到下一级镜像, 这里是 kernel
provisioning of roots 051
opensbi 介绍
背景
RISC-V指令集的SBI标准规定了类Unix平台下,操作系统运行环境的规范。这个规范拥有多种实现,OpenSBI是它的一种实现.
这个运行环境不仅将引导启动RISC-V下的操作系统, 还将常驻后台,为操作系统提供一系列二进制接口,以便其获取和操作硬件信息。 RISC-V给出了此类环境和二进制接口的规范,称为“操作系统二进制接口”,即“SBI”。
SBI是在M模式下运行的特定于平台的固件,它将管理S、U等特权上的程序或通用的操作系统。
RISCV 模式
机器模式(Machine Mode)
- 处理器内核被复位后,默认处于 Machine Mode
- 在 Machine Mode 下,程序能够访问所有的 CSR 寄存器。
- 在 Machine Mode 下,程序能够访问所有的物理地址区域
监督者模式(Supervisor mode)
用户模式(User Mode)
Machine-Level ISA
- machine mode 读写的寄存器,如 mhartid、mstatus、mtvec、mcause
- machine 特权指令,如 ecall(所有模式)、mret、sret、wfi(所有模式、U 模式可选)
- 复位、NMI 发生后 hart 状态
- PMA 物理内存属性,原子、order、一致性
- PMP 物理内存保护机制和寄存器
Supervisor-Level ISA
- Supervisor mode 读写的寄存器,如 sstaus、stvec、scause、satp
- Supervisor 特权指令,如 ecal、sret、sfence.vma
- Page-Based 32/39/48/57-bit Virtual-Memory Systems
Hypervisor Extension
- hypervisor 虚拟化读写的寄存器,如 hstatus、vstvec、vepc
- hypervisor 特权指令,load/store、fence
![[../Excalidraw/opensbi框架 2022-09-09 14.33.53.excalidraw]]
- 为操作系统层(S-mode) 提供 sbi 接口的高特权级别的软件被称为sbi 实现或 特权执行环境(SEE). sbi 实现或SEE工作在M层, 带H扩展的 Host S层 os 也可以实现相关的sbi 接口用来与Guest os 交互通信.
SBI 的作用
可以为多个 os 提供公共 driver
可以避免为每个不同 os 适配平台相关的功能, 将功能做到 sbi 层, 其他的各种 os 只需要 ecall 调用, 访问 sbi 层来实现平台的功能
在 S 层工作的 OS 可以通过 ecall sbi 来访问或修改受限在 M 层的硬件资源
多个 OS 之间的通信媒介, 限制不同 OS 的可以访问的硬件资源 (如 PMP 限制 os 可以使用哪些内存地址)
AEE: application execution environment
ABI: application binary interface
The ABI includes the supported user-level ISA plus a set of ABI calls to interact with the AEE. The ABI hides details of the AEE from the application to allow greater flexibility in implementing the AEE.
SEE: supervisor execution environment
SBI: supervisor binary interface
HEE: hypervisor execution environment
- isolate the hypervisor from details of the hardware platform.
HBI: hypervisor binary interface
所有硬件必须提供M-mode,因为它拥有访问整个机器的能力,最简单的RISC-V实现只有M-mode,但是不能抑制恶意APP。
未扩展前,不支持 hypervisor 模式
hypervisor扩展模式,V标识当前hart是否处于虚拟化模式
SBI EXTENSION
- Base Extension (EID
#0x10
)
- Base Extension (EID
- Legacy Extensions (EIDs
#0x00
-#0x0F
)
- Legacy Extensions (EIDs
- Timer Extension (EID
#0x54494D45
“TIME”)
- Timer Extension (EID
- IPI Extension (EID
#0x735049
“sPI: s-mode IPI”)
- IPI Extension (EID
- RFENCE Extension (EID
#0x52464E43
“RFNC”)
- RFENCE Extension (EID
- Hart State Management Extension (EID
#0x48534D
“HSM”)
- Hart State Management Extension (EID
- System Reset Extension (EID
#0x53525354
“SRST”)
- System Reset Extension (EID
- Performance Monitoring Unit Extension (EID
#0x504D55
“PMU”)
- Performance Monitoring Unit Extension (EID
- Experimental SBI Extension Space (EIDs
#0x08000000
-#0x08FFFFFF
)
- Experimental SBI Extension Space (EIDs
- Vendor-Specific SBI Extension Space (EIDs
#0x09000000
-#0x09FFFFFF
)
- Vendor-Specific SBI Extension Space (EIDs
- Firmware Specific SBI Extension Space (EIDs
#0x0A000000
-#0x0AFFFFFF
)
- Firmware Specific SBI Extension Space (EIDs
引导过程
RISC-V芯片的启动流程演变历程:
- **Zeroth Stage Boot Loader(ZSBL)**,安装在板载的ROM中,处于M-mode
- **First Stage Boot Loader(FSBL)**,brings up PPLs and DDR, 处于M-mode
- **Berkeley Boot Loader(BBL)**,adds emulation for soft instructions,处于M-mode
- User Payload,包含软件,如Linux,处于S-mode或U-mode
ZSBL(第0阶段Bootloader)
处于M-mode的ZSBL保存在maskROM 中地址为0x1_0000的位置,它负责从GPT中加载更为复杂的FSBL(寻找uuid的GPT分区)。通过先加载GPT的头文件,然后一块一块(块大小为512bytes)的顺序地扫描GPT。加载过程结束后,FSBL被加载进地址为0x0800_0000的L2 LIM缓存中,随后,将执行FSBL阶段。
FSBL(第1阶段Bootloader)
处于M-mode的FSBL从L2 LIM地址为0x0800_0000的缓存上执行,它负责为在DDR上运行系统做准备,可大概分为如下的这些任务:
- 配置芯片上的PLL 核心频率
- 配置DDR PLL,PHY和DDR控制器
- 外部PHY,重置
- 从编号为uuid的GTP分区下载BBL
- 扫描OTP获取的芯片序列号
- 将DTB(硬件设备树)复制到DDR,填写FSBL版本,内存大小和MAC地址
- 跳转到DDR上地址为0x8000_0000的位置, 启动BBL 或 opensbi
BBL(第2阶段的Bootloader)
处于M-mode的BBL从DDR上地址为0x8000_0000的位置执行。它负责为如SBI(Supervisor Binary Interface)等RISC-V需要的,但没有被芯片本身实现的指令。在进行写操作的时候,BBL通常包含一个嵌入的Linux内核负载,当SBI被初始化后,它将跳转的Linux内核。
U-Boot属于一种bootloader,简单来说,其作用就是从flash中读出内核,随后加载在内存中,最终初始化并启动操作系统内核。
具体来说,可以分为下述几个方面:
1)U-Boot主要作用是用来启动操作系统内核。体现在uboot最后一句代码就是启动内核。
2)U-Boot还要负责部署操作系统内核。体现在uboot最后的传参。
3)U-Boot中还有操作Flash等板子上硬件的驱动。例如串口要打印,ping网络,擦除、烧写flash等。提供烧写功能等
4)U-Boot还得提供一个命令行界面供人来操作。如果kernel 起不来, uboot命令行可以提供操作界面, 让用户来手动加载kernel
OpenSBI的初始化流程如下:
(1)底层初始化:
- 判断 hart(Hardware Thread) id
- 代码重定位(判断
_load_start
与_start
函数是否一致) - 清除除保存设备树地址的寄存器的值
- 清除bss段
- 设置栈指针(预留栈空间)
- 读取设备树中的设备信息
- 设备树重定位,为U-Boot提供信息
- 至此,底层初始化结束,执行sbi_init,进行正式的初始化程序
(2)设备初始化:
首先判断是通过 S-mode 还是 M-mode 启动
- sbi_domain_init 初始化动态加载的镜像的模块
- sbi_platform_early_init 平台的早期初始化
- sbi_console_init 控制台初始化,从这里开始,就可以使用串口输出了。
- sbi_platform_irqchip_init irq中断初始化
- sbi_ipi_init 核间中断初始化
- sbi_tlb_init mmu的tlb表的初始化
- sbi_timer_init timer初始化
- sbi_hsm_prepare_next_jump 准备下一级的boot
(3)二级boot跳转,如加载U-Boot或者Linux内核
ARM架构中的Hypervisor与OpenSBI的对比
在 ARM 架构中,Hypervisor 层承担了虚拟化的作用,承担了如内存管理、设备模拟、设备分配、异常处理、指令捕获、虚拟异常管理、中断控制器管理、调度、上下文切换、内存转换、多个虚拟地址空间管理等非常多的功能。
相比之下,SBI在RISC-V架构中充当了BIOS和在操作系统运行时为上层提供底层抽象的作用,功能较少。不过SBI也在不断发展中,可能在将来SBI会去承担虚拟化的功能。