ZEVORN.blog

July 28, 2024

记录移植 PLCT 实验室的 qemu-nuclei_gd32vf103 到 QEMU v9.0.2 的一些经验

noteqemu7.1 min to read

当前成果:

git clone -b nuclei_gd32vf103 https://gitee.com/gevico/qemu.git

下面记录关键 patch 解决的问题:

[patch] 修复 dts 编译警告问题

commit c48d9d247e6bf30153b8e5e91dab4f63ac847a80Author: zevorn <[email protected]t>Date:   Tue Jul 23 00:38:22 2024 +0800    pc-bios: Fix the compilation warning when dtb was generated        It is mainly related to the memory and opb nodes in bamboo.dts.

这个 patch 主要解决了编译过程中遇到的 dts 编译警告,后续主线应该会修复该问题。

[patch] 添加 nuclei n205 核心

commit 8b0a11489cb4c7d796e545fb4bb59a0ada6ed09dAuthor: zevorn <[email protected]t>Date:   Mon Jul 22 22:57:21 2024 +0800    target/riscv: Add nuclei_n205 core for RISCV architecture

初始化 N205 核心的时候,用的 DEFINE_VENDOR_CPU(),可扩展厂商自定义指令集。

diff --git a/target/riscv/cpu-qom.h b/target/riscv/cpu-qom.hindex 3670cfe6d9..7351cb4b0b 100644--- a/target/riscv/cpu-qom.h+++ b/target/riscv/cpu-qom.h@@ -50,6 +50,7 @@ #define TYPE_RISCV_CPU_THEAD_C906       RISCV_CPU_TYPE_NAME("thead-c906") #define TYPE_RISCV_CPU_VEYRON_V1        RISCV_CPU_TYPE_NAME("veyron-v1") #define TYPE_RISCV_CPU_HOST             RISCV_CPU_TYPE_NAME("host")+#define TYPE_RISCV_CPU_NUCLEI_N205      RISCV_CPU_TYPE_NAME("nuclei-n205")

[patch] 添加 gd32vf103 Machine

commit aa5224ecb03957825982f52579dae0dc02a93c98Author: zevorn <[email protected]t>Date:   Mon Jul 22 22:58:01 2024 +0800    hw/riscv: Add gd32vf103 mcu for RISCV architecture

没有实现具体外设,只增加了一些 mr 的初始化:

static void nuclei_board_init(MachineState *machine)+{+    const struct MemmapEntry *memmap = gd32vf103_memmap;+    NucleiGDState *s = g_new0(NucleiGDState, 1);+    MemoryRegion *system_memory = get_system_memory();+    MemoryRegion *main_mem = g_new(MemoryRegion, 1);+    s->soc.sram = *main_mem;+    int i;++    /* TODO: Add qtest support */+    /* Initialize SOC */+    object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_NUCLEI_GD32VF103_SOC);+    qdev_realize(DEVICE(&s->soc), NULL, &error_abort);++    memory_region_init_ram(&s->soc.main_flash, NULL, "riscv.nuclei.main_flash",+                           memmap[GD32VF103_MAINFLASH].size, &error_fatal);+    memory_region_add_subregion(system_memory,+                                memmap[GD32VF103_MAINFLASH].base, &s->soc.main_flash);++    memory_region_init_ram(&s->soc.boot_loader, NULL, "riscv.nuclei.boot_loader",+                           memmap[GD32VF103_BL].size, &error_fatal);+    memory_region_add_subregion(system_memory,+                                memmap[GD32VF103_BL].base, &s->soc.boot_loader);++    memory_region_init_ram(&s->soc.ob, NULL, "riscv.nuclei.ob",+                           memmap[GD32VF103_OB].size, &error_fatal);+    memory_region_add_subregion(system_memory,+                                memmap[GD32VF103_OB].base, &s->soc.ob);++    memory_region_init_ram(&s->soc.sram, NULL, "riscv.nuclei.sram",+                           memmap[GD32VF103_SRAM].size, &error_fatal);+    memory_region_add_subregion(system_memory,+                                memmap[GD32VF103_SRAM].base, &s->soc.sram);++    /* reset vector */+    uint32_t reset_vec[8] = {+        0x00000297, /* 1:  auipc  t0, %pcrel_hi(dtb) */+        0x02028593, /*     addi   a1, t0, %pcrel_lo(1b) */+        0xf1402573, /*     csrr   a0, mhartid  */+#if defined(TARGET_RISCV32)+        0x0182a283, /*     lw     t0, 24(t0) */+#elif defined(TARGET_RISCV64)+        0x0182b283, /*     ld     t0, 24(t0) */+#endif+        0x00028067, /*     jr     t0 */+        0x00000000,+        memmap[GD32VF103_MAINFLASH].base, /* start: .dword */+        0x00000000,+        /* dtb: */+    };++    /* copy in the reset vector in little_endian byte order */+    for (i = 0; i < sizeof(reset_vec) >> 2; i++)+    {+        reset_vec[i] = cpu_to_le32(reset_vec[i]);+    }+    rom_add_blob_fixed_as("mrom.reset", reset_vec, sizeof(reset_vec),+                          memmap[GD32VF103_MFOL].base + 0x1000, &address_space_memory);++    /* boot rom */+    if (machine->kernel_filename)+    {+        riscv_load_kernel(machine, &s->soc.cpus,+                          memmap[GD32VF103_MAINFLASH].base,+                          false, NULL);+    }+}

[patch] 增加 eclic 外设,完善中断控制流程

commit 72a1f9a351d5b3adf2dd8dbcbb1505e9f3824ec6Author: zevorn <[email protected]t>Date:   Thu Jul 25 00:05:12 2024 +0800    hw/riscv: Add the intc device to gdv32vf103 cpucommit ac3c55d0e19cb03bb31bd8f580ec24f18eb18590Author: zevorn <[email protected]t>Date:   Thu Jul 25 00:05:02 2024 +0800    hw/intc: Add the gdv32vf103_eclic device

这部分还没有仔细研究,直接移植的 PLCT 原本的代码,但是做了 patch 整理,代码更加清晰。

[patch] 增加 nuclei-n205 CSR 扩充的寄存器的支持

commit 7df1ce25dd9ea24fe1c4e9a4bc0ed41d933495e2Author: zevorn <[email protected]t>Date:   Sun Jul 28 17:04:20 2024 +0800    hw/riscv: Add the rcu device to gdv32vf103 cpu    target/riscv: Add CSR register support for nuclei N205

这个 patch 有几个关键修改。

需要将 n205 的指令集版本设置为 latest,不然不支持 CSR_MCOUNTINHIBIT 寄存器,或者运行时有警告,另外需要将 cpu->cfg.ext_zifencei 和 cpu->cfg.ext_zicsr 设置为 true,不然有些 CSR 寄存器也不支持,会导致 startup 阶段访问某些 CSR 寄存器时意外陷入异常:

diff --git a/target/riscv/cpu.c b/target/riscv/cpu.cindex 326576fe20..1d9d36ddb2 100644--- a/target/riscv/cpu.c+++ b/target/riscv/cpu.c@@ -684,13 +684,15 @@ static void rv32imacu_nuclei_cpu_init(Object *obj) #endif      riscv_cpu_set_misa_ext(env, RVI | RVM | RVA | RVC | RVU);-    env->priv_ver = PRIV_VERSION_1_10_0;+    env->priv_ver = PRIV_VERSION_LATEST; // support CSR_MCOUNTINHIBIT      /* inherited from parent obj via riscv_cpu_init() */     qdev_prop_set_bit(DEVICE(obj), "mmu", false); #ifndef CONFIG_USER_ONLY     env->resetvec = DEFAULT_RSTVEC; #endif+    cpu->cfg.ext_zifencei = true;+    cpu->cfg.ext_zicsr = true;     cpu->cfg.pmp = true; }

[patch] 增加 gd32vf103 startup 阶段需要的几个外设

commit b37aac1ef3641e4aff84c9d95f48ff1cba8b2b74 (HEAD -> nuclei_gd32vf103)Author: zevorn <[email protected]t>Date:   Sun Jul 28 17:16:46 2024 +0800    hw/riscv: Add the gpio device to gdv32vf103 cpucommit 5969602dcb461db5cba548ba83c29069be097dd8Author: zevorn <[email protected]t>Date:   Sun Jul 28 17:16:39 2024 +0800    hw/gpio: Add the nuclei_gpio devicecommit 60d9091f591599635af9ef49625962c25e50c2dcAuthor: zevorn <[email protected]t>Date:   Sun Jul 28 17:04:20 2024 +0800    hw/riscv: Add the rcu device to gdv32vf103 cpucommit e0af6cbaf0307ebd727ae5bdf471920733557f08Author: zevorn <[email protected]t>Date:   Sun Jul 28 17:04:07 2024 +0800    hw/timer: Add the nuclei_rcu device

这里使用 riscv-gdb 远程 remote 到 qemu 以后,观察异常点的函数调用栈,可以追溯是哪个外设没实现,导致的访问寄存器时触发了异常,然后将对应的外设移植过来即可。

比较简单,这里不赘述移植过程,只说一下调试的流程,命令如下:

open 一个终端,启动 qemu :

cd qemuqemu-system-riscv32 -M gd32vf103v_rvstar -cpu nuclei-n205 -icount shift=0 -nodefaults -nographic -serial stdio -kernel nuclei-sdk/application/baremetal/helloworld/helloworld.elf -gdb tcp::1234 -S

open 一个新的终端,启动 gdb 来调试客户程序:

riscv-nuclei-linux-gnu-gdb ../nuclei-sdk/application/baremetal/helloworld/helloworld.elf(gdb) target remote localhost:1234(gdb) run(gdb) ctrl + c(gdb) bt...

这里可以根据函数调用栈,来定位具体异常对应的外设,或者其他指令。

Ps:对于来自客户程序的异常行为,即使是 qemu 这边没有实现对应的指令或者外设,亦或者没有初始化一些内存区域,也是按照 Guest Machine 的异常来处理,这样非常方便通过客户程序的指令流或者函数调用流,来定位出错的地方。