This is a quick write-up on qemu riscv emulation. Qemu provides several APIs to create user defined boards(in qemu lingo, it’s called machine) and devices in this board.
risc virt Board Link to heading
virt
machine is a basic machine to run riscv code with VIRTIO MMIO
RISC-V machine with 16550a UART and VirtIO MMIO
In hw/riscv/virt.c
, The initialization code for the virt machine. virt_machine_typeinfo
registers the type with qemu QOM using function virt_machine_class_init
.
static const TypeInfo virt_machine_typeinfo = {
.name = MACHINE_TYPE_NAME("virt"),
.parent = TYPE_MACHINE,
.class_init = virt_machine_class_init,
.instance_init = virt_machine_instance_init,
.instance_size = sizeof(RISCVVirtState),
.interfaces = (InterfaceInfo[]) {
{ TYPE_HOTPLUG_HANDLER },
{ }
},
};
static void virt_machine_init_register_types(void)
{
type_register_static(&virt_machine_typeinfo);
}
type_init(virt_machine_init_register_types)
Let’s get virt_machine_instance_init
out of the way.
static void virt_machine_instance_init(Object *obj)
{
RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
s->oem_id = g_strndup(ACPI_BUILD_APPNAME6, 6);
s->oem_table_id = g_strndup(ACPI_BUILD_APPNAME8, 8);
s->acpi = ON_OFF_AUTO_AUTO;
}
virt_machine_class_init
is called on class initialization as part of QOM. And registers virt_machine_init
for machine initialization and it’s does most of the heavy lifting.
static void virt_machine_class_init(ObjectClass *oc, void *data)
{
char str[128];
MachineClass *mc = MACHINE_CLASS(oc);
HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(oc);
mc->desc = "RISC-V VirtIO board";
mc->init = virt_machine_init;
It also registers some class properties but defining their setters/getters. Anyway moving on.
object_class_property_add_bool(oc, "aclint", virt_get_aclint,
virt_set_aclint);
object_class_property_set_description(oc, "aclint",
"Set on/off to enable/disable "
"emulating ACLINT devices");
object_class_property_add_str(oc, "aia", virt_get_aia,
virt_set_aia);
object_class_property_set_description(oc, "aia",
"Set type of AIA interrupt "
"conttoller. Valid values are "
"none, aplic, and aplic-imsic.");
object_class_property_add_str(oc, "aia-guests",
virt_get_aia_guests,
virt_set_aia_guests);
sprintf(str, "Set number of guest MMIO pages for AIA IMSIC. Valid value "
"should be between 0 and %d.", VIRT_IRQCHIP_MAX_GUESTS);
object_class_property_set_description(oc, "aia-guests", str);
object_class_property_add(oc, "acpi", "OnOffAuto",
virt_get_acpi, virt_set_acpi,
NULL, NULL);
object_class_property_set_description(oc, "acpi",
"Enable ACPI");
virt_machine_init Link to heading
virt_machine_init
does a lot of things, but at the end it registers virt_machine_done
to be called by qemu core to finish the initialization.
TODO
static void virt_machine_init(MachineState *machine)
{
...
...
s->machine_done.notify = virt_machine_done;
qemu_add_machine_init_done_notifier(&s->machine_done);
}
virt_machine_done Link to heading
In virt_machine_done
, The actual firmware is loaded and control is passed back to qemu to start execution loop.
const char *firmware_name = riscv_default_firmware_name(&s->soc[0]);
...
firmware_end_addr = riscv_find_and_load_firmware(machine, firmware_name,
start_addr, NULL);
Note, firmware is opensbi defined hw/riscv/boot.c
,
#define RISCV32_BIOS_BIN "opensbi-riscv32-generic-fw_dynamic.bin"
#define RISCV64_BIOS_BIN "opensbi-riscv64-generic-fw_dynamic.bin"
and riscv_load_firmware
is eventually called.
target_ulong riscv_load_firmware(const char *firmware_filename,
hwaddr firmware_load_addr,
symbol_fn_t sym_cb)
{
uint64_t firmware_entry, firmware_end;
ssize_t firmware_size;
g_assert(firmware_filename != NULL);
if (load_elf_ram_sym(firmware_filename, NULL, NULL, NULL,
&firmware_entry, NULL, &firmware_end, NULL,
0, EM_RISCV, 1, 0, NULL, true, sym_cb) > 0) {
return firmware_end;
}
firmware_size = load_image_targphys_as(firmware_filename,
firmware_load_addr,
current_machine->ram_size, NULL);
Back to virt_machine_done
where device tree is loaded
fdt_load_addr = riscv_compute_fdt_addr(memmap[VIRT_DRAM].base,
memmap[VIRT_DRAM].size,
machine);
riscv_load_fdt(fdt_load_addr, machine->fdt);
/* load the reset vector */
riscv_setup_rom_reset_vec(machine, &s->soc[0], start_addr,
virt_memmap[VIRT_MROM].base,
virt_memmap[VIRT_MROM].size, kernel_entry,
fdt_load_addr);