学习:RV lkvm SBI

一句话结论

lkvm 起的 Guest 里根本没有 OpenSBI 。SBI 信息不来自任何固件文件,而是分三处拼出来的:主体由 Host 的 KVM 内核模块实现 (arch/riscv/kvm/vcpu_sbi*.c),部分调用转发给 lkvm 用户态模拟 ,可见的扩展集合由 lkvm 通过 KVM_SET_ONE_REG 在启动前配置 。设备树在其中几乎不扮演角色------Guest 靠 ecall 探测 SBI,而不是读 DTB。


1. 为什么这里没有 OpenSBI

裸机 / QEMU 的路径是:ZSBL → OpenSBI(M-mode) → Linux(S-mode),SBI 由 M-mode 固件提供。

kvmtool 的路径完全不同------它直接把内核镜像丢进 Guest,Guest 跑在 VS-mode ,Guest 的 ecall 被硬件直接送到 HS-mode 的 KVM 模块手里。所以 KVM 自己就是 Guest 的"SBI 实现者"。kvmtool 的 patch 里也明确写着 "Firmware loading is not implemented currently... booting kernel directly without any bootloader"。

2. SBI 信息的三个来源

① 绝大部分:Host KVM 内核模块内建

arch/riscv/kvm/vcpu_sbi.c 里有一张扩展表,Host 内核编译时就固定了:

复制代码
static const struct kvm_riscv_sbi_extension_entry sbi_ext[] = {
    { KVM_RISCV_SBI_EXT_V01,  &vcpu_sbi_ext_v01  },   /* legacy, 依赖 CONFIG_RISCV_SBI_V01 */
    { KVM_RISCV_SBI_EXT_MAX,  &vcpu_sbi_ext_base },   /* Can't be disabled */
    { KVM_RISCV_SBI_EXT_TIME, &vcpu_sbi_ext_time },   /* set_timer → vcpu timer */
    { KVM_RISCV_SBI_EXT_IPI,  &vcpu_sbi_ext_ipi  },   /* → IRQ_VS_SOFT */
    { KVM_RISCV_SBI_EXT_RFENCE, ... }, { _SRST }, { _HSM },
    { _PMU }, { _DBCN }, { _STA }, { _EXPERIMENTAL }, { _VENDOR },
};

版本号和实现 ID 是 KVM 自己定义的常量,与 Host 上的 OpenSBI 版本无关:

复制代码
#define KVM_SBI_IMPID 3
#define KVM_SBI_VERSION_MAJOR 0
#define KVM_SBI_VERSION_MINOR 2

即 Guest 里 sbi_get_spec_version() 拿到的 0.2,是 KVM 报给它的,不是 Host 固件的版本。

② 内核处理不了的:转发给 lkvm

kvm_riscv_vcpu_sbi_forward() 把调用打包成 KVM_EXIT_RISCV_SBI 退出到用户态:

复制代码
run->exit_reason = KVM_EXIT_RISCV_SBI;
run->riscv_sbi.extension_id = cp->a7;
run->riscv_sbi.function_id  = cp->a6;
run->riscv_sbi.args[0..5]   = cp->a0..a5;

lkvm 侧 kvm_cpu_riscv_sbi() 处理:

复制代码
case SBI_EXT_0_1_CONSOLE_PUTCHAR:
    ch = vcpu->kvm_run->riscv_sbi.args[0];
    term_putc(&ch, 1, 0);          /* 打到 lkvm 的终端 */
    vcpu->kvm_run->riscv_sbi.ret[0] = 0;
    break;
case SBI_EXT_0_1_CONSOLE_GETCHAR:
    ...  term_getc() : SBI_ERR_FAILURE;

返回后 kvm_riscv_vcpu_sbi_return() 把 ret[0]/ret[1] 写回 a0/a1 并 sepc += 4。SBI experimental / vendor 两类扩展是无条件转发给用户态的(KVM 无从知晓厂商语义)。较新的 kvmtool 还加了 SBI SUSP 的测试用 handler(睡 5 秒再恢复 Guest)。

③ 可见扩展集合:lkvm 主动下发配置

lkvm 提供 --disable-sbi-legacy / -time / -ipI / -rfence / -srst / -hsm / -pmu / -dbcn / -susp / -sta / -experimental / -vendor 等开关,存入 cfg.arch.sbi_ext_disabled[],在 kvm_cpu__arch_init() 里对每个 vCPU 下发:

复制代码
reg.id   = RISCV_SBI_EXT_REG(KVM_REG_RISCV_SBI_MULTI_DIS, i);
reg.addr = (unsigned long)&masks[i];
ioctl(vcpu->vcpu_fd, KVM_SET_ONE_REG, &reg);

即 ONE_REG 接口 KVM_REG_RISCV_SBI_EXT 的 MULTI_DIS 子类型 。默认 KVM 侧是"全部可用",但对于 DBCN 这类需要转发给用户态 的扩展,KVM 会标 default_unavail,要求用户态显式使能才收得到转发------防止老版本 lkvm 收到无法处理的调用。

3. 关于设备树:这里不是 SBI 的载体

setup_fdt() 生成的节点里没有 SBI 相关内容,只有 /chosen(bootargs、initrd)、/memory、/cpus(riscv,isa、mmu-type)、中断控制器、/smb 下的设备。

原因很直接:SBI 是运行时 ABI,靠 ecall 探测,不是靠 DTB 描述 。Guest 内核启动时调 sbi_get_spec_version() / sbi_probe_extension(),这些调用正好被上面 ① 的 KVM base 扩展接住。

唯一沾边的是 SBI base 扩展的 GET_MVENDORID / MARCHID / MIMPID------这些值来自 vCPU 的 CSR,可以用 --custom-mvendorid / --custom-marchid / --custom-mimpid 覆盖。

4. 时序

复制代码
KVM_CREATE_VCPU
   └─ kvm_arch_vcpu_create() → kvm_riscv_vcpu_sbi_init()
        逐个 probe(),填 ext_status[](AVAILABLE / UNAVAILABLE)
                ↓
lkvm: KVM_SET_ONE_REG(SBI_EXT / MULTI_DIS)    ← --disable-sbi-xxx 生效
                ↓
KVM_RUN
   └─ Guest ecall
        ├─ 命中 in-kernel handler → 内核直接处理(timer/IPI/rfence/HSM...)
        └─ 无 handler → KVM_EXIT_RISCV_SBI → lkvm 模拟 → 返回

注意 kvm_riscv_vcpu_sbi_init() 的注释写得很清楚:"This must be the last thing to be initialized"------所有 SBI 配置必须在 vCPU 首次 RUN 之前完成,之后改是要走 reset 语义的。

5. 与 Host SBI 的真实关系

维度 是否受 Host 影响
Guest 看到的 SBI 版本号 / IMPID 否,KVM 常量决定
Guest 可用的扩展列表 主要由 Host 内核 CONFIG_* 和 kvmtool 开关决定
部分扩展的底层实现 是 ,例如 RFENCE 的 SBI_EXT_RFENCE_REMOTE_SFENCE_VMA 最终调 Host 侧的 sbi_remote_hfence_vvma();PMU 依赖 CONFIG_RISCV_PMU_SBI
控制台字符输出 由 lkvm 的终端接管,与 Host 串口无关

换句话说:Guest 的 SBI 是 KVM 虚拟出来的一个"假固件",只有落到真实硬件操作那一步时才借用 Host 的 SBI。

6. 想看实际效果

复制代码
# 关掉某个扩展,看 Guest 是否降级
lkvm-static run -k ./Image -i ./rootfs.img --disable-sbi-hsm -c 2 --debug

# Guest 里看内核探测到的 SBI
dmesg | grep -i sbi        # 或 /sys/firmware/...

--debug 下 lkvm 会把未处理的 SBI 调用(extension_id/function_id/args 全部打印出来)写进 debug fd,这是排查"SBI 调用没人接"最直接的手段。


一句话总结 :lkvm 场景没有 OpenSBI------SBI 由 Host KVM 模块内建实现(版本/实现 ID 是 KVM 自己的常量),处理不了的调用经 KVM_EXIT_RISCV_SBI 转发给 lkvm 用户态模拟,Guest 可见的扩展集合由 lkvm 通过 KVM_REG_RISCV_SBI_EXT 的 ONE_REG 接口在 vCPU 启动前裁剪;设备树不承载 SBI 信息,Guest 靠 ecall 探测。

相关推荐
承渊政道1 分钟前
Linux系统学习【线程概念与控制核心知识详解】
linux·运维·vscode·学习·ubuntu·线程创建 终止 等待
励志不掉头发的内向程序员2 分钟前
【从零写一个CAD 04】中键拖动平移:抓住一个点,让它一直待在鼠标底下
开发语言·c++·qt·学习·系统架构·计算机外设
UIU1143 分钟前
同一个变量在两个 .c 文件里类型不一致,程序会怎样?
c++·学习·c#
阿俊-全栈开发6 小时前
LikeShop 单商户SaaS商城分销模块二开:佣金计算与结算逻辑实现
java·开发语言·微服务·likeshop·likeshop开源商城·likeshop多商户
GEO从入门到精通8 小时前
课程怎么选?2026年GEO学习资源全梳理
学习
wuminyu9 小时前
C++协程实现接收端的零拷贝Buffer管理原理剖析
java·linux·c语言·jvm·c++
优化Henry9 小时前
LTE 载波频率与频点配置详解
运维·网络·学习·5g·信息与通信
xiaomu001239 小时前
高湿环境床垫湿度管理技术框架:湿度阈值、排湿结构与材料选型
经验分享·学习·学习方法
zhangrelay10 小时前
ROS项目设计案例智能大模型正经乱答案例
linux·笔记·学习·ubuntu·机器人
probex_10 小时前
学习笔记:知识图谱是什么,在测试里能干什么
笔记·学习·知识图谱