3. OpenSBI概述

OpenSBI 是 RISC-V 平台上最主流的开源 SBI(Supervisor Binary Interface)实现,运行在 M 模式(Machine Mode),为 S 模式操作系统提供统一的运行时服务。理解 OpenSBI 的项目结构、启动流程、固件类型和核心组件,是进行平台适配工程的第一步。本节从工程视角梳理 OpenSBI 的整体骨架,帮助读者建立“代码在哪里、启动怎么走、固件怎么选、组件怎么协作”的完整认知。

一、项目结构:从目录看职责划分

OpenSBI 源码仓库的目录组织高度模块化,每个目录对应一类明确职责。掌握目录结构,能快速定位平台适配时需要修改的文件。

目录/文件职责说明平台适配关注点
firmware/三种固件类型的入口实现:FW_DYNAMIC、FW_JUMP、FW_PAYLOAD选择并配置固件类型
lib/sbi/SBI 核心服务实现,如 ecall 分发、IPI、定时器、RFENCE一般无需修改
lib/utils/通用工具:设备树解析、串口、IRQ、复位、系统挂起按需扩展驱动
platform/具体平台支持,如 generic、kendryte、sifive、thead新增平台目录与 platform.c
include/公共头文件,含 sbi 接口、平台配置宏定义平台常量
docs/文档,含 firmware、platform、library 说明查阅接口约定
Makefile / Kconfig构建系统与配置项添加平台编译目标

其中 platform/ 是平台适配的核心战场。每个平台通常包含 platform.c、config.mk、objects.mk 以及可选的 Kconfig,用于描述内存布局、时钟频率、串口地址、中断控制器等硬件参数。

二、启动流程:从复位向量到 S 模式交接

OpenSBI 的启动流程可以概括为“冷启动初始化 → 平台初始化 → 核心服务注册 → 跳转下一阶段”。下图展示了从复位到交接给 S 模式的关键路径与组件关系。

OpenSBI 启动流程与核心组件协作 复位向量 _start 保存 a0/a1,设置栈 sbi_init 冷启动/热启动判定 平台初始化 platform_early_init 核心服务注册 sbi_ecall 分发表 固件类型分支(编译期选择) FW_DYNAMIC:由上一级固件传入 next_addr / next_mode FW_JUMP:固定跳转地址;FW_PAYLOAD:内嵌下一阶段镜像 FW_DYNAMIC fw_dynamic_info 结构体 magic / version / next_addr next_mode / options FW_JUMP FW_JUMP_ADDR 固定地址 FW_JUMP_FDT_ADDR 可选 适合 U-Boot SPL 场景 FW_PAYLOAD 内嵌 payload 二进制 FW_PAYLOAD_PATH 指定 适合直接引导内核 最终交接:mret 进入 S 模式,a0=hartid,a1=dtb

启动流程中的关键函数调用链如下:

_start
 └─ sbi_init
     ├─ sbi_platform_early_init   // 平台早期初始化
     ├─ sbi_hart_init             // HART 级初始化
     ├─ sbi_console_init          // 串口控制台
     ├─ sbi_irqchip_init          // 中断控制器
     ├─ sbi_ipi_init              // 核间中断
     ├─ sbi_timer_init            // 定时器
     └─ sbi_hsm_init              // HSM 状态管理
         └─ sbi_hart_switch_mode  // 切换到 S 模式

冷启动时,OpenSBI 完成全部初始化并跳转下一阶段;热启动(secondary hart)则通过 HSM 机制等待主核唤醒,避免重复初始化全局资源。

三、固件类型:三种形态的适用场景

OpenSBI 提供三种固件类型,本质区别在于“下一阶段镜像从哪里来、跳转地址如何确定”。

  • FW_DYNAMIC:最灵活。OpenSBI 不关心下一阶段是谁,由上一级固件(如 U-Boot SPL)在跳转前填充 fw_dynamic_info 结构体,传入 next_addr、next_mode 和 options。适合多级启动链。
  • FW_JUMP:最简单。编译期通过 FW_JUMP_ADDR 指定固定跳转地址,OpenSBI 初始化完成后直接跳转。适合地址固定的场景,如某些 SoC 的 BootROM 直接加载。
  • FW_PAYLOAD:最自包含。将下一阶段镜像(如 Linux 内核、U-Boot)作为 payload 内嵌到 OpenSBI 二进制中,通过 FW_PAYLOAD_PATH 指定。适合单镜像直接引导。
选择固件类型的经验法则:如果启动链中有 U-Boot SPL 且地址不固定,选 FW_DYNAMIC;如果地址固定且追求最小体积,选 FW_JUMP;如果希望一个镜像直接引导内核,选 FW_PAYLOAD。

四、核心组件:SBI 服务与平台抽象

OpenSBI 的核心组件可分为“SBI 服务层”和“平台抽象层”两大部分。

SBI 服务层实现标准 SBI 接口,通过 ecall 指令从 S 模式陷入 M 模式后分发处理。主要服务包括:

  • TIME:定时器设置与读取,对应 sbi_set_timer。
  • IPI:核间中断发送,对应 sbi_send_ipi。
  • RFENCE:远程栅栏,对应 sbi_remote_fence_i 等。
  • HSM:HART 状态管理,对应 sbi_hart_start、sbi_hart_stop。
  • SRST:系统复位,对应 sbi_system_reset。
  • PMU:性能监控单元访问。

平台抽象层通过 struct sbi_platform 描述硬件差异,关键字段包括:

struct sbi_platform {
    const char *name;
    unsigned long features;
    u32 hart_count;
    u32 hart_index2id[];
    const struct sbi_platform_operations *platform_ops;
    const struct sbi_hart_operations *hart_ops;
    const struct sbi_ipi_operations *ipi_ops;
    const struct sbi_timer_operations *timer_ops;
    const struct sbi_console_operations *console_ops;
    const struct sbi_system_reset_operations *reset_ops;
};

平台适配的本质,就是实现这些 operations 结构体中的回调函数,把具体硬件的串口、定时器、中断控制器、复位控制器接入 OpenSBI 的通用框架。

五、常见误区与复盘

误区一:认为 OpenSBI 必须修改源码才能适配新平台。实际上,多数平台只需在 platform/ 下新增目录并实现 operations 回调,核心库无需改动。
误区二:混淆 FW_DYNAMIC 与 FW_JUMP 的地址来源。FW_DYNAMIC 的跳转地址来自运行时传入的结构体,FW_JUMP 的地址来自编译期宏,二者不可混用。
误区三:忽略热启动路径。多核平台上,secondary hart 不应重复执行全局初始化,否则可能导致资源竞争或死锁。务必确认 HSM 与冷热启动判定逻辑正确。

复盘平台适配流程时,建议按“确认固件类型 → 核对内存布局 → 实现平台 operations → 验证串口输出 → 验证定时器与 IPI → 验证多核启动”的顺序逐项排查。每一步都应有明确的日志或现象作为验证依据,避免一次性修改过多变量导致问题难以定位。