0%

opensbi框架

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
2
3
4
5
6
"-DFW_TEXT_START=0x80000000",
"-DFW_JUMP_ADDR=0x80200000",
"-DFW_JUMP_FDT_ADDR=0x82200000",
"-DFW_PAYLOAD_PATH=\"/keystone/build/linux.build/arch/riscv/boot/Image\"",
"-DFW_PAYLOAD_OFFSET=0x200000",
"-DFW_PAYLOAD_FDT_ADDR=0x82200000",

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const struct sbi_platform_operations platform_ops = {
.early_init = generic_early_init,
.final_init = generic_final_init,
.early_exit = generic_early_exit,
.final_exit = generic_final_exit,
.domains_init = generic_domains_init,
.console_putc = fdt_serial_putc,
.console_getc = fdt_serial_getc,
.console_init = fdt_serial_init,
.irqchip_init = fdt_irqchip_init,
.irqchip_exit = fdt_irqchip_exit,
.ipi_send = fdt_ipi_send,
.ipi_clear = fdt_ipi_clear,
.ipi_init = fdt_ipi_init,
.ipi_exit = fdt_ipi_exit,
.get_tlbr_flush_limit = generic_tlbr_flush_limit,
.timer_value = fdt_timer_value,
.timer_event_stop = fdt_timer_event_stop,
.timer_event_start = fdt_timer_event_start,
.timer_init = fdt_timer_init,
.timer_exit = fdt_timer_exit,
.system_reset_check = generic_system_reset_check,
.system_reset = generic_system_reset,
};

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_MIDELEGCSR_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
2
3
4
5
6
static struct sbi_ipi_event_ops tlb_ops = {
.name = "IPI_TLB",
.update = sbi_tlb_update,
.sync = sbi_tlb_sync,
.process = sbi_tlb_process,
};

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
2
3
4
5
6
7
8
9
10
11
12
13
14
root_memregs[ROOT_FW_REGION].order = log2roundup(scratch->fw_size);
root_memregs[ROOT_FW_REGION].base = scratch->fw_start &
~((1UL << root_memregs[0].order) - 1UL);
root_memregs[ROOT_FW_REGION].flags = 0;

/* Root domain allow everything memory region */
root_memregs[ROOT_ALL_REGION].order = __riscv_xlen;
root_memregs[ROOT_ALL_REGION].base = 0;
root_memregs[ROOT_ALL_REGION].flags = (SBI_DOMAIN_MEMREGION_READABLE |
SBI_DOMAIN_MEMREGION_WRITEABLE |
SBI_DOMAIN_MEMREGION_EXECUTABLE);

/* Root domain memory region end */
root_memregs[ROOT_END_REGION].order = 0;

其中 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
2
3
4
5
6
7
8
9
10
11
Boot HART ID              : 0
Boot HART Domain : root
Boot HART ISA : rv64imafdcsu
Boot HART Features : scounteren,mcounteren,time
Boot HART PMP Count : 16
Boot HART PMP Granularity : 4
Boot HART PMP Address Bits: 54
Boot HART MHPM Count : 0
Boot HART MHPM Count : 0
Boot HART MIDELEG : 0x0000000000000222
Boot HART MEDELEG : 0x000000000000b109

wake_coldboot_harts

当前核启动完成, coldboot 完成, 告诉其他核 coldboot 完成了

sbi_hsm_prepare_next_jump

核状态 由SBI_HART_STARTING -> SBI_HART_STARTED

sbi_hart_switch_mode

启动下一级镜像

1
2
sbi_hart_switch_mode(hartid, scratch->next_arg1, scratch->next_addr,
scratch->next_mode, FALSE);

根据模式以及模式的支持情况( 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

    1. Base Extension (EID #0x10)
    1. Legacy Extensions (EIDs #0x00 - #0x0F)
    1. Timer Extension (EID #0x54494D45 “TIME”)
    1. IPI Extension (EID #0x735049 “sPI: s-mode IPI”)
    1. RFENCE Extension (EID #0x52464E43 “RFNC”)
    1. Hart State Management Extension (EID #0x48534D “HSM”)
    1. System Reset Extension (EID #0x53525354 “SRST”)
    1. Performance Monitoring Unit Extension (EID #0x504D55 “PMU”)
    1. Experimental SBI Extension Space (EIDs #0x08000000 - #0x08FFFFFF)
    1. Vendor-Specific SBI Extension Space (EIDs #0x09000000 - #0x09FFFFFF)
    1. Firmware Specific SBI Extension Space (EIDs #0x0A000000 - #0x0AFFFFFF)

引导过程

RISC-V芯片的启动流程演变历程:

  1. **Zeroth Stage Boot Loader(ZSBL)**,安装在板载的ROM中,处于M-mode
  2. **First Stage Boot Loader(FSBL)**,brings up PPLs and DDR, 处于M-mode
  3. **Berkeley Boot Loader(BBL)**,adds emulation for soft instructions,处于M-mode
  4. 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)底层初始化:

  1. 判断 hart(Hardware Thread) id
  2. 代码重定位(判断 _load_start_start 函数是否一致)
  3. 清除除保存设备树地址的寄存器的值
  4. 清除bss段
  5. 设置栈指针(预留栈空间)
  6. 读取设备树中的设备信息
  7. 设备树重定位,为U-Boot提供信息
  8. 至此,底层初始化结束,执行sbi_init,进行正式的初始化程序

(2)设备初始化:
 首先判断是通过 S-mode 还是 M-mode 启动

  1. sbi_domain_init 初始化动态加载的镜像的模块
  2. sbi_platform_early_init 平台的早期初始化
  3. sbi_console_init 控制台初始化,从这里开始,就可以使用串口输出了。
  4. sbi_platform_irqchip_init        irq中断初始化
  5. sbi_ipi_init    核间中断初始化
  6. sbi_tlb_init    mmu的tlb表的初始化
  7. sbi_timer_init    timer初始化
  8. sbi_hsm_prepare_next_jump   准备下一级的boot
    (3)二级boot跳转,如加载U-Boot或者Linux内核

ARM架构中的Hypervisor与OpenSBI的对比

在 ARM 架构中,Hypervisor 层承担了虚拟化的作用,承担了如内存管理、设备模拟、设备分配、异常处理、指令捕获、虚拟异常管理、中断控制器管理、调度、上下文切换、内存转换、多个虚拟地址空间管理等非常多的功能。
相比之下,SBI在RISC-V架构中充当了BIOS和在操作系统运行时为上层提供底层抽象的作用,功能较少。不过SBI也在不断发展中,可能在将来SBI会去承担虚拟化的功能。