qemu 启动虚拟机时需要指定Machine 即 -M 参数指定对应的机型.
从我们之前分析的demo看, 默认以virt 启动, 因riscv vcpu 只能运行在S/U mode 下, 所以不需要关注opensbi的部分, 只需要关注u-boot 和 kernel的部分.
这里只说kernel的部分, kernel 在编译时也要按对应的机型编译, 如virt. 还有dtb的部分, kernel 和 qemu 中也是对应的.
新增Machine 只是对于没有开发板时的妥协,便于开发人员在没有开发板时有一个板子的虚拟环境进行测试验证, 所以可以看到该新增Machine的模拟硬件同开发板基本上是一致的;
对于虚拟化来说只有virt就可以了, virt上的feature也是最全的, 最适合跑guest os, 没有必要用其他Machine 跑guest os.
dtb 信息
在qemu 中一般会以代码的形式写dtb的相关属性, 可以用 qemu 导出相应机型的dtb信息
1 | qemu-system-riscv64 -machine virt,dumpdtb=virt.dtb -smp 1 -m 2G -nographic |
为了宏观的观察新增一个Machine 需要添加哪些部分, 可以先对比下 virt.dts 和 新增Machine 如sifive_u.dts的内容.
对于dtb 中描述的硬件信息, qemu都需要模拟出来对应的硬件.
qemu 中 dtb 信息的创建
qemu 对 libfdt 的接口做了二次封装,部分接口举例如下:
- create_device_tree:初始化一颗设备树
- qemu_fdt_setprop_cell:设置设备树的节点的属性,值为数字
- qemu_fdt_setprop_string:设置设备树的节点的属性,值为字符串
- qemu_fdt_add_subnode:添加一个子节点
sifive_u.c 中 create_fdt 函数就是使用上述的接口构建了整个dtb.
memory 信息
对于模拟的机型, 其中最重要的一块之一就是模拟的物理内存 GPA 的布局
如sifive_u的
1 | static const MemMapEntry sifive_u_memmap[] = { |
从这个简表上大概也能看出涉及到的硬件资源有哪些, 如clint plic qspi pwm otp uart 等等, 而对应的这些硬件资源都需要qemu 进行模拟.
这些模拟的硬件资源的特性都应该跟硬件SPEC 上一致.
machine_init
对应machine的class_init, 注册 MachineClass -> init, 如sifive_u 为 sifive_u_machine_init
该函数由 /hw/core/machine.c#machine_run_board_init
调用
该函数涉及到的初始化的硬件资源有 cpu ddr/ram qspi gpio spi sdcard, sdcard跟SPI2 相连, dtb 信息也是在该函数中调用 create_fdt 创建的
以下均以sifive_u 的Machine展开介绍
cpu
child obj – TYPE_RISCV_U_SOCqdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
涉及到的重要函数有 cpu的 类初始化 实例初始化 具现化函数, 以sifive_u 举例
sifive_u_soc_class_init sifive_u_soc_instance_init sifive_u_soc_realize
sifive_u_soc_class_init的主要作用是指定具现化函数
instance_init
sifive_u_soc_instance_init
该函数除了定义cpu的一些属性外, 还引出了cpu关联的总线上的一些ip 硬件资源
1 | static void sifive_u_soc_instance_init(Object *obj) { |
这些硬件资源每一个都需要展开, 需要qemu 模拟对应的特性.
这里抽一个简单的来看, 以otp 为例
最重要的函数为其具现化函数 sifive_u_otp_realize
对应总线上的ip, 最关键的是其 mmio的注册添加到io的memory region上, 当guest os 访问对应的物理地址时, 需要发生陷入, 最终陷入到qemu 中为该ip注册的mmio MemoryRegion上.
此处, otp相关的mmio MemoryRegion的注册过程如下, 对应的陷入的读写函数为 sifive_u_otp_read
sifive_u_otp_write
1 | static const MemoryRegionOps sifive_u_otp_ops = { |
在具现化函数中负责为该ip 进行具现化sysbus_mmio_map(SYS_BUS_DEVICE(&s->otp), 0, memmap[SIFIVE_U_DEV_OTP].base);
其中 SIFIVE_U_DEV_OTP 的GPA 区域, 同硬件SPEC 上OTP的mmio 一致.[SIFIVE_U_DEV_OTP] = { 0x10070000, 0x1000 },
realize
对应 sifive_u 的 具现化函数
sifive_u_soc_realize
包含 dtb中cpu的设置
这里设置了 u_cpus, 对应fu540, e_cpus 对应e24 单元
调用 sysbus_realize 进行platform 总线的设置
创建 “riscv.sifive.u.l2lim” 内存
plic 初始化
uart 初始化
clint 初始化
clint software interrupt 初始化
cline timer interrupt 初始化
各外设和ip的 mmio 设置