OpenSBI 技术调研报告(v1.1):RISC-V 64 平台的 M 态固件 —— 从固件形态到 ecall 分发的完整机制
OpenSBI 技术调研报告(v1.1):RISC-V 64 平台的 M 态固件 —— 从固件形态到 ecall 分发的完整机制
| 字段 | 内容 |
|---|---|
| 文档类型 | 技术调研报告 |
| 版本 | v1.1 |
| 状态 | 公开资料整理;源码锁定 master 6ad246a1(2026-09-28,v1.10 开发线);未做实机启动与中断路径的完整验证 |
| 密级 | 公开资料 |
| 作者 | Qoder Research |
| 生成日期 | 2026-10-10 |
| 研究截点 | 2026-10-10 |
| 目标读者 | 固件与 OS 移植工程师、板卡 bringup 与验证团队、SoC 平台选型与安全评估人员 |
| 调研问题 | ① 三种固件形态各自如何与上一级交接,a0/a1/a2 的语义边界在哪里;② 冷启动从 _start 到 mret 的固定序列与每一站的源码锚点;③ S 态 ecall 如何经 trap 进入 M 态并分发到扩展,SBI v3.0 的扩展面包含什么;④ 域与 PMP/SMEPMP 保护如何落盘、如何用域 dump 与 JTAG 核对;⑤ HSM 从核启动与固件侧中断设施(PLIC/AIA)的现状与失效边界 |
| 覆盖范围 | OpenSBI master 6ad246a1(v1.10 开发线)的核心机制:fw_base.S 冷/热启动、sbi_init/域/PMP protection 框架、ecall 分发与扩展注册表、HSM 状态机、lib/utils/irqchip 三驱动;RISC-V SBI 规范 v3.0;OpenSBI v1.7–v1.9 发布节奏与平台接纳;Linux 与 U-Boot 侧的消费方式 |
| 排除范围 | DDR 训练算法与未公开 SoC 初始化细节;真机性能、功耗与稳定性实测;安全认证结论;商业工具与采购建议;构建、烧录与量产固化操作指南 |
摘要
- OpenSBI 是 RISC-V SBI v3.0 规范的参考实现,运行于 M 态:对上以
ecall为唯一入口,在a7/a6选择扩展与函数、在a0/a1回写结果;对下以a0=hartid、a1=FDT、a2=交接块接收上一级交棒。三种固件形态fw_flat/fw_jump/fw_dynamic由编译期选择,其中fw_dynamic经struct fw_dynamic_info的 magic/version 校验后才跳转,是 SPL 流程的默认形态。 [S01][S02][S05] - 冷启动是一条可以逐行 grep 的固定序列:
_start→fw_boot_hart()择主(抽签未中者自旋)→ GOT 重定位与清场 →fw_save_info()归档交棒参数 →fw_platform_init()→_scratch_init→sbi_init()→init_coldboot()按固定次序初始化子系统 →sbi_hart_protection_configure()落 PMP →mret交付 S 态;任何一站写错,现场都会留在串口最后一个成功锚点之后。 [S02][S07][S08] ecall分发集中在lib/sbi/sbi_ecall.c的sbi_ecall_handler():按a7在扩展区间表查找、按a6取函数,返回码写a0、输出值写a1(0.1 遗留仅写a0),mepc += 4后继续执行;未注册扩展返回-2,内核侧经 BASE 扩展的 probe 语义(未注册 0、注册 1)判定 HSM 等能力是否可用。 [S05][S06][S11]- 内存保护自 v1.9 起统一到 hart protection 框架:SMEPMP 存在时注册
epmp(rating 200)、否则注册pmp(rating 100),域 region 经sbi_hart_protection_configure()落盘,失败时只留一条可 grep 的错误串;域 dump 形如Domain0 Region00 … M: (F,R,W,X) S/U: (R,W,X),是核对 PMP 的第一手材料。 [S02][S03][S09] - 从核启动与中断设施同样已收敛:HSM 以票据(ticket)与
atomic_cmpxchg状态机保证一次放行一个 hart,中断侧在lib/utils/irqchip收敛为plic/aplic/imsic三驱动;QEMU virt 与真板的行为差异主要集中在 HSM 可用性与 AIA 配置两处。 [S02][S07][S10]
背景与现状
OpenSBI 在 RISC-V 64 启动链中的位置可以一句话概括:它运行在 M 态,向下承接上一级引导器(SPL 或 BootROM)按固定寄存器协议交付的 a0=hartid、a1=FDT、a2=交接块,向上把时钟、IPI、远程 fence、从核管理、系统复位等平台特权服务封装成 S 态唯一可用的请求通道 ecall。S 态内核因此永远不需要直接触碰 M 态 CSR 或平台私有寄存器,跨平台差异被压缩到设备树与固件内部的平台钩子两处。这种「窄接口、厚实现」的形态使它同时成为规范落地的参照与排查启动问题的事实标准入口:上游仓库 riscv-software-src/opensbi 在 2026-10-10 已积累 1546 星,master HEAD 为 6ad246a1(2026-09-28,v1.10 开发线),本文的全部机制结论都锁定到该 commit。图 4 给出版本、规范与平台接纳的时间线。
图 4:OpenSBI 与 SBI 规范时间线 —— 上带四个 OpenSBI 里程碑(v1.7 合规、v1.8 平台接纳、v1.9 特性落地、master 开发线),下带两条规范与生态事件(SBI v3.0 批准、平台伙伴进展)。来源:按 OpenSBI GitHub Releases 与 riscv-non-isa/riscv-sbi-doc 公开发布信息整理 [S02][S04];数据口径:版本日期取 Releases 的发布时间,master 行号与 commit 以 2026-10-10 抓取为准。
见图 4,读图先看两条竖向轴线的错位:规范批准(2025-07-16)发生在 OpenSBI v1.7 发布(2025-06-30)之后十几天,说明参考实现的合规工作先于规范正式稿完成;随后 v1.8 把国内平台 SpacemiT K1 与 ESWIN EIC7700 接纳进上游,v1.9 再把 Zkr 栈保护、Smrnmi NMI 与保护框架推到默认路径。也就是说,2025 年之后 OpenSBI 的演进主线是「先把规范兑现,再把平台收编」,理解这条主线有助于判断某个特性在目标板固件上的可用性。
上游版本节奏与平台现状
版本节奏可以直接从发布说明读出。v1.7(2025-06-30)达成 SBI v3.0 合规,把 TIME/IPI/RFENCE/HSM/SRST 等核心扩展的行为对齐到规范正式稿;v1.8(2025-12-29)与 v1.8.1(2026-01-08)在核心续接的同时接纳了 SpacemiT K1、ESWIN EIC7700 等国内平台;v1.9(2026-07-01)的发布说明列出了本轮最密集的机制更新——用 Zkr 扩展初始化栈保护、用 Smrnmi 扩展支持 NMI、把 PMP 编解码抽成公共辅助、为域切换加入向量与浮点上下文保存恢复、改进 irqchip 框架同时处理有线中断与 MSI、支持定时器事件,并加入 UBSan 与改进的非对齐向量 load/store 模拟;平台侧新增 SiFive 开发平台与 Tenstorrent Atlantis,移除长期无人维护的 kendryte/k210;驱动侧新增 Andes 末级缓存与 ATCSMU 挂起驱动、Altera JTAG UART、MPXY 的 RPMI MM 服务组客户端、SpacemiT I2C 与 P1 PMIC 复位驱动、Litex 复位驱动。 [S02][S04]
平台目录的结构收敛值得单独指出:本轮快照下 platform/ 只剩 generic、nuclei、template 三个目录,绝大多数 SoC 经设备树驱动的 generic 平台接入——fw_platform_init() 在 platform/generic/platform.c 中从 /cpus 生成 hart 索引映射、检查 M 态 IMSIC 等;template 提供新平台脚手架,nuclei 承载该厂商的私有差异。这意味着「平台支持」在上游的形态已经从「一个新目录」变成「一段设备树加少量钩子」,板级差异的审计面随之缩小到设备树与平台钩子两处。中断侧同样收敛:lib/utils/irqchip/ 下是 plic.c(经典平台级中断控制器)、aplic.c 与 imsic.c(AIA 双件套:APLIC 处理有线中断、IMSIC 处理 MSI)以及对应的 fdt_irqchip_*.c 适配层,固件按下发设备树选择驱动,S 态内核看到的依然是统一的 ecall 接口。 [S02][S03][S10]
规范侧,RISC-V SBI v3.0 于 2025-07-16 正式发布,仓库 riscv-non-isa/riscv-sbi-doc 在 2026-07-21 仍有提交,说明 v3.0 之后的维护线已经开启;v3.0 把 HSM、SRST、PMU、CPPC、DBTR 等此前分散在多个草案中的扩展收进正式文本,并新增 FWFT(固件特性,本快照实现 SET/GET 两个函数)、MPXY(消息代理,GET_SHMEM_SIZE/SET_SHMEM 等八个函数)、SSE(软件事件,READ_ATTR/WRITE_ATTR/REGISTER 等十个函数)等平台服务类扩展。规范与实现的这种同步节奏给工程带来一个直接推论:判断某块板卡固件支持什么能力,最稳妥的方法是查该固件基于的 OpenSBI 版本与提交,再用 BASE 扩展的 probe 在运行时逐项确认,而不是依赖板卡手册或二手资料。 [S01][S04][S05]
版本与平台里程碑的对照关系汇总如下,行号与版本锚点均可在来源台账中回查。
| 里程碑 | 时间 | 关键变化 | 对排查的意义 | 关联来源 |
|---|---|---|---|---|
| SBI 规范 v3.0 | 2025-07-16 | 扩展面收进正式文本,新增 FWFT/MPXY/SSE 等 | 固件与内核的共同基线,扩展探测以 v3.0 语义为准 | [S01] |
| OpenSBI v1.7 | 2025-06-30 | 达成 SBI v3.0 合规 | v1.7 之前固件不保证核心扩展行为一致 | [S04] |
| OpenSBI v1.8 / v1.8.1 | 2025-12-29 / 2026-01-08 | 接纳 SpacemiT K1、ESWIN EIC7700 等平台 | 国内板卡上游支持以此为分界 | [S04] |
| OpenSBI v1.9 | 2026-07-01 | Zkr 栈保护、Smrnmi NMI、保护框架、irqchip 改进、向量/浮点上下文 | 「域切换保存上下文」与 MSI 路径在 v1.9 才完整 | [S02][S04] |
master 6ad246a1 | 2026-09-28 | v1.10 开发线,平台驱动为主 | 本文全部行号锚点对应此 commit | [S02] |
技术详解:从 fw_dynamic 交付到 ecall 分发
上一章停在版本与平台层面的现状,本章把机制拆成四段:先看三种固件形态与上一级的交棒约定,再逐步跟一遍冷启动的固定序列与域、PMP 落盘,然后进入 ecall 的分发路径与 SBI v3.0 的扩展面,最后收束到 HSM 多核启动与固件侧的中断设施。每一段都给出可 grep 的源码锚点、失败后的现场特征与核对方法;全部行号锁定上游 master 6ad246a1,引用编号在参考来源台账中定义。
3.1 固件形态与启动链交棒
固件目录在 firmware/ 下只保留一份公共骨架与三个变体汇编文件:公共骨架是 firmware/fw_base.S,三个变体分别是 firmware/fw_dynamic.S、firmware/fw_jump.S、firmware/fw_payload.S。所谓 fw_flat 并不是独立的汇编文件,而是 fw_payload 以 FW_PAYLOAD_OFFSET(非 none)配置出的「平铺」形态——payload 直接跟随固件二进制排布并一起烧录;因此上游实现上只有三种形态的上游实现路径,而板卡手册里常见的「flat/jump/dynamic 三选一」说的正是 Kconfig 对这组文件的编译选择。对上一级引导器(BootROM 或 SPL)而言,接口只有一条:a0 = hartid、a1 = 设备树物理地址;a2 仅在 dynamic 形态下有定义,指向 struct fw_dynamic_info。SBI 规范 v3.0 约定的是 S 态可见的接口,这一组 M 态交棒约定是 OpenSBI 的实现约定,规范文本并不覆盖 [S01][S02]。
dynamic 形态的交接块定义在 include/sbi/fw_dynamic.h:struct fw_dynamic_info 共六个字段(L49-76),magic/version/next_addr/next_mode/options/boot_hart 分别固定在 __SIZEOF_LONG__ 的 0 至 5 倍偏移(L18-28 的偏移宏与 L82-87 的 assert_member_offset 静态断言锁定),magic 为字符串 'OSBI' 的十六进制 0x4942534f(L31),版本上限 FW_DYNAMIC_INFO_VERSION_MAX 取 0x2(L34-35);next_addr/next_mode/options 自版本 1 起有效、boot_hart 自版本 2 起有效(L21-28 注释),next_mode 的默认值在 firmware/fw_dynamic.S 的 .data 中给成 PRV_S。交给 SPL 的打包侧(例如 U-Boot SPL 的 FIT 装载)只需按偏移填好这块结构,固件就能在重定位之前完成校验并确定下一跳地址 [S02][S05]。
图 1:OpenSBI 固件形态与启动链交棒 —— 上行为 SPL/BootROM → OpenSBI → U-Boot proper → Linux 的控制流顺序,虚线回路表示 S 态经 ecall 回到 M 态固件;下方为编译期三选的 fw_flat、fw_jump、fw_dynamic 与其交接块。来源:OpenSBI 源码与官方固件文档 [S02][S03];数据口径:寄存器与形态约定取自 firmware/fw_*.S 与 include/sbi/fw_dynamic.h(master 6ad246a1)。
见图 1,读图先看两条线:实线是控制流的转交——SPL 写 a0/a1 后跳入固件头,固件完成冷启动初始化后再把控制权交给 U-Boot proper 或内核的 S 态入口;虚线回路是运行期的反向请求——S 态通过 ecall 在 a7/a6 里点名服务。下方三形态框对应编译期选择:fw_flat 把 payload 编进镜像、fw_jump 用常量地址跳转、fw_dynamic 经交接块校验后跳转;只有 dynamic 会把 a2 当结构体指针解释,这也解释了为什么同一份 u-boot.itb 里 SPL 传给 OpenSBI 的 a2 指向一个随镜像搬移的交接块。
三类形态的取值函数分工不同,校验强度也不同。dynamic 形态的 fw_boot_hart()(firmware/fw_dynamic.S L21-37)在返回主 hart 号之前做两项健全性校验:magic 不等即跳 _start_hang,version 大于上限同样跳 _start_hang;version 小于 2 时返回 -1,交由抽签路径决定主 hart。fw_save_info()(同文件 L34-72)把 next_arg1(直接取 a1)与 next_addr/next_mode/options(自结构体偏移 2/3/4 读入)存进 .data,版本为 2 时再补收 boot_hart;随后 _scratch_init 阶段的 fw_next_arg1() 等取值函数只读这些静态槽位。jump 形态在 firmware/fw_jump.S 中以构建常量 FW_JUMP_ADDR 与 FW_JUMP_FDT_ADDR 取代交接块;flat 形态由 firmware/fw_payload.S 提供 fw_next_addr() 返回 payload_bin 符号地址(L60-67),必要时用 FW_PAYLOAD_FDT_ADDR 覆盖 a1(L46-58)。这一层的失效边界非常直白:magic 或 version 不匹配时,固件在打印任何字节之前就停进 _start_hang 的自旋(firmware/fw_base.S L521-524),现场表现为零字节串口输出——这正是「上电无输出」类问题在固件侧的第一嫌疑点,值得先用静态自查排除。
/* firmware/fw_dynamic.S: fw_boot_hart() 的两项健全性校验(逐字摘录) */
li a1, FW_DYNAMIC_INFO_MAGIC_VALUE
REG_L a0, FW_DYNAMIC_INFO_MAGIC_OFFSET(a2)
bne a0, a1, _start_hang
li a1, FW_DYNAMIC_INFO_VERSION_MAX
REG_L a0, FW_DYNAMIC_INFO_VERSION_OFFSET(a2)
bgt a0, a1, _start_hang
本快照的 _start 还新增了一层 128 字节固件头,值得单独交代,因为它改变了对「镜像头」的静态核对方法。头部布局在 firmware/fw_base.S L60-89 的注释中逐字段给出:
Offset Size Field
0x00 4 jump to _start_real
0x04 4 magic ('OSBI')
0x08 4 header version
0x0c 4 firmware XLEN
0x10 4 firmware size (_fw_end - _fw_start)
0x14 4 flags (FW_HEADER_FLAGS_*)
0x18 8 override value for a1
0x28 8 override value for a2
头部的跳转指令 _fw_header_jump 用 norvc 与 norelax 两个指示保证始终是 4 字节编码(L79-88 注释),从而后续字段偏移对 RV32/RV64 都恒定。_start_real(L111)读取 _fw_header_flags 决定是否覆盖寄存器:置了 FW_HEADER_FLAGS_OVERRIDE_A0 就用 csrr a0, CSR_MHARTID 取本 hart 号,置了 OVERRIDE_A1/A2 则从头部 0x18/0x28 偏移装载替代值(L121-147)。这条路径服务的是「不能在寄存器里传参、但能改写镜像」的上一级(如部分 BootROM),对以寄存器交棒的 SPL/U-Boot 流程透明无感。
把冷启动前的交棒动作串起来,主 hart 的推进顺序是:
- 上一级按约定装配
a0/a1/a2并跳入镜像首地址,_start(firmware/fw_base.SL58)落在固件头内并由头部跳转进入_start_real; _start_real按 flags 可选覆盖 a0/a1/a2 之后执行call fw_boot_hart(L145)取主 hart 号,返回非 -1 则与mhartid比较、不中即转_wait_for_boot_hart(L401);返回 -1 的形态用amoswap.w(或lr/sc回退)在_boot_lottery上原子抽签(L157-166);- 主 hart 依次完成 GOT 重定位(按
.rela.dyn中的R_RISCV_RELATIVE项修正,L170-189)、寄存器清场(call _reset_regs,L194)、BSS 清零(L199)与临时 trap 出口设置(mtvec = _start_hang,L203),并清mstatus.MDT避免双重异常进入 critical error(L211); call fw_save_info(L223)归档 next_* 系列,call fw_platform_init(L237)给平台钩子改写 a1 的机会——platform/generic正是在此从设备树/cpus节点生成 hart 索引映射;_scratch_init(L273-347)为每个 hart 建立 scratch:fw_start/fw_size/RW 区偏移/heap 范围/next_arg1/next_addr/next_mode/_start_warm地址/platform 地址/_hartid_to_scratch地址/options/hartindex 全部落盘;构建期可选变量FW_OPTIONS按位或写入 options 字段,bit0 即SBI_SCRATCH_NO_BOOT_PRINTS = (1 << 0),make FW_OPTIONS=0x1即关闭固件启动打印(include/sbi/sbi_scratch.h);- 若 next_arg1 给出不同的非零地址,FDT 整块拷贝到新位置(L355-391);随后主 hart 写
_boot_status = BOOT_STATUS_BOOT_HART_DONE(L394-398)并跳_start_warm; - 等待中的从核轮询到该标志后同样落入
_start_warm(L411),两条路径在 L414 的寄存器清场与 L462 的mscratch建立处合流,最后统一call sbi_init(L479)进入 C 世界。
验证这一层不需要任何自定义工具:串口零输出时先静态核对镜像头 magic 与交接块两项校验;能连 JTAG 时读 mepc,若落在 _start_hang 附近即可判定为交棒校验失败或从核 hartindex 越界(L451 的 bge s6, s7, _start_hang)。实测的顺序建议按「镜像头 → 交接块校验 → scratch 落盘 → sbi_init 入口」四级推进,这个次序与图 1 上方控制流一致 [S02][S08]。
三种形态的选择是一场固化的权衡。flat 把固件与 payload 编成单一镜像、烧录路径最短,代价是任一组件升级都要重编整个固件;jump 用编译期常量地址跳转,交接开销最小,但换内核布局就要重编;dynamic 以一次结构体读取的微小开销把装载器与固件解耦,代价是交接块必须随镜像搬移且被两侧共同维护——U-Boot SPL、QEMU 与多数发行版镜像选择它,正是因为这点成本换来的组装灵活性更高。版本 2 的交接块还允许装载器指定 boot_hart,取代抽签决定主 hart,为确定性启动测试(固定主 hart 复现时序问题)提供了一个零成本的开关 [S02][S04]。
3.2 冷启动序列与域、PMP 落盘
_start_warm 的 call sbi_init(firmware/fw_base.S L479)把控制权交给 sbi_init()(lib/sbi/sbi_init.c L579)。它在入口用 coldboot_lottery 与 sbi_hsm_hart_wait() 的组合判定本 hart 的角色:冷启动路径进 init_coldboot()(L224),热启动路径进 init_warmboot()(L547),两者最终都由 sbi_hsm_hart_start_finish()(同文件 L446;定义在 lib/sbi/sbi_hsm.c L139)把 hart 送进 S 态。init_coldboot() 的初始化次序不是风格问题,而是被注释逐条约束的硬性依赖,前三条尤其严格:sbi_scratch_init() 必须第一、sbi_heap_init() 必须第二、sbi_domain_init() 必须第三(L230-243 的三段 Note: 注释),随后才允许从核相关步骤介入。
图 2:OpenSBI 冷启动固定序列与从核等待路径 —— 左列为 boot hart 从 _start 到交付 S 态的六站及对应行号,右列为其余 hart 的 _wait_for_boot_hart 轮询与热启动入口;两列在 _start_warm 合流。来源:OpenSBI firmware/fw_base.S、lib/sbi/sbi_init.c、lib/sbi/sbi_hsm.c 逐行核对 [S02][S07][S08];数据口径:行号对应 master 6ad246a1,序列以 init_coldboot() 内的实际调用次序为准。
见图 2,读图先看左右两列的合流点:左列六站是 boot hart 的推进主线,右列三节点是从核的等待与热启动支线,两列在 _start_warm 处合流后统一调用 sbi_init(),再由冷启动抽签分流。第④站开始进入 C 世界,第⑤站的两次落盘——sbi_domain_init() 建域、sbi_hart_protection_configure() 写保护——共同决定内核能看到的物理地址视野;标注中给出的行号即本节的核对锚点。
冷启动中段的固定次序如下(括号内为 lib/sbi/sbi_init.c 行号):
sbi_scratch_init()→sbi_heap_init()→sbi_domain_init()(L231/L236/L241,前三名硬约束)→ entry_count 偏移分配;sbi_hsm_init()(L253)与wake_coldboot_harts()(L266):注释解释为何在此放行从核——从核进入热路径时自己会做 HSM 初始化,不必在wait_for_coldboot()里空转消耗总线;sbi_hart_init()(L268)之后,若设备树声明 Zkr 扩展则从CSR_SEED按 16 位块收取熵并刷新__stack_chk_guard(L273-307,带CONFIG_ZKR_POLL_BUDGET尝试预算);sbi_trap_init()→sbi_timer_init()→sbi_platform_early_init()→sbi_pmu_init()→sbi_dbtr_init()→ 打印 banner →sbi_double_trap_init()(L309-331);sbi_irqchip_init()→sbi_ipi_init()→sbi_tlb_init()→sbi_fwft_init()→sbi_mpxy_init()(L333-361);sbi_domain_finalize()(L373,注释:必须在 HSM 初始化之后、PMP 配置之前,保证用正确的域落盘)→sbi_platform_final_init()(L381,注释:必须在域定稿之后,FDT fixup 才能看到正确域归属)→sbi_sse_init()(L395)→sbi_ecall_init()(L407,注释:必须在平台终初始化之后,保证设备都已注册);sbi_boot_print_general/domains/hart打印(L412-416)、run_all_tests();sbi_domain_startup()(L425)启动非 root 域的 boot hart;最后才执行sbi_hart_protection_configure()(L436)——紧随其后的注释点明原因:若检测到 SMEPMP,M 态对 S/U 空间的访问会被立即撤回,因此保护配置必须是最后一步(L430-435);sbi_hsm_hart_start_finish()(L446)把冷 hart 送入 S 态,冷启动主线到此结束。
保护落盘由 v1.9 引入的 hart protection 框架统一调度。注册侧在 lib/sbi/sbi_hart_pmp.c 的 sbi_hart_pmp_init():PMP 条目数非零时,有 SMEPMP 就注册 epmp_protection(name 为 epmp、rating 200、configure 指向 sbi_hart_smepmp_configure()),否则注册 pmp_protection(rating 100、sbi_hart_oldpmp_configure()),两者都声明为 SBI_HART_PROTECTION_TYPE_MEMORY(L425-460)。框架侧 sbi_hart_protection_register()(lib/sbi/sbi_hart_protection.c L53)按 rating 从高到低插入链表,__hart_memory_protection_best()(L15)返回首个 MEMORY 类实现;sbi_hart_protection_configure()(L108)遍历链表时只让第一个 MEMORY 类真正落盘——这就是「注册两个、只落一个」的机制:SMEPMP 可用走 epmp,否则退回 pmp。落盘失败现场固定在两个错误串:区域越界输出 Can not configure pmp for domain %s because memory region address 0x%lx or size 0x%lx is not in range.,条目不足输出 error: insufficient PMP entries(lib/sbi/sbi_hart_pmp.c L215-227、L396-401);上层统一收敛为 hart protection configure failed (error %d)。启动打印中 Platform HART Protection : %s 一行直接来自 sbi_hart_protection_get_str()(L27-51)的逗号拼接,空链表打印 none——串口上看到 epmp 还是 pmp,即可判定该板固件实际走了哪条保护路径。
核对域到 PMP 落盘的第一手材料是域 dump。sbi_domain_dump()(lib/sbi/sbi_domain.c L534)逐域输出 Name/Boot HART/HARTs/Region/Next Address/Next Arg1/Next Mode/SysReset/SysSuspend 各节;每个 Region 行左侧给出地址区间,右侧分两段权限视图:M: 渲染 M 态权限(I/F/R/W/X 按位拼装为括号列表),S/U: 渲染低特权级权限(R/W/X)(L556-593)。格式骨架如下,占位符按源码格式串保留,具体值随平台设备树与域配置变化:
Domain0 Name boot: <name>
Domain0 Boot HART boot: <hartid>
Domain0 HARTs boot: <hartid 列表>
Domain0 Region00 boot: 0x<base>-0x<end> M: (<I,F,R,W,X 组合>) S/U: (<R,W,X 组合>)
Domain0 Next Address boot: 0x<next_addr>
这一层的失效边界有三处需要记住。其一,区域越界与条目不足只打印错误串并继续执行,不会让固件停住——错误不是致命的,系统可能照常进入 S 态,直到内核首次触碰越界地址才以访问异常的形式暴露,因此「域 dump 看起来对」不等于「PMP 落盘成功」。其二,SMEPMP 落盘后 M 态自身对 S/U 空间的访问被撤回,保护配置必须是冷启动的最后一步;任何在函数之后追加读写低特权级内存的代码都会异常。其三,域 dump 的权限字母是域数据结构 flags 的渲染值,而非 PMP 硬件的读回值,两者之间还隔着 sbi_hart_pmp_set() 的编码步骤(NAPOT 粒度与 pmp_log2gran 能力检查参与其中,越界检查在 L208-218)。验证条件因此落在双向核对上:先记录串口域 dump 的区间与两段权限,再用 JTAG 读本 hart 的 pmpcfg0-pmpcfg3 与 pmpaddr0-pmpaddr15 逐条比对;两者不一致说明落盘路径被平台钩子改写——框架对平台侧的实现接口只有 sbi_platform_pmp_set() 一处——属于平台代码而非框架逻辑。成本与权衡方面,protection 框架把「什么保护机制生效」从编译期宏改为运行期链表决策:多出一层链表遍历与一次字符串拼接的启动开销,换来的是同一份二进制在 SMEPMP 与非 SMEPMP 平台上的行为可解释性——串口的 HART Protection 行与域 dump 共同构成两个可复现的验收动作,而代价是排查者必须同时具备「域 flags 语义」与「PMP 硬件编码」两层知识,这也是本文把双向核对写成固定动作的原因 [S02][S03][S09]。
3.3 ecall 分发与 SBI v3.0 扩展面
S 态的每一次请求都从一条 ecall 指令开始。trap 入口 _trap_handler 把现场打包成 sbi_trap_context 后进入 sbi_trap_handler()(lib/sbi/sbi_trap.c),该函数在 cause 分发中把 CAUSE_SUPERVISOR_ECALL 与 CAUSE_MACHINE_ECALL 两条分支合并到同一处理路径(L343-346),统一调用 sbi_ecall_handler()。这条合并设计意味着 M 态固件自身几乎不使用 M 态 ecall 通道(固件内部直接函数调用),S 态 ecall 是这条路径的绝对主体。SBI 规范 v3.0 把调用约定写死在寄存器上:扩展 ID 在 a7、函数 ID 在 a6,返回错误码在 a0、输出值在 a1(0.1 遗留扩展除外)——本节的实现细节与规范文本逐条对应 [S01][S02]。
把整条链路压成一行可复述的调用链:ecall 指令 → _trap_handler 保存现场 → sbi_trap_handler() 按 cause 分流 → sbi_ecall_handler() 取 a7/a6 → sbi_ecall_find_extension() 命中区间表 → ext->handle() 执行扩展逻辑 → mepc += 4 并回写 a0/a1 → sret 返回 S 态。后面所有细节都挂在这七站上。
图 3:SBI ecall 分发链路与扩展注册表 —— 左侧七步为 S 态 ecall 从 m_trap 到 sret 的完整路径,右侧为扩展区间注册表与 EID 清单,底部为 BASE probe 的判定语义。来源:OpenSBI lib/sbi/sbi_ecall.c、lib/sbi/sbi_trap.c 与 include/sbi/sbi_ecall_interface.h 逐行核对 [S02][S05];数据口径:行号与 EID 数值对应 master 6ad246a1,注册表区间语义以 sbi_ecall_register_extension() 的校验逻辑为准。
见图 3,读图先看左侧脊柱的第③④步:sbi_ecall_handler() 拿到现场后只做两件事——按 a7 查扩展、按 a6 取函数;第④步 sbi_ecall_find_extension() 是整条链路唯一的分发决策点,右侧注册表就是它的查找表。第⑥步回写阶段有两个容易忽视的细节:mepc += 4 跳过 ecall 指令本身,以及 0.1 遗留扩展只回写 a0——这就是老内核与新扩展行为差异的根源。底部 probe 注解给出内核运行时探测的判定规则,它是「无 HSM 固件如何被识别」的答案。
分发的实现全部压缩在 sbi_ecall_handler()(lib/sbi/sbi_ecall.c L122-167)中,逐段可读:
/* 摘录:lib/sbi/sbi_ecall.c,行号为原文位置 */
unsigned long extension_id = regs->a7; /* L127 */
unsigned long func_id = regs->a6; /* L128 */
ext = sbi_ecall_find_extension(extension_id); /* L132:区间表查找 */
if (ext && ext->handle) {
ret = ext->handle(extension_id, func_id, regs, &out); /* L134 */
if (extension_id >= SBI_EXT_0_1_SET_TIMER &&
extension_id <= SBI_EXT_0_1_SHUTDOWN)
is_0_1_spec = 1; /* L135-137:遗留区间标记 */
} else {
ret = SBI_ENOTSUPP; /* L139:未注册返回 -2 */
}
回写阶段再补两步兜底:其一是错误码校验窗口,扩展返回的错误码必须落在 SBI_LAST_ERR(即 SBI_ERR_DENIED_LOCKED,-14)到 0 之间,唯一豁免是遗留扩展 SBI_EXT_0_1_CONSOLE_GETCHAR 可以返回正数字符值,越界时固件打印 Invalid error %d for ext=0x%lx func=0x%lx 并把 ret 压成 SBI_ERR_FAILED(-1)(L142-150);其二是 out.skip_regs_update 逃生口——置位时整段回写被跳过,mepc、a0、a1 保持请求方现场不动,供需要保留执行上下文语义的扩展使用(L142)。正常路径最后三行是固定的:regs->mepc += 4、regs->a0 = ret、非 0.1 扩展再写 regs->a1 = out.value(L160-163)。扩展注册侧在 sbi_ecall_register_extension()(L82-101)设了三道校验:区间倒置(extid_end < extid_start)或缺少 handle 直接 SBI_EINVAL,与已有扩展区间重叠同样 SBI_EINVAL,通过后按加入顺序挂在链表尾部;sbi_ecall_init()(L169-183)在冷启动第 6 站被调用,遍历 sbi_ecall_exts[] 数组让每个扩展完成注册。
扩展面本身可以按 EID 一张表读完。EID 的取值刻意做成大写 ASCII 助记:0x54494D45 拼出 TIME、0x48534D 拼出 HSM、0x53525354 拼出 SRST,个别为压缩形式(RFENCE 记作 RFNC、IPI 记作小写 s 开头的 sPI),这一规律在抓现场日志时可直接肉眼识别:
| EID(a7) | 名称 | 规范状态 | 语义要点 |
|---|---|---|---|
| 0x0–0x8 | 遗留 0.1 扩展 | 遗留 | SET_TIMER/CONSOLE_PUTCHAR/CONSOLE_GETCHAR/CLEAR_IPI/SEND_IPI/REMOTE_FENCE_I/REMOTE_SFENCE_VMA/REMOTE_SFENCE_VMA_ASID/SHUTDOWN;仅回写 a0 |
| 0x10 | BASE | 核心 | 版本/实现 ID 查询与 PROBE_EXT(FID 0x3) |
| 0x54494D45 | TIME | 核心 | 定时器事件设置 |
| 0x735049 | IPI | 核心 | 核间中断发送 |
| 0x52464E43 | RFENCE | 核心 | 远程 fence.i / sfence.vma |
| 0x48534D | HSM | 核心 | 从核启停与状态查询(见 3.4 节) |
| 0x53525354 | SRST | 核心 | 系统复位与关机 |
| 0x504D55 | PMU | 性能 | 硬件计数器读写 |
| 0x4442434E | DBCN | 调试 | 调试控制台读写 |
| 0x53555350 | SUSP | 电源 | 系统挂起 |
| 0x43505043 | CPPC | 性能 | 协作处理器性能控制 |
| 0x44425452 | DBTR | 调试 | 调试触发器 |
| 0x535345 | SSE | 事件 | 软件事件注入,10 个函数(READ_ATTR 至 HART_MASK) |
| 0x46574654 | FWFT | 固件 | 固件特性位读写,SET/GET 两个函数 |
| 0x4D505859 | MPXY | 消息 | 消息代理 IPC,8 个函数(GET_SHMEM_SIZE 至 GET_NOTIFICATION_EVENTS) |
表中的函数数量逐项来自 include/sbi/sbi_ecall_interface.h 与本快照各 sbi_ecall_*.c 实现文件的 switch 分支核对:FWFT 两分支、SSE 十分支、MPXY 八分支 [S02][S05]。probe 的判定逻辑在 sbi_ecall_base_handler() 中占一个 case(sbi_ecall_base.c L65),落到 sbi_ecall_base_probe()(L18-33):待查 EID 不在注册链表返回 0,在则优先调用扩展自带的 probe 回调、否则返回 1。这个「0/1」协议是内核侧能力探测的支点:Linux 在启动早期用 SBI_EXT_BASE + PROBE_EXT 判定 HSM 等扩展是否存在,决定从核启动走 SBI 路径还是退回 spin-table 路径 [S11]。
失效边界集中在一处认知误区:未注册扩展返回的 -2(SBI_ERR_NOT_SUPPORTED)不是故障,而是规范行为——内核会按下层能力缺失退化(例如无 HSM 时改用 spin-table)。真正需要警惕的是错误码校验兜底的掩盖效应:扩展本意返回的异常值会被压成 -1 并留下 Invalid error 打印,排查时应以该打印为准而不是以内核看到的 -1 为准。回写侧还有一个隐含前提:mepc += 4 假设发起调用的 ecall 指令长 4 字节——该假设由 ISA 保证(压缩指令集中不存在 2 字节 ecall),但若在 S 态以模拟方式注入 ecall 请求(如测试桩),必须自己保证该前提。验证条件建议做成可复述的三步:先在启动日志的 Platform Extensions 行核对已注册扩展的逗号清单(sbi_ecall_get_extensions_str() 拼接、空则 none,lib/sbi/sbi_ecall.c L57-80);再用 PROBE_EXT 对目标 EID 做运行时确认;最后用 GET_SPEC_VERSION 读取规范版本——本快照返回 0x03000000(主版本 3 左移 24 位,include/sbi/sbi_ecall.h L16-17),与 SBI v3.0 对齐。成本与权衡方面,分发的全部开销是一次链表线性查找加两次寄存器写回;扩展数在十几项量级时该查找完全可忽略,代价几乎全部转移到语义侧——扩展区间表、0.1 特例、错误码窗口三件事都必须同时记住,才能解释「同一个负值在 0.1 与 3.0 扩展上为什么表现不同」 [S01][S02][S06]。
3.4 HSM 多核启动与固件侧中断设施
从核世界的一切都由 HSM 扩展串起来。服务面只有四个函数:HART_START(0x0,参数 hartid/start_addr/opaque 分别装在 a0/a1/a2)、HART_STOP(0x1)、HART_GET_STATUS(0x2,状态值经 out.value 回写到 a1)、HART_SUSPEND(0x3)(lib/sbi/sbi_ecall_hsm.c L29-49;include/sbi/sbi_ecall_interface.h L67-70)。状态枚举共七个值,定义在同一头文件 L72-78:
| 状态 | 值 | 进入路径 | 退出路径 |
|---|---|---|---|
STARTED | 0x0 | sbi_hsm_hart_start_finish() 完成交付;复位后的初始态 | HART_STOP / HART_SUSPEND |
STOPPED | 0x1 | 停止完成或启动失败回滚 | HART_START 受理(cmpxchg 换出) |
START_PENDING | 0x2 | HART_START 受理并在目标 scratch 写入 next_* 之后 | start_finish → STARTED;唤醒失败回滚 → STOPPED |
STOP_PENDING | 0x3 | HART_STOP 受理 | 停止收尾 → STOPPED |
SUSPENDED | 0x4 | HART_SUSPEND 直接转换(保留与非保留均走此列) | HART_SUSPEND(恢复语义)→ RESUME_PENDING |
SUSPEND_PENDING | 0x5 | 本快照 lib/sbi 路径未见使用(仅头文件定义) | — |
RESUME_PENDING | 0x6 | 恢复请求受理 | resume_finish → STARTED |
状态机的实现要点是「一次只放行一个请求」:每个目标 hart 的 HSM 数据里有一个 atomic_t start_ticket(lib/sbi/sbi_hsm.c L52),hsm_start_ticket_acquire() 用 atomic_cmpxchg(&hdata->start_ticket, 0, 1) 抢占(L95-99),hsm_start_ticket_release() 以 atomic_write 归零(L106-109)——并发的 START/STOP 请求拿不到票据即被拒。主路径 sbi_hsm_hart_start()(L307-377)的执行序列如下:
- 校验三件事:
smode只允许PRV_S或PRV_U、目标 hart 必须属于本次调用所属的域、saddr必须通过域的可执行地址检查(不通过分别返回SBI_EINVAL与SBI_EINVALID_ADDR); - 抢占目标 hart 的启动票据,然后把
next_arg1/next_addr/next_mode写进目标 hart 的 scratch; - 用
atomic_cmpxchg(STOPPED → START_PENDING)发布状态——源码注释明确该 cmpxchg 的隐式屏障保证「先写 scratch、后见状态」(L341-348); - 依据返回值区分错误:已是
STARTED返回SBI_EALREADY,处于其他过渡态返回SBI_EINVAL; - 唤醒路径二选一:平台 HSM 设备支持热插拔或二级启动时调
hsm_device_hart_start(hartid, warmboot_addr),否则用sbi_ipi_raw_send(hartindex, true)发 IPI 把目标 hart 从wfi中叫醒(L367); - 唤醒失败则把状态回滚为
STOPPED并释放票据;成功路径的收尾由目标 hart 侧的sbi_hsm_hart_start_finish()(L139-158)完成:cmpxchg(START_PENDING → STARTED)失败即挂起,随后释放票据并调用sbi_hart_switch_mode(hartid, next_arg1, next_addr, next_mode, false)完成 mret 交付。
目标 hart 在热启动路径上的等待由 sbi_hsm_hart_wait()(L160-194)实现:保存 MIE,置位 MIP_MSIP | MIP_MEIP 以接收 IPI,循环检查 atomic_read(&hdata->state) != SBI_HSM_STATE_START_PENDING——循环体内若平台提供热插拔设备则先归还 entry 计数并停核省电,然后 wfi();状态就绪后恢复 MIE。把三步连起来看就是一次完整的从核上线:
发起侧: HART_START → 票据 → 写 scratch → STOPPED→START_PENDING → IPI
目标侧: wfi 循环 → 见 START_PENDING → (冷路径: init_coldboot / 热路径: init_warmboot)
收尾侧: start_finish → START_PENDING→STARTED → 释放票据 → mret 进 S 态
固件侧的中断设施在 v1.9 之后同样完成收敛:lib/utils/irqchip/ 目录下是 plic.c(经典 PLIC 有线中断)、aplic.c 与 imsic.c(AIA 双件套:APLIC 管有线、IMSIC 管 MSI)、fdt_irqchip.c 与 fdt_irqchip_plic.c/fdt_irqchip_aplic.c/fdt_irqchip_imsic.c 适配层(按设备树节点发现驱动,驱动表静态注册在 fdt_irqchip_drivers.carray)。统一框架在 lib/sbi/sbi_irqchip.c:设备经 sbi_irqchip_add_device()(L508)注册、sbi_irqchip_init()(L548)在冷启动第 5 站初始化;trap 时的入口是 sbi_irqchip_process()(L63),它按设备找到 hwirq 后转 sbi_irqchip_process_hwirq()(L74)再调用注册的 handler(sbi_irqchip_register_handler() L417,保留号走 sbi_irqchip_register_reserved() L431);MSI 语义单独成对——sbi_irqchip_register_msi()(L375)登记 MSI 号段、sbi_irqchip_write_msi()(L213)执行写入,这正是 IMSIC 注入路径与 PLIC 时代最大的结构差异;亲和性设置走 sbi_irqchip_set_affinity()(L248)。核对驱动是否解析成功,第一手材料是启动打印的四行设备名:Platform IPI Device、Platform Timer Device @ <freq>Hz、Platform Console Device、Platform HSM Device(lib/sbi/sbi_init.c L98-110),任一行为 --- 即表示对应设备树节点没有被驱动认领。
这一层的失效边界都有清晰的现场特征。其一,票据机制使并发请求互斥:同一次启动中对同一 hart 重入 START/STOP,第二次会直接拿到 SBI_EINVAL——脚本批量拉起从核时出现的「偶发失败」多半是测试框架自己的并发问题而非固件缺陷。其二,中断目标的合法性被显式过滤:sbi_hsm_hart_interruptible_mask()(L117-137)只保留处于 STARTED、SUSPENDED、RESUME_PENDING 的 hart,给未上线 hart 发 IPI/fence 会被静默剔除——表现为发送方成功返回、接收方永不见事件,是「fence 等不到导致挂死」类问题的固定嫌疑链。其三,平台 HSM 设备缺失时从核唤醒完全依赖 IPI 加 wfi,若 MSIP 路由或委托配置错误,目标 hart 会永久停在等待循环,且没有任何错误打印。其四,AIA 平台若 IMSIC 的 M 态配置与设备树声明不一致,S 态会收不到外部中断而串口照常输出,需要把 /proc/interrupts 与设备树 interrupts-extended 属性对照排查。验证条件按三层组织:HSM 层用从核首条日志时间戳与 HART_GET_STATUS 探针交叉核对;中断控制器层以启动打印四行设备名加内核 /proc/interrupts 的 PLIC/APLIC 行确认路由;端到端则用一次已知频率的外设中断(定时器)确认 tick 递增,三者全绿才可判定多核与中断设施完整可用。成本与权衡方面,票据加 cmpxchg 的状态机以极低的无锁开销换来了并发安全,代价是状态语义复杂(七态与四条指令路径叠加)——排查者必须先判定 hart 处于哪个状态,才能解释一次调用为什么成功、失败或「成功了但没人醒」;irqchip 统一框架的代价是多出设备与 handler 两层查找的间接层,换来的是有线中断与 MSI 在同一接口面下的可替换性,AIA 与非 AIA 平台的固件行为因此可以用同一张核对表覆盖 [S02][S07][S10]。
故障定位速查
OpenSBI 的启动过程没有交互式调试器可用,串口是唯一稳定的现场来源。前三节的机制结论可以直接转成一套定位次序:banner(固件版本与平台名)→ Platform HART Protection 行(保护框架选择的编码)→ 各子系统 boot print(IPI/HSM/定时器周期数)→ 域 dump 与最终交给 S 态的痕迹。每一条打印都对应 init_coldboot() 固定次序中的某一站,因此「串口最后停在哪条打印之后」等价于「初始化停在哪一站与下一站之间」。两个容易被忽略的陷阱需要先说明:其一,保护配置失败不会中断启动,sbi_hart_protection_configure() 失败时只打印一条错误串就继续走下去,系统可以照常进入 S 态但内存保护处于降级状态——「启动成功」不等于「保护落盘成功」;其二,动态形态下如果 SPL 交付的 fw_dynamic_info 校验失败,固件在打印任何字符之前就静默挂起在 _start_hang,此时串口全静默,锅在交棒参数而不在 OpenSBI 本身 [S02][S03][S08]。
按症状归拢,常见现场可以压缩成一张核对表:
| 现场症状 | 最可能的断点 | 核对手段 | 关联来源 |
|---|---|---|---|
| SPL 已跳转但串口全静默 | fw_dynamic_info 校验失败,静默挂在 _start_hang | 检查 a2 指向结构的 magic 是否为 0x4942534f、version 是否不超过 0x2;version 小于 2 时 fw_boot_hart() 返回 -1 | [S08] |
| SPL 有输出、无 OpenSBI banner | 启动打印被关闭,而非启动失败 | 查构建变量 FW_OPTIONS=0x1(bit0 对应 SBI_SCRATCH_NO_BOOT_PRINTS)与固件头 flags 字段是否覆盖了打印开关 | [S02][S03] |
| banner 之后停在半程 | init_coldboot() 固定次序中断在某一站 | 以串口最后一条打印定位子系统,对照 3.2 节的次序表向前后各查一站 | [S07] |
| 出现 protection 错误串 | region 地址/大小越界,或 PMP 条目不足 | Can not configure pmp for domain %s...、error: insufficient PMP entries;注意失败不 panic,系统会继续启动 | [S09] |
| 域 dump 权限与预期不一致 | region 标志或平台钩子改写了落盘结果 | 对 Domain%d Region%02d 行逐位比对 M: 与 S/U: 位图,再回查域定义处的标志位 | [S02][S09] |
| 从核不启动 | HSM START_PENDING 长期停留,或域权限被拒 | 查 sbi_hsm_hart_start() 的返回码、目标 hart 所在域的 range 是否含 EXECUTE 权限、启动票据是否被占用 | [S07] |
| S 态中断不通 | irqchip 驱动与设备树描述不匹配 | 按 lib/utils/irqchip/ 三驱动选择逐项确认,核对 boot print 中的 IPI 设备名与定时器频率是否与预期一致 | [S10] |
| QEMU 与真板行为不同 | AIA 配置差异 | 比对 QEMU virt 启动行的 aia= 取值(none/aplic/aplic-imsic)与真板设备树中的中断控制器描述 | [S13] |
表里第一条之外,还有一类「看起来像固件问题、实际在上一级」的现场:SPL 侧 spl_invoke_opensbi() 负责填写 fw_dynamic_info 再跳转(common/spl/spl_opensbi.c L47),它填错 next_addr/next_mode 或漏填 boot_hart,都会表现为 OpenSBI 静默或从核行为异常,回查 U-Boot 侧的这一层往往比在固件里翻找更快 [S12]。另外两台机器上的确认动作值得固化成习惯:域 dump 与 JTAG 双通道互证——dump 给出固件自报的 region 视图,JTAG 直接读 PMP 配置 CSR 给出硬件实际值,两者不一致时优先怀疑平台钩子 [S09];以及用 BASE 扩展的 PROBE_EXT 与 GET_SPEC_VERSION 在 S 态复核固件自报能力,避免拿板卡手册当结论 [S05][S11]。
总结与展望
把全文机制收敛成三条稳定事实。第一,交棒约定已经收敛:a0=hartid、a1=FDT、a2=交接块 三个寄存器加上 128 字节固件头与 fw_dynamic_info 的 magic/version 校验,构成了从 BootROM 到 SPL 再到 OpenSBI 的完整流水线;任何一个环节错位,现场都会停在可预期的静默点,而不是随机行为 [S08][S12]。第二,冷启动是一条可以逐行 grep 的固定序列:从 _start 的抽签择主、GOT 重定位、fw_save_info() 归档,到 init_coldboot() 的十六站子系统初始化与最后 mret 交付 S 态,每一站都有对应的串口锚点与源码行号,这让启动问题可以被系统化缩小范围,而不是靠经验猜测 [S02][S07]。第三,保护与请求两条通道都收敛到统一框架:hart protection 用 rating 机制在 epmp 与 pmp 之间选择、域 region 经单一落盘点写进硬件;ecall 分发用区间注册表把 0.1 遗留、核心扩展与平台服务扩展到同一条查找路径,未注册扩展经 BASE probe 的 0/1 语义被内核识别 [S06][S09]。
展望侧有三条观察。其一,v1.10 开发线的重心明显偏向平台驱动与框架打磨,说明核心机制层已经进入低增量阶段——对使用者而言,稳定性的边际提升将主要来自平台代码质量而非固件框架本身 [S02][S04]。其二,域切换的向量与浮点上下文保存、MSI 路径的完善、Smrnmi NMI 支持等 v1.9 引入的能力还在向默认路径渗透,域隔离正在从「内存区间隔离」走向「完整执行上下文隔离」,对安全评估与调试工具链都提出了新要求 [S04][S10]。其三,运行时可验证性持续增强:BASE probe、域 dump、启动打印开关构成了一套轻量自检面,把它们写进板卡 bringup 的验收流程,比在故障发生后再补课的成本低得多 [S05][S09]。对工程团队的落地建议是:把「BASE probe 复核能力 + 域 dump 双通道核对 + 四级串口锚点」三件事固化为出厂前检查动作,本报告的附录 B 给出了可直接照抄的清单 [S11]。
参考来源
本文全部行号与机制结论的抓取时点为 2026-10-10;OpenSBI 相关来源锁定同一 commit,其余仓库来源为当日 GitHub master 快照。
| 来源 ID | 来源 | 版本 / 锚点 | 在本文中的用途 |
|---|---|---|---|
| [S01] | RISC-V SBI 规范(riscv-non-isa/riscv-sbi-doc,github.com) | v3.0 正式文本(2025-07-16 批准) | 调用约定(a7/a6/a0/a1)、扩展语义与错误码的规范依据 |
| [S02] | OpenSBI 源码仓库(riscv-software-src/opensbi,github.com) | master 6ad246a1(2026-09-28,v1.10 开发线) | 全文行号锚点与机制结论的代码出处 |
| [S03] | OpenSBI 官方文档(仓库 docs/,含 firmware/fw.md) | 随 master 6ad246a1 快照 | 固件形态与构建变量(如 FW_OPTIONS)的行为说明 |
| [S04] | OpenSBI 发布记录(GitHub Releases) | v1.7(2025-06-30)/ v1.8(2025-12-29)/ v1.8.1(2026-01-08)/ v1.9(2026-07-01) | 版本节奏、平台接纳与特性落地时间线 |
| [S05] | include/sbi/sbi_ecall_interface.h(OpenSBI 仓库) | master 6ad246a1 | EID/FID 全表、HSM 状态取值与错误码定义 |
| [S06] | lib/sbi/sbi_ecall.c(OpenSBI 仓库) | master 6ad246a1 | ecall 分发、扩展注册校验与寄存器回写路径 |
| [S07] | lib/sbi/sbi_hsm.c、lib/sbi/sbi_init.c(OpenSBI 仓库) | master 6ad246a1 | 冷启动次序、HSM 票据与状态机 |
| [S08] | firmware/fw_base.S、firmware/fw_dynamic.S、include/sbi/fw_dynamic.h(OpenSBI 仓库) | master 6ad246a1 | 启动链、固件头与 fw_dynamic_info 交棒约定 |
| [S09] | lib/sbi/sbi_hart_pmp.c、lib/sbi/sbi_hart_protection.c(OpenSBI 仓库) | master 6ad246a1 | PMP/ePMP 落盘、rating 选择与保护错误串 |
| [S10] | lib/sbi/sbi_irqchip.c、lib/utils/irqchip/(OpenSBI 仓库) | master 6ad246a1 | 中断框架与 plic/aplic/imsic 三驱动收敛 |
| [S11] | Linux 内核(torvalds/linux,github.com) | arch/riscv/kernel/cpu_ops.c(2026-10-10 快照) | S 态消费侧:HSM 探测与 spinwait 回退(L16、L28-30) |
| [S12] | U-Boot(u-boot/u-boot,github.com) | common/spl/spl_opensbi.c(2026-10-10 快照) | SPL 侧交接实现:spl_invoke_opensbi()(L47)与 fw_dynamic_info 填写 |
| [S13] | QEMU(qemu/qemu,github.com) | docs/system/riscv/virt.rst(2026-10-10 快照) | virt 平台 AIA 配置项 `aia=[none\ |
附录 A:主要结论与依据对照
| 编号 | 主要结论 | 依据(来源与锚点) |
|---|---|---|
| C-01 | 三种固件形态由编译期选择;fw_dynamic 经 magic/version 校验后跳转,是 SPL 流程的默认形态 | firmware/fw_dynamic.S L21-37、include/sbi/fw_dynamic.h L31-40 [S02][S08] |
| C-02 | 冷启动为固定序列:抽签择主后由单个 hart 走完全部初始化,未中签者在 _wait_for_boot_hart 轮询 | firmware/fw_base.S L145/L157-166/L401;lib/sbi/sbi_init.c L224 [S02][S07][S08] |
| C-03 | init_coldboot() 按固定次序初始化子系统,scratch/heap/domain 为前三站硬约束 | lib/sbi/sbi_init.c L231/L236/L241 [S02][S07] |
| C-04 | 内存保护经 hart protection 框架落盘:SMEPMP 存在注册 epmp(rating 200)、否则 pmp(rating 100),仅第一个 MEMORY 类生效 | lib/sbi/sbi_hart_pmp.c L425-444、lib/sbi/sbi_hart_protection.c L108 [S02][S09] |
| C-05 | ecall 分发按 a7 区间查找扩展、按 a6 取函数;未注册返回 -2,回写 mepc += 4 且 0.1 遗留仅写 a0 | lib/sbi/sbi_ecall.c L122-167 [S02][S06] |
| C-06 | BASE probe 以 0/1 语义报告扩展注册状态,是内核运行时能力探测的规范判据 | lib/sbi/sbi_ecall_base.c L18-33、include/sbi/sbi_ecall.h L16-17 [S01][S05] |
| C-07 | HSM 以启动票据与 atomic_cmpxchg 状态机保证一次只放行一个 hart,抢占失败即回滚 | lib/sbi/sbi_hsm.c L95-109/L307-377 [S02][S07] |
| C-08 | 固件侧中断设施收敛为 plic/aplic/imsic 三驱动,按设备树选择,与有线中断/MSI 两条路径对应 | lib/utils/irqchip/、lib/sbi/sbi_irqchip.c [S02][S10] |
附录 B:检查与验收清单
| 编号 | 检查项 | 方法 | 期望结果 | 关联来源 |
|---|---|---|---|---|
| R-01 | 交棒参数核对 | 检查 SPL 跳转前 a0/a1/a2 与 fw_dynamic_info 内容;magic 须 0x4942534f、version 不超过 0x2 | 参数与构建配置一致,无静默挂起 | [S08][S12] |
| R-02 | 启动打印完整性 | 检查构建变量 FW_OPTIONS 与固件头 flags | banner 与各子系统 boot print 完整出现 | [S02][S03] |
| R-03 | 域 dump 核对 | 触发域 dump,逐条比对 region 地址范围与 M:/S/U: 位图 | 与设计一致 | [S02][S09] |
| R-04 | PMP 硬件值互证 | 经 JTAG 读 PMP 配置 CSR,与域 dump 双通道比对 | 两者一致;不一致优先查平台钩子 | [S09] |
| R-05 | 保护失败串扫描 | 对全量启动日志检索 protection 错误串 | 零命中;失败不 panic,必须主动扫描 | [S09] |
| R-06 | 固件能力探测 | S 态调用 BASE 的 PROBE_EXT 与 GET_SPEC_VERSION | HSM 等核心扩展返回 1;v1.9 固件版本号 0x03000000 | [S01][S05][S11] |
| R-07 | 从核启动时序 | 启动从核并观察 HSM 状态迁移与票据 | START_PENDING 迁移到 STARTED,无票据泄漏 | [S07] |
| R-08 | 中断通路确认 | 核对设备树中断控制器与 lib/utils/irqchip/ 驱动选择;QEMU 场景检查 aia= 参数 | 有线中断与 MSI 可达 | [S10][S13] |
附录 C:研究方法与局限
本报告采用「规范—实现—发布记录」三线交叉的研究方法。实现侧以公开 GitHub 仓库为准:先锁定 OpenSBI master 的单个 commit(6ad246a1,2026-09-28),再经公开 API 拉取该快照下的源码文件,对每个声称的机制逐行核对后再写进行号锚点;引用 U-Boot、Linux、QEMU 的锚点同样取自当日公开 master 快照并限定到具体文件与函数。规范侧对照 RISC-V SBI v3.0 正式文本,确认调用约定与错误码语义;发布侧用 OpenSBI Releases 的日期与说明校正版本时间线,并把「参考实现合规」与「规范批准」两条时间线的错位作为事实记录,而不是按印象取整。凡三线无法互证的说法一律不进入正文。
局限需要明确三点。其一,本报告未在真实硬件上完整走通启动与中断路径,全部结论来自源码与规范的对读,硬件行为(如具体平台的时钟频率、私有中断路由)可能引入快照之外的偏差,元数据中的状态字段已如实标注这一边界。其二,行号锚点属于快照事实,OpenSBI 以周为单位合入改动,复现时若 commit 不同,应以函数名与错误串为准检索,行号只作为该快照内的定位辅助。其三,平台钩子(platform/ 目录与各 SoC 设备树)只覆盖 generic 平台的公共路径,Nuclei 等厂商私有差异与 DDR 训练等 SoC 初始化细节不在本报告范围内。
附录 D:术语与缩略语
| 术语 | 展开 | 说明 |
|---|---|---|
| SBI | Supervisor Binary Interface | M 态固件向 S 态提供的二进制接口,规范由 RISC-V 国际基金会维护 |
| ecall | Environment Call | S 态向 M 态发起服务请求的唯一指令入口 |
| hart | Hardware Thread | RISC-V 的硬件线程,核内可并行执行流的最小单位 |
| CSR | Control and Status Register | RISC-V 控制状态寄存器,PMP 配置与 trap 现场均在其中 |
| FDT | Flattened Device Tree | 设备树二进制,交棒时经 a1 传递 |
| SPL | Secondary Program Loader | 二级引导器(典型为 U-Boot SPL),OpenSBI 的上一级交棒方 |
fw_dynamic_info | 动态固件信息结构 | SPL 与 OpenSBI 之间经 a2 传递的结构,含下一跳地址、特权模式与 boot hart |
| GOT | Global Offset Table | 全局偏移表,PIC 构建下启动早期的重定位对象 |
| PMP | Physical Memory Protection | 物理内存保护,M 态配置的地址区间权限机制 |
| SMEPMP | PMP 增强扩展 | 允许非 M 态代码受限访问 M 态数据的 PMP 扩展,固件优先经其实施保护 |
| HSM | Hart State Management | 从核启停与状态查询扩展(EID 0x48534D) |
| AIA | Advanced Interrupt Architecture | 新一代中断体系,由 APLIC(有线)与 IMSIC(MSI)组成 |
| PLIC | Platform-Level Interrupt Controller | 传统平台级中断控制器 |
| MSI | Message Signaled Interrupt | 消息信号中断,IMSIC 为其投递载体 |
| FWFT | Firmware Features | SBI v3.0 新增的固件特性查询扩展 |
| MPXY | Message Proxy | SBI v3.0 新增的消息代理扩展 |
| SSE | Supervisor Software Events | SBI v3.0 新增的软件事件扩展 |
| Zkr | 熵源 CSR 扩展 | 提供随机种子寄存器,固件用其初始化栈保护值 |
| Smrnmi | 非屏蔽中断扩展 | M 态 NMI 支持所依赖的扩展 |
| NMI | Non-Maskable Interrupt | 不可屏蔽中断,固件高可用场景的兜底手段 |
| JTAG | Joint Test Action Group | 芯片调试接口,核对 PMP 硬件配置的外部通道 |