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 c48d9d247e6bf30153b8e5e91dab4f63ac847a80
Author: 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 8b0a11489cb4c7d796e545fb4bb59a0ada6ed09d
Author: 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.h
index 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 aa5224ecb03957825982f52579dae0dc02a93c98
Author: 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 72a1f9a351d5b3adf2dd8dbcbb1505e9f3824ec6
Author: zevorn <[email protected]t>
Date:   Thu Jul 25 00:05:12 2024 +0800

    hw/riscv: Add the intc device to gdv32vf103 cpu

commit ac3c55d0e19cb03bb31bd8f580ec24f18eb18590
Author: 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 7df1ce25dd9ea24fe1c4e9a4bc0ed41d933495e2
Author: 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.c
index 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 cpu

commit 5969602dcb461db5cba548ba83c29069be097dd8
Author: zevorn <[email protected]t>
Date:   Sun Jul 28 17:16:39 2024 +0800

    hw/gpio: Add the nuclei_gpio device

commit 60d9091f591599635af9ef49625962c25e50c2dc
Author: zevorn <[email protected]t>
Date:   Sun Jul 28 17:04:20 2024 +0800

    hw/riscv: Add the rcu device to gdv32vf103 cpu

commit e0af6cbaf0307ebd727ae5bdf471920733557f08
Author: 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 qemu
qemu-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 的异常来处理,这样非常方便通过客户程序的指令流或者函数调用流,来定位出错的地方。