学习: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 探测。

相关推荐
weixin_448771727 分钟前
使用Aspose给PDF添加水印
java·pdf
\光辉岁月/8 分钟前
7.system类
java·system类
SL_staff22 分钟前
BI落地失败的3个技术断点:从数据接入、自助分析到服务闭环的工程实践
java·数据分析·数据可视化
大风起兮云飞扬丶25 分钟前
Java——线程池
java·开发语言
别动我齐刘海41 分钟前
机器人 C++ / 系统联调补强2——异常处理、Protobuf
linux·c++·人工智能·学习·算法·机器学习·机器人
一直C1 小时前
嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动
linux·驱动开发·嵌入式硬件·学习·imx6ull
文丑颜不良啊1 小时前
LeetCode276~300题目解析
java·leetcode
Vegetable_xu1 小时前
Spring Bean 循环依赖:为什么有时能过、有时启动就挂
java·spring·mybatis
承渊政道1 小时前
Linux网络学习【传输层协议UDP】
linux·网络·网络协议·学习·ubuntu·udp
andrsted1 小时前
练题簿免费刷题小程序:和学习搭子共用一份题库,一起刷题复习
学习·面试·微信小程序·小程序·学习方法·刷题