基于qemu-riscv从0开始构建嵌入式linux系统ch2. 添加qemu仿真板——Quard-Star板

增加源文件

首先看下图,预览本节教程要添加的源代码,内容不多,主要是增加一个quard_star.c和.h文件用来定义我们的虚拟板卡,然后将其加入qemu编译体系内。

ch2-0

源码解析

路径qemu-6.0.0/hw/riscv/quard_star.c:(由于对应代码已上传github,我们这里仅对关键代码做以解析)

注册板卡到系统内,并定义名称。

static const TypeInfo quard_star_machine_typeinfo = {
    .name       = MACHINE_TYPE_NAME("quard-star"),
    .parent     = TYPE_MACHINE,
    .class_init = quard_star_machine_class_init,
    .instance_init = quard_star_machine_instance_init,
    .instance_size = sizeof(RISCVVirtState),
};

static void quard_star_machine_init_register_types(void)
{
    type_register_static(&quard_star_machine_typeinfo);
}

type_init(quard_star_machine_init_register_types)

quard_star_machine_class_init,注册板卡资源初始化函数以及最大支持的smp核心数

    mc->init = quard_star_machine_init;
    mc->max_cpus = QUARD_STAR_CPUS_MAX;

构造虚拟cpu,在qemu中各个cpu核心为线程模拟的,相当于创建子线程。

soc_name = g_strdup_printf("soc%d", i);
object_initialize_child(OBJECT(machine), soc_name, &s->soc[i],
                        TYPE_RISCV_HART_ARRAY);
g_free(soc_name);
object_property_set_str(OBJECT(&s->soc[i]), "cpu-type",
                        machine->cpu_type, &error_abort);
object_property_set_int(OBJECT(&s->soc[i]), "hartid-base",
                        base_hartid, &error_abort);
object_property_set_int(OBJECT(&s->soc[i]), "num-harts",
                        hart_count, &error_abort);
sysbus_realize(SYS_BUS_DEVICE(&s->soc[i]), &error_abort);

定义内存相关资源,这里定义一个cpu内部的一片maskrom,一片sram,和一片ddr内存,maskrom用于cpu启动时固定执行其内部的代码,sram为早期启动代码时数据存放空间,ddr一般在真实板卡上是需要对应控制器初始化后才能使用,但在qemu模拟时直接将其配置为了可以使用的内存了,但我们追求真实,不在早期启动时使用这片内存空间。

static const MemMapEntry virt_memmap[] = {
    [QUARD_STAR_MROM]  = {        0x0,        0x8000 },
    [QUARD_STAR_SRAM]  = {     0x8000,        0x8000 },
    [QUARD_STAR_UART0] = { 0x10000000,         0x100 },
    [QUARD_STAR_DRAM]  = { 0x80000000,           0x0 },
};

memory_region_init_ram(main_mem, NULL, "riscv_quard_star_board.dram",
                           machine->ram_size, &error_fatal);
memory_region_add_subregion(system_memory,memmap[QUARD_STAR_DRAM].base,main_mem);

memory_region_init_ram(sram_mem, NULL, "riscv_quard_star_board.sram",
                           memmap[QUARD_STAR_SRAM].size, &error_fatal);
memory_region_add_subregion(system_memory,memmap[QUARD_STAR_SRAM].base, sram_mem);

memory_region_init_rom(mask_rom, NULL, "riscv_quard_star_board.mrom",
                           memmap[QUARD_STAR_MROM].size, &error_fatal);
memory_region_add_subregion(system_memory,memmap[QUARD_STAR_MROM].base,  mask_rom);

加载maskrom的固件到mrom区域,这段代码是使用汇编代码编写编译器生成的二进制指令数据,功能如注释描述,主要功能将cpu hart id值加载到a0寄存器,设备树文件基地址加载到a1寄存器,然后跳转到下级代码的运行地址(在这里我们定义为0x0地址,如此形成了循环,这个在后面我们是会修改的),这里riscv中的abi规范规定a0寄存器为参数0,a1寄存器为参数1,其本质相当于函数调用,并将cpu id和下级设备树基地址传入下级代码。

quard_star_setup_rom_reset_vec(machine, &s->soc[0], memmap[QUARD_STAR_MROM].base,
                              virt_memmap[QUARD_STAR_MROM].base,
                              virt_memmap[QUARD_STAR_MROM].size,
                              0x0, 0x0);
    uint32_t reset_vec[10] = {
        0x00000297,                  /* 1:  auipc  t0, %pcrel_hi(fw_dyn) */
        0x02828613,                  /*     addi   a2, t0, %pcrel_lo(1b) */
        0xf1402573,                  /*     csrr   a0, mhartid  */
        0,
        0,
        0x00028067,                  /*     jr     t0 */
        start_addr,                  /* start: .dword */
        start_addr_hi32,
        fdt_load_addr,               /* fdt_laddr: .dword */
        0x00000000,
    };
    if (riscv_is_32bit(harts)) {
        reset_vec[3] = 0x0202a583;   /*     lw     a1, 32(t0) */
        reset_vec[4] = 0x0182a283;   /*     lw     t0, 24(t0) */
    } else {
        reset_vec[3] = 0x0202b583;   /*     ld     a1, 32(t0) */
        reset_vec[4] = 0x0182b283;   /*     ld     t0, 24(t0) */
    }

编译运行

为了方便运行,我们再创建个run.sh脚本用于运行quard-star板

SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)

$SHELL_FOLDER/output/qemu/bin/qemu-system-riscv64 \
-M quard-star \
-m 1G \
-smp 8 \
-nographic --parallel none

执行后,输出如下,因为我们的代码会陷入循环,且没有串口打印等信息,所以只能看到qemu仿真器的终端,我们在仿真器终端输入info registers命令是可以看到cpu的一些相关寄存器值是正常的。在仿真终端输入q即可退出仿真。

QEMU 6.0.0 monitor - type 'help' for more information
(qemu) 

到这里本篇顺利完成。这一节主要是一下qemu的API接口的使用,还是比较容易理解,我们添加到板卡的资源还很少,只有cpu和内存相关,下一节我们将添加中断控制,串口,flash到板卡内部。

本教程的
github仓库:https://github.com/QQxiaoming/quard_star_tutorial
gitee仓库:https://gitee.com/QQxiaoming/quard_star_tutorial
本节所在tag:ch2