OpenSBI三种固件的区别

简介: OpenSBI三种固件的区别

OpenSBI作为RISC-V Linux运行在M模式下的固件,其支持三种不同的平台固件类型,今天聊聊它们的区别。

OpenSBI固件类型

在介绍OpenSBI固件类型前,我们需要知道一点RISC-V Linux的启动过程:

启动流程:ZSBL-->FSBL-->OpenSBI-->u-boot-->Linux

ZSBL和FSBL通常固化在芯片内部,不在本文讨论范围,本文关注OpenSBI。

可以看到,在启动uboot之前,需要先启动OpenSBI,然后再执行uboot/linux。而OpenSBI提供了不同的固件类型来处理不同平台早期启动阶段的差异。

OpenSBI简介

OpenSBI为特定平台提供固件构建,支持不同类型的固件来处理不同平台早期启动阶段之间的差异。所有固件将根据平台特定代码以及OpenSBI通用库代码执行平台硬件的相同初始化过程。支持的固件类型在如何处理平台早期启动阶段传递的参数以及如何处理和执行固件之后的启动阶段方面会有所不同。

上一个引导阶段将通过RISC-V CPU的以下寄存器传递信息:

  • hart id通过a0寄存器传递
  • 通过 a1 寄存器在内存中存储设备树 blob 地址。地址必须与 8 个字节对齐。

OpenSBI 目前支持三种不同类型的固件

  • fw_dynamic固件:带有动态信息的固件
  • fw_jump固件:指定下一引导阶段的跳转地址,不直接包含下一阶段的二进制代码
  • fw_payload固件:包含下一引导阶段有效负载的二进制代码,通常这个有效负载是bootloader或者操作系统镜像

FW_DYNAMIC固件

FW_DYNAMIC固件在运行时从上一个启动阶段获取有关下一个启动阶段的信息,例如引导加载程序或操作系统内核。

  • 上一个启动阶段(即LOADER)通过a2 寄存器将struct fw_dynamic_info的位置传递给FW_DYNAMIC
  • 之前的启动阶段(即LOADER)需要知道struct fw_dynamic_info
struct fw_dynamic_info {
      /** Info magic */
      unsigned long magic;
      /** Info version */
      unsigned long version;
      /** Next booting stage address */
      unsigned long next_addr;
      /** Next booting stage mode */
      unsigned long next_mode;
      /** Options for OpenSBI library */
      unsigned long options;
      unsigned long boot_hart;
  } __packed;

FW_JUMP固件

FW_JUMP固件假定下一个引导阶段的地址固定,但是不直接包含下一阶段的二进制代码,只是告诉OpenSBI,它运行完后需要到哪个地址执行。

例如,OpenSBI执行完后,需要加载kernel,而Kernel的加载地址为0x80200000,那么我们指定0x80200000为需要跳转的地址,OpenSBI执行完毕后,就会跳转到0x80200000处去加载kernel。

具体例子:

OpenSBI执行完后,去执行uboot或者kernel,假设uboot或者kernel在内存中的地址为0x80200000,则编译OpenSBI:

make PLATFORM=generic FW_JUMP_ADDR=0X80200000

在编译OpenSBI时,加入参数FW_JUMP_ADDR,即可指定需要跳转的地址。

注意,由于OpenSBI运行在M模式下,本质上也是一个bootloader,启动时,OpenSBI就会进行一些硬件的初始化操作,加上fw_jump固件可以指定跳转地址,因此对于RISC-V Linux而言,OpenSBI执行完后,可以不执行uboot,直接启动kernel,将uboot去掉,不影响RISC-V Linux正常启动。

在某些内存优化的场景下,可以考虑将uboot去掉,利用fw_jump固件直接启动kernel,从而节省内存。

FW_PAYLOAD固件

FW_PAYLOAD固件直接包含下一引导阶段的二进制代码,下一引导阶段通常是bootloader或os镜像。

不同于FW_JUMP固件的指定地址跳转,FW_PAYLOAD固件是将bootloader或os镜像直接打包进来。

具体例子:

将uboot打包进来,则编译OpenSBI:

make PLATFORM=generic FW_PAYLOAD_PATH=uboot.bin

将Linux kernel打包进来,则编译OpenSBI:

make PLATFORM=generic FW_PAYLOAD_PATH=Image

在编译OpenSBI时,加入参数FW_PAYLOAD_PATH,即可以将下一引导阶段的二进制代码打包进来。相当于将OpenSBI、uboot、kernel合并为一个文件。

实际中,我们用的更多的是FW_PAYLOAD固件,将uboot和OpenSBI一起编译。

配置和编译

OpenSBI的配置选项位于不同平台下的config.mk文件,例如platform/generic/config.mk

# Blobs to build
FW_TEXT_START=0x80000000
FW_DYNAMIC=y
FW_JUMP=y
FW_PAYLOAD=y
ifeq ($(PLATFORM_RISCV_XLEN), 32)
  # This needs to be 4MB aligned for 32-bit system
  FW_JUMP_ADDR=$(shell printf "0x%X" $$(($(FW_TEXT_START) + 0x400000)))
else
  # This needs to be 2MB aligned for 64-bit system
  FW_JUMP_ADDR=$(shell printf "0x%X" $$(($(FW_TEXT_START) + 0x200000)))
endif
FW_JUMP_FDT_ADDR=$(shell printf "0x%X" $$(($(FW_TEXT_START) + 0x2200000)))
ifeq ($(PLATFORM_RISCV_XLEN), 32)
  # This needs to be 4MB aligned for 32-bit system
  FW_PAYLOAD_OFFSET=0x400000
else
  # This needs to be 2MB aligned for 64-bit system
  FW_PAYLOAD_OFFSET=0x200000
endif
FW_PAYLOAD_FDT_ADDR=$(FW_JUMP_FDT_ADDR)

选择编译需要的固件类型:

三种固件,根据自己的需求,在对应的固件类型配置为y:

  • FW_DYNAMIC=y
  • FW_JUMP=y
  • FW_PAYLOAD=y

默认三种固件类型都编译。

FW_TEXT_START

OpenSBI的运行地址,需要把OpenSBI下载到FW_TEXT_START指定的地址才能运行

FW_JUMP_ADDR

FW_JUMP固件的跳转地址,对于RV32需要4M对齐,对于RV64需要2M对齐

FW_PAYLOAD_OFFSET

FW_PAYLOAD固件的偏移地址,对于RV32需要4对齐,对于RV64需要2M对齐

FW_JUMP_FDT_ADDRFW_PAYLOAD_FDT_ADDR

设备树的地址

这里可能有个疑问:为什么跳转地址/偏移地址需要2M对齐或4M对齐?这个跟RISC-V Linux启动时建立的页表有关,需要深入分析才知道。

OpenSBI常用编译选项

选项 说明
FW_FDT_PATH 指定设备树的路径,一起编译进来
FW_PAYLOAD_PATH 指定有效负载的路径,一起编译进来,通常是uboot或者kernel
FW_JUMP_ADDR 指定下一引导阶段的跳转地址,用于FW_JUMP固件
FW_OPTIONS 控制OpenSBI运行时行为,例如是否开启打印,0x0禁止打印,0x1开启打印

更多的信息可以参考OpenSBI官方文档,docs目录下进行了详细的介绍和说明:

不同平台的配置和编译:

https://github.com/riscv-software-src/opensbi/tree/v1.0/docs/platform

不同类型固件的说明:

https://github.com/riscv-software-src/opensbi/tree/v1.0/docs/firmware

总结

学习RISC-V Linux,OpenSBI是必须要知道的,它是RISC-V Linux运行在M模式下的固件,uboot和kernel都运行在S模式,应用程序运行在S模式。

如果钻研RISC-V Linux的内核,那么也会经常和OpenSBI打交道,这就涉及RISC-V Linux和OpenSBI之间的交互。因此OpenSBI有很多可挖掘的地方。

另外,OpenSBI其实是一个裸机代码,也是一个bootloader,相比于uboot,它的代码量比较少,很适合深入分析学习。


相关文章
|
6月前
|
传感器 Windows
(3)将固件加载到已有ArduPilot固件的主板上
(3)将固件加载到已有ArduPilot固件的主板上
54 2
|
6月前
|
Java Linux iOS开发
(4)将固件加载到没有ArduPilot固件的主板上
(4)将固件加载到没有ArduPilot固件的主板上
49 2
|
6月前
|
存储 监控 调度
服务器固件
服务器固件
80 0
USB3.0、3.1、3.2...各版本区别
USB3.0、3.1、3.2...各版本区别
1799 0
USB3.0、3.1、3.2...各版本区别
硬件与固件的区别
硬件与固件的区别
409 0
|
内存技术
stm32实现iap远程固件更新
stm32实现iap远程固件更新
186 0
|
传感器 Windows 内存技术
(4)(4.3) 将固件加载到已有ArduPilot固件的主板上
(4)(4.3) 将固件加载到已有ArduPilot固件的主板上
196 0
|
Java Linux 芯片
(4)(4.4) 将固件加载到没有ArduPilot固件的主板上
(4)(4.4) 将固件加载到没有ArduPilot固件的主板上
305 0
|
算法 物联网 测试技术
开发一个arm固件加载基址定位器
最近入坑iot,涉及很多芯片固件的逆向。但是这些固件很多时候都不是标准二进制格式,也就是说丢进ida,识别不出架构和指令集。架构和指令集可以查芯片的文档,但是加载基址还没法确定,这个靠自己去定位,再配置ida。人工做这个工作太累,而我又是懒狗,所以自动化这一过程不香吗?
|
存储
使用树莓派简单快速的烧录操作系统(含两个方法)
使用树莓派简单快速的烧录操作系统(含两个方法)
599 0