0%

qemu 新增 riscv machine 参考.

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
2
3
4
qemu-system-riscv64 -machine virt,dumpdtb=virt.dtb -smp 1 -m 2G -nographic
dtc virt.dtb > virt.dts
qemu-system-riscv64 -machine sifive_u,dumpdtb=sifive_u.dtb -smp 2 -m 2G -nographic
dtc sifive_u.dtb > sifive_u.dts

为了宏观的观察新增一个Machine 需要添加哪些部分, 可以先对比下 virt.dts 和 新增Machine 如sifive_u.dts的内容.

对于dtb 中描述的硬件信息, qemu都需要模拟出来对应的硬件.

qemu 中 dtb 信息的创建

qemu 对 libfdt 的接口做了二次封装,部分接口举例如下:

  1. create_device_tree:初始化一颗设备树
  2. qemu_fdt_setprop_cell:设置设备树的节点的属性,值为数字
  3. qemu_fdt_setprop_string:设置设备树的节点的属性,值为字符串
  4. qemu_fdt_add_subnode:添加一个子节点

sifive_u.c 中 create_fdt 函数就是使用上述的接口构建了整个dtb.

memory 信息

对于模拟的机型, 其中最重要的一块之一就是模拟的物理内存 GPA 的布局
如sifive_u的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static const MemMapEntry sifive_u_memmap[] = {
[SIFIVE_U_DEV_DEBUG] = { 0x0, 0x100 },
[SIFIVE_U_DEV_MROM] = { 0x1000, 0xf000 },
[SIFIVE_U_DEV_CLINT] = { 0x2000000, 0x10000 },
[SIFIVE_U_DEV_L2CC] = { 0x2010000, 0x1000 },
[SIFIVE_U_DEV_PDMA] = { 0x3000000, 0x100000 },
[SIFIVE_U_DEV_L2LIM] = { 0x8000000, 0x2000000 },
[SIFIVE_U_DEV_PLIC] = { 0xc000000, 0x4000000 },
[SIFIVE_U_DEV_PRCI] = { 0x10000000, 0x1000 },
[SIFIVE_U_DEV_UART0] = { 0x10010000, 0x1000 },
[SIFIVE_U_DEV_UART1] = { 0x10011000, 0x1000 },
[SIFIVE_U_DEV_PWM0] = { 0x10020000, 0x1000 },
[SIFIVE_U_DEV_PWM1] = { 0x10021000, 0x1000 },
[SIFIVE_U_DEV_QSPI0] = { 0x10040000, 0x1000 },
[SIFIVE_U_DEV_QSPI2] = { 0x10050000, 0x1000 },
[SIFIVE_U_DEV_GPIO] = { 0x10060000, 0x1000 },
[SIFIVE_U_DEV_OTP] = { 0x10070000, 0x1000 },
[SIFIVE_U_DEV_GEM] = { 0x10090000, 0x2000 },
[SIFIVE_U_DEV_GEM_MGMT] = { 0x100a0000, 0x1000 },
[SIFIVE_U_DEV_DMC] = { 0x100b0000, 0x10000 },
[SIFIVE_U_DEV_FLASH0] = { 0x20000000, 0x10000000 },
[SIFIVE_U_DEV_DRAM] = { 0x80000000, 0x0 },
};

从这个简表上大概也能看出涉及到的硬件资源有哪些, 如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_SOC
qdev_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
2
3
4
5
6
7
8
9
10
11
12
static void sifive_u_soc_instance_init(Object *obj) {
... // 定义cpu的相关属性
object_initialize_child(obj, "prci", &s->prci, TYPE_SIFIVE_U_PRCI);
object_initialize_child(obj, "otp", &s->otp, TYPE_SIFIVE_U_OTP);
object_initialize_child(obj, "gem", &s->gem, TYPE_CADENCE_GEM);
object_initialize_child(obj, "gpio", &s->gpio, TYPE_SIFIVE_GPIO);
object_initialize_child(obj, "pdma", &s->dma, TYPE_SIFIVE_PDMA);
object_initialize_child(obj, "spi0", &s->spi0, TYPE_SIFIVE_SPI);
object_initialize_child(obj, "spi2", &s->spi2, TYPE_SIFIVE_SPI);
object_initialize_child(obj, "pwm0", &s->pwm[0], TYPE_SIFIVE_PWM);
object_initialize_child(obj, "pwm1", &s->pwm[1], TYPE_SIFIVE_PWM);
}

这些硬件资源每一个都需要展开, 需要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
2
3
4
5
6
7
8
9
10
11
static const MemoryRegionOps sifive_u_otp_ops = {
.read = sifive_u_otp_read,
.write = sifive_u_otp_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4
}
};
memory_region_init_io(&s->mmio, OBJECT(dev), &sifive_u_otp_ops, s,
TYPE_SIFIVE_U_OTP, SIFIVE_U_OTP_REG_SIZE);

在具现化函数中负责为该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 设置