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