Cover image for QEMU initialization process

QEMU initialization process

Words 2.9k
Views
Visitors
Timeline

Timeline

2025-11-21

  1. init
This article introduces the QEMU initialization process, focusing on the creation process of the RISC-V virt Machine. The article uses gdb to reverse-locate the source code, and according to the QOM mechanism, at class_init and instance_Set breakpoints at init, and observe that the main function first calls qemu_init, and then calls qemu_create_machine creates the machine, then first creates the class and then instantiates the QObject. Then it describes in detail the virt_machine_The function of the init function: creates CPU cores by cluster, initializes various devices, and generates a device tree (DTB) in memory for virt. The article also explains the loading timing of the OpenSBI firmware—after the entire virt Machine is ready, through the machine_done callback calls riscv_find_and_load_firmware loads the default firmware. Finally, the article explains the initialization process of the vCPU executing the first instruction, that is, env->pc is assigned the value of resetvec (the default value), and mentions the use of g

Environment

123456
wget https://download.qemu.org/qemu-10.1.2.tar.xztar xvJf qemu-10.1.2.tar.xzcd qemu-10.1.2mkdir -p output./configure --prefix=$PWD/output --target-list=aarch64-softmmu,riscv64-softmmu --enable-debugbear -- make -j$(nproc)

Create .clangd

123
CompileFlags:  Add: -Wno-unknown-warning-option  Remove: [-m*, -f*]

virt Machine initialization

The most convenient way is to use gdb to reverse-locate the source code.

According to the previous explanation of QOM, the virt Machine must belong to a QObject. We can set a breakpoint at the source code location of its class initialization or object instantiation to observe the call stack. Here, let’s first search for code related to typeinfo in the virt Machine source code:

virt_machine_typeinfo

123456789101112131415161718
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 = (const 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)

So, we canvirt_machine_class_init()andvirt_machine_instance_init()Set breakpoints respectively.

Use the following command to debug QEMU with gdb:

12345
$ gdb ./build/qemu-system-riscv64 -ex "set args -M virt -nographic"(gdb) b virt_machine_class_init(gdb) b virt_machine_instance_init(gdb) r...

virt_machine_class_init

The first thing we trace isvirt_machine_class_initfunction

hw/riscv/virt.c

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
static void virt_machine_class_init(ObjectClass *oc, const void *data){    MachineClass *mc = MACHINE_CLASS(oc);    HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(oc);    mc->desc = "RISC-V VirtIO board";    mc->init = virt_machine_init;    mc->max_cpus = VIRT_CPUS_MAX;    mc->default_cpu_type = TYPE_RISCV_CPU_BASE;    mc->block_default_type = IF_VIRTIO;    mc->no_cdrom = 1;    mc->pci_allow_0_address = true;    mc->possible_cpu_arch_ids = riscv_numa_possible_cpu_arch_ids;    mc->cpu_index_to_instance_props = riscv_numa_cpu_index_to_props;    mc->get_default_cpu_node_id = riscv_numa_get_default_cpu_node_id;    mc->numa_mem_supported = true;    /* platform instead of architectural choice */    mc->cpu_cluster_has_numa_boundary = true;    mc->default_ram_id = "riscv_virt_board.ram";    assert(!mc->get_hotplug_handler);    mc->get_hotplug_handler = virt_machine_get_hotplug_handler;    hc->plug = virt_machine_device_plug_cb;    machine_class_allow_dynamic_sysbus_dev(mc, TYPE_RAMFB_DEVICE);    machine_class_allow_dynamic_sysbus_dev(mc, TYPE_UEFI_VARS_SYSBUS);#ifdef CONFIG_TPM    machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS);#endif    object_class_property_add_bool(oc, "aclint", virt_get_aclint,                                   virt_set_aclint);    object_class_property_set_description(oc, "aclint",                                          "(TCG only) 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 "                                          "controller. Valid values are "                                          "none, aplic, and aplic-imsic.");    object_class_property_add_str(oc, "aia-guests",                                  virt_get_aia_guests,                                  virt_set_aia_guests);    {        g_autofree char *str =            g_strdup_printf("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");    object_class_property_add(oc, "iommu-sys", "OnOffAuto",                              virt_get_iommu_sys, virt_set_iommu_sys,                              NULL, NULL);    object_class_property_set_description(oc, "iommu-sys",                                          "Enable IOMMU platform device");}

Call stack:

virt_machine_init call stack
virt_machine_init call stack

virt_machine_instance_init

hw/riscv/virt.c

1234567891011
static void virt_machine_instance_init(Object *obj){    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);    virt_flash_create(s);    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;    s->iommu_sys = ON_OFF_AUTO_AUTO;}

Call stack:

virt_machine_instance_init
virt_machine_instance_init

Here we can see that the main function first calls qemu_init function, and then calls qemu_create_machine function to create the machine:

1
main() → qemu_init() → qemu_create_machine()

Then it first creates the class, and then instantiates the qobject, which is consistent with the process described earlier in the QOM explanation.

virt_machine_init

Forvirt_machine_class_init, which assigns the init of the defined MachineClass tovirt_machine_init

1234
MachineClass *mc = MACHINE_CLASS(oc);   HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(oc);mc->desc = "RISC-V VirtIO board";mc->init = virt_machine_init;

hw/riscv/virt.c

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
static void virt_machine_init(MachineState *machine){    RISCVVirtState *s = RISCV_VIRT_MACHINE(machine);    MemoryRegion *system_memory = get_system_memory();    MemoryRegion *mask_rom = g_new(MemoryRegion, 1);    DeviceState *mmio_irqchip, *virtio_irqchip, *pcie_irqchip;    int i, base_hartid, hart_count;    int socket_count = riscv_socket_count(machine);    s->memmap = virt_memmap;    /* Check socket count limit */    if (VIRT_SOCKETS_MAX < socket_count) {        error_report("number of sockets/nodes should be less than %d",            VIRT_SOCKETS_MAX);        exit(1);    }    if (!virt_aclint_allowed() && s->have_aclint) {        error_report("'aclint' is only available with TCG acceleration");        exit(1);    }    /* Initialize sockets */    mmio_irqchip = virtio_irqchip = pcie_irqchip = NULL;    for (i = 0; i < socket_count; i++) {        g_autofree char *soc_name = g_strdup_printf("soc%d", i);        if (!riscv_socket_check_hartids(machine, i)) {            error_report("discontinuous hartids in socket%d", i);            exit(1);        }        base_hartid = riscv_socket_first_hartid(machine, i);        if (base_hartid < 0) {            error_report("can't find hartid base for socket%d", i);            exit(1);        }        hart_count = riscv_socket_hart_count(machine, i);        if (hart_count < 0) {            error_report("can't find hart count for socket%d", i);            exit(1);        }        object_initialize_child(OBJECT(machine), soc_name, &s->soc[i],                                TYPE_RISCV_HART_ARRAY);        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_fatal);        if (virt_aclint_allowed() && s->have_aclint) {            if (s->aia_type == VIRT_AIA_TYPE_APLIC_IMSIC) {                /* Per-socket ACLINT MTIMER */                riscv_aclint_mtimer_create(s->memmap[VIRT_CLINT].base +                            i * RISCV_ACLINT_DEFAULT_MTIMER_SIZE,                        RISCV_ACLINT_DEFAULT_MTIMER_SIZE,                        base_hartid, hart_count,                        RISCV_ACLINT_DEFAULT_MTIMECMP,                        RISCV_ACLINT_DEFAULT_MTIME,                        RISCV_ACLINT_DEFAULT_TIMEBASE_FREQ, true);            } else {                /* Per-socket ACLINT MSWI, MTIMER, and SSWI */                riscv_aclint_swi_create(s->memmap[VIRT_CLINT].base +                            i * s->memmap[VIRT_CLINT].size,                        base_hartid, hart_count, false);                riscv_aclint_mtimer_create(s->memmap[VIRT_CLINT].base +                            i * s->memmap[VIRT_CLINT].size +                            RISCV_ACLINT_SWI_SIZE,                        RISCV_ACLINT_DEFAULT_MTIMER_SIZE,                        base_hartid, hart_count,                        RISCV_ACLINT_DEFAULT_MTIMECMP,                        RISCV_ACLINT_DEFAULT_MTIME,                        RISCV_ACLINT_DEFAULT_TIMEBASE_FREQ, true);                riscv_aclint_swi_create(s->memmap[VIRT_ACLINT_SSWI].base +                            i * s->memmap[VIRT_ACLINT_SSWI].size,                        base_hartid, hart_count, true);            }        } else if (tcg_enabled()) {            /* Per-socket SiFive CLINT */            riscv_aclint_swi_create(                    s->memmap[VIRT_CLINT].base + i * s->memmap[VIRT_CLINT].size,                    base_hartid, hart_count, false);            riscv_aclint_mtimer_create(s->memmap[VIRT_CLINT].base +                    i * s->memmap[VIRT_CLINT].size + RISCV_ACLINT_SWI_SIZE,                    RISCV_ACLINT_DEFAULT_MTIMER_SIZE, base_hartid, hart_count,                    RISCV_ACLINT_DEFAULT_MTIMECMP, RISCV_ACLINT_DEFAULT_MTIME,                    RISCV_ACLINT_DEFAULT_TIMEBASE_FREQ, true);        }        /* Per-socket interrupt controller */        if (s->aia_type == VIRT_AIA_TYPE_NONE) {            s->irqchip[i] = virt_create_plic(s->memmap, i,                                             base_hartid, hart_count);        } else {            s->irqchip[i] = virt_create_aia(s->aia_type, s->aia_guests,                                            s->memmap, i, base_hartid,                                            hart_count);        }        /* Try to use different IRQCHIP instance based device type */        if (i == 0) {            mmio_irqchip = s->irqchip[i];            virtio_irqchip = s->irqchip[i];            pcie_irqchip = s->irqchip[i];        }        if (i == 1) {            virtio_irqchip = s->irqchip[i];            pcie_irqchip = s->irqchip[i];        }        if (i == 2) {            pcie_irqchip = s->irqchip[i];        }    }    if (kvm_enabled() && virt_use_kvm_aia_aplic_imsic(s->aia_type)) {        kvm_riscv_aia_create(machine, IMSIC_MMIO_GROUP_MIN_SHIFT,                             VIRT_IRQCHIP_NUM_SOURCES, VIRT_IRQCHIP_NUM_MSIS,                             s->memmap[VIRT_APLIC_S].base,                             s->memmap[VIRT_IMSIC_S].base,                             s->aia_guests);    }    if (riscv_is_32bit(&s->soc[0])) {#if HOST_LONG_BITS == 64        /* limit RAM size in a 32-bit system */        if (machine->ram_size > 10 * GiB) {            machine->ram_size = 10 * GiB;            error_report("Limiting RAM size to 10 GiB");        }#endif        virt_high_pcie_memmap.base = VIRT32_HIGH_PCIE_MMIO_BASE;        virt_high_pcie_memmap.size = VIRT32_HIGH_PCIE_MMIO_SIZE;    } else {        virt_high_pcie_memmap.size = VIRT64_HIGH_PCIE_MMIO_SIZE;        virt_high_pcie_memmap.base = s->memmap[VIRT_DRAM].base +                                     machine->ram_size;        virt_high_pcie_memmap.base =            ROUND_UP(virt_high_pcie_memmap.base, virt_high_pcie_memmap.size);    }    /* register system main memory (actual RAM) */    memory_region_add_subregion(system_memory, s->memmap[VIRT_DRAM].base,                                machine->ram);    /* boot rom */    memory_region_init_rom(mask_rom, NULL, "riscv_virt_board.mrom",                           s->memmap[VIRT_MROM].size, &error_fatal);    memory_region_add_subregion(system_memory, s->memmap[VIRT_MROM].base,                                mask_rom);    /*     * Init fw_cfg. Must be done before riscv_load_fdt, otherwise the     * device tree cannot be altered and we get FDT_ERR_NOSPACE.     */    s->fw_cfg = create_fw_cfg(machine, s->memmap[VIRT_FW_CFG].base);    rom_set_fw(s->fw_cfg);    /* SiFive Test MMIO device */    sifive_test_create(s->memmap[VIRT_TEST].base);    /* VirtIO MMIO devices */    for (i = 0; i < VIRTIO_COUNT; i++) {        sysbus_create_simple("virtio-mmio",            s->memmap[VIRT_VIRTIO].base + i * s->memmap[VIRT_VIRTIO].size,            qdev_get_gpio_in(virtio_irqchip, VIRTIO_IRQ + i));    }    gpex_pcie_init(system_memory, pcie_irqchip, s);    create_platform_bus(s, mmio_irqchip);    serial_mm_init(system_memory, s->memmap[VIRT_UART0].base,        0, qdev_get_gpio_in(mmio_irqchip, UART0_IRQ), 399193,        serial_hd(0), DEVICE_LITTLE_ENDIAN);    sysbus_create_simple("goldfish_rtc", s->memmap[VIRT_RTC].base,        qdev_get_gpio_in(mmio_irqchip, RTC_IRQ));    for (i = 0; i < ARRAY_SIZE(s->flash); i++) {        /* Map legacy -drive if=pflash to machine properties */        pflash_cfi01_legacy_drive(s->flash[i],                                  drive_get(IF_PFLASH, 0, i));    }    virt_flash_map(s, system_memory);    /* load/create device tree */    if (machine->dtb) {        machine->fdt = load_device_tree(machine->dtb, &s->fdt_size);        if (!machine->fdt) {            error_report("load_device_tree() failed");            exit(1);        }    } else {        create_fdt(s);    }    if (virt_is_iommu_sys_enabled(s)) {        DeviceState *iommu_sys = qdev_new(TYPE_RISCV_IOMMU_SYS);        object_property_set_uint(OBJECT(iommu_sys), "addr",                                 s->memmap[VIRT_IOMMU_SYS].base,                                 &error_fatal);        object_property_set_uint(OBJECT(iommu_sys), "base-irq",                                 IOMMU_SYS_IRQ,                                 &error_fatal);        object_property_set_link(OBJECT(iommu_sys), "irqchip",                                 OBJECT(mmio_irqchip),                                 &error_fatal);        sysbus_realize_and_unref(SYS_BUS_DEVICE(iommu_sys), &error_fatal);    }    s->machine_done.notify = virt_machine_done;    qemu_add_machine_init_done_notifier(&s->machine_done);}

Although this function looks long, what it actually does is very simple:

1234
virt_machine_init()  → riscv_socket_count()  // Create CPU Socket  → memory_region_init() // Initialize memory region  → create_fdt()         // Generate device tree (DTB)

CPU socket mainly allows creating multiple groups of CPU cores based on clusters., then initialize various devices according to the address space, and finally generate a device tree (DTB) in memory for virt, making it convenient to run the Linux kernel.

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
┌───────────────────────────────────────────────────────────────┐│                         RISC-V VIRT Machine                    ││                    (created by virt_machine_init)              │└───────────────────────────────────────────────────────────────┘   CPU & Interrupt Subsystem   ────────────────────────────────────────────────────────────────   ┌───────────────┐    ┌───────────────┐    ┌───────────────┐   │  Socket 0      │    │  Socket 1      │    │  Socket 2      │ ...   │ (Hart Array)   │    │ (Hart Array)   │    │ (Hart Array)   │   │ harts 0..X     │    │ harts N..M     │    │ ...            │   └──────┬────────┘    └──────┬────────┘    └───────┬────────┘          │                     │                     │          │                     │                     │     ┌────▼─────┐          ┌────▼─────┐          ┌────▼─────┐     │ ACLINT / │          │ ACLINT / │          │ ACLINT / │     │  CLINT   │          │  CLINT   │          │  CLINT   │     └────┬─────┘          └────┬─────┘          └─────┬────┘          │                     │                      │          ▼                     ▼                      ▼     ┌───────────┐        ┌───────────┐         ┌───────────┐     │  IRQCHIP  │        │  IRQCHIP  │         │  IRQCHIP  │     │ PLIC/APLIC│        │ PLIC/APLIC│         │ PLIC/APLIC│     │ + IMSIC    │        │ + IMSIC   │         │ + IMSIC   │     └───────────┘        └───────────┘         └───────────┘   (其中:)      • Socket0 IRQCHIP → 负责 MMIO + VirtIO + PCIe      • Socket1 IRQCHIP → 负责 VirtIO + PCIe      • Socket2 IRQCHIP → 负责 PCIe   Memory Subsystem   ────────────────────────────────────────────────────────────────   Physical Address Space (system_memory)   ┌───────────────────────────────────────────────────────────────┐   │ 0x0000_0000 ───────────────────────────────────────────────┐   │   │                       MROM (boot ROM)                      │   │   ├────────────────────────────────────────────────────────────┤   │   │                       DRAM (RAM)                           │   │   ├────────────────────────────────────────────────────────────┤   │   │                       Device MMIO                          │   │   │   • CLINT / ACLINT                                         │   │   │   • PLIC / APLIC + IMSIC                                   │   │   │   • UART0                                                  │   │   │   • RTC                                                    │   │   │   • fw_cfg                                                 │   │   │   • flash                                                  │   │   │   • VirtIO-mmio buses                                      │   │   │   • PCIE ECAM + MMIO window                                │   │   └───────────────────────────────────────────────────────────────┘   Platform & IO Devices   ────────────────────────────────────────────────────────────────    ┌───────────────────────┐    │  UART0 (serial)       │ → IRQ via mmio_irqchip    ├───────────────────────┤    │  RTC (goldfish)       │ → IRQ via mmio_irqchip    ├───────────────────────┤    │  Test Device          │ (sifive_test)    ├───────────────────────┤    │  Flash (pflash)       │    ├───────────────────────┤    │  VirtIO-MMIO(0..7)    │ → IRQ via virtio_irqchip    ├───────────────────────┤    │  PCIE Root Complex    │ → IRQ via pcie_irqchip    └───────────────────────┘   Firmware and Configuration   ────────────────────────────────────────────────────────────────   • fw_cfg         — 用于传递 kernel / initrd / cmdline   • FDT (DTB)      — 机器设备树,描述上述资源   • machine_done   — 初始化结束回调

So, where is the OpenSBI binary program loaded?

Load guest program

One thing is clear: the device model storing OpenSBI must be created and initialized before the guest program binary data can be loaded. Following this idea, we can find the following code (in fact, QEMU starts loading the guest program only after the entire virt Machine is ready, and it is implemented in the machine_done callback):

virt_machine_done

1234567891011121314
static void virt_machine_done(Notifier *notifier, void *data){    RISCVVirtState *s = container_of(notifier, RISCVVirtState,                                     machine_done);    MachineState *machine = MACHINE(s);    hwaddr start_addr = s->memmap[VIRT_DRAM].base;    target_ulong firmware_end_addr, kernel_start_addr;    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);	....}
  • firmware_name is assigned the valueriscv_default_firmware_name(&s->soc[0])
  • start_addr is assigneds->memmap[VIRT_DRAM].base

riscv_default_firmware_name

hw/riscv/boot.c

123456789
const char *riscv_default_firmware_name(RISCVHartArrayState *harts){    if (riscv_is_32bit(harts)) {        return RISCV32_BIOS_BIN;    }    return RISCV64_BIOS_BIN;}

Beforeinclude/hw/riscv/boot.his defined in

12
#define RISCV32_BIOS_BIN    "opensbi-riscv32-generic-fw_dynamic.bin"#define RISCV64_BIOS_BIN    "opensbi-riscv64-generic-fw_dynamic.bin"

Therefore firmware_name is assigned the valueopensbi-riscv64-generic-fw_dynamic.bin

riscv_find_and_load_firmware

riscv_find_and_load_firmwarethe second parameter is default_machine_firmware, string type, and what we pass in isopensbi-riscv64-generic-fw_dynamic.bin, this function callsriscv_find_firmwareget the firmware string

hw/riscv/boot.c

1234567891011121314151617181920
target_ulong riscv_find_and_load_firmware(MachineState *machine,                                          const char *default_machine_firmware,                                          hwaddr *firmware_load_addr,                                          symbol_fn_t sym_cb){    char *firmware_filename;    target_ulong firmware_end_addr = *firmware_load_addr;    firmware_filename = riscv_find_firmware(machine->firmware,                                            default_machine_firmware);    if (firmware_filename) {        /* If not "none" load the firmware */        firmware_end_addr = riscv_load_firmware(firmware_filename,                                                firmware_load_addr, sym_cb);        g_free(firmware_filename);    }    return firmware_end_addr;}

riscv_find_firmware

hw/riscv/boot.c

123456789101112131415161718
char *riscv_find_firmware(const char *firmware_filename,                          const char *default_machine_firmware){    char *filename = NULL;    if ((!firmware_filename) || (!strcmp(firmware_filename, "default"))) {        /*         * The user didn't specify -bios, or has specified "-bios default".         * That means we are going to load the OpenSBI binary included in         * the QEMU source.         */        filename = riscv_find_bios(default_machine_firmware);    } else if (strcmp(firmware_filename, "none")) {        filename = riscv_find_bios(firmware_filename);    }    return filename;}

Here we see that if we do not-biosspecify the firmware, it will load the default_machine_firmware, that is, OpenSBI.

riscv_load_firmware

riscv_load_firmwarewill load the firmware

hw/riscv/boot.c

123456789101112131415161718192021222324252627
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) {        *firmware_load_addr = firmware_entry;        return firmware_end;    }    firmware_size = load_image_targphys_as(firmware_filename,                                           *firmware_load_addr,                                           current_machine->ram_size, NULL);    if (firmware_size > 0) {        return *firmware_load_addr + firmware_size;    }    error_report("could not load firmware '%s'", firmware_filename);    exit(1);}

The first instruction executed by the vCPU

Run qemu-system-riscv64

1
$ ./build/qemu-system-riscv64 -M virt -s -S -nographic

This command makes QEMU create a virt Machine, running in nographic mode,serial output to the terminalThe default BIOS uses OpenSBI, and enables the gdbstub remote debugging feature, allowing riscv64-gdb to debug the guest program, with the default port number 1234, and stops at the first instruction, waiting for gdb to connect.

then

1
$ gdb -ex "set architecture riscv64" -ex "target remote localhost:1234"

The vCPU's first instruction
The vCPU's first instruction

The corresponding initialization location in the source code is:

target/riscv/cpu.c

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
static void riscv_cpu_reset_hold(Object *obj, ResetType type){#ifndef CONFIG_USER_ONLY    uint8_t iprio;    int i, irq, rdzero;#endif    CPUState *cs = CPU(obj);    RISCVCPU *cpu = RISCV_CPU(cs);    RISCVCPUClass *mcc = RISCV_CPU_GET_CLASS(obj);    CPURISCVState *env = &cpu->env;    if (mcc->parent_phases.hold) {        mcc->parent_phases.hold(obj, type);    }#ifndef CONFIG_USER_ONLY    env->misa_mxl = mcc->def->misa_mxl_max;    env->priv = PRV_M;    env->mstatus &= ~(MSTATUS_MIE | MSTATUS_MPRV);    if (env->misa_mxl > MXL_RV32) {        /*         * The reset status of SXL/UXL is undefined, but mstatus is WARL         * and we must ensure that the value after init is valid for read.         */        env->mstatus = set_field(env->mstatus, MSTATUS64_SXL, env->misa_mxl);        env->mstatus = set_field(env->mstatus, MSTATUS64_UXL, env->misa_mxl);        if (riscv_has_ext(env, RVH)) {            env->vsstatus = set_field(env->vsstatus,                                      MSTATUS64_SXL, env->misa_mxl);            env->vsstatus = set_field(env->vsstatus,                                      MSTATUS64_UXL, env->misa_mxl);            env->mstatus_hs = set_field(env->mstatus_hs,                                        MSTATUS64_SXL, env->misa_mxl);            env->mstatus_hs = set_field(env->mstatus_hs,                                        MSTATUS64_UXL, env->misa_mxl);        }        if (riscv_cpu_cfg(env)->ext_smdbltrp) {            env->mstatus = set_field(env->mstatus, MSTATUS_MDT, 1);        }    }    env->mcause = 0;    env->miclaim = MIP_SGEIP;    env->pc = env->resetvec;//Set the PC value to the location of the first instruction.    env->bins = 0;    env->two_stage_lookup = false;    env->menvcfg = (cpu->cfg.ext_svpbmt ? MENVCFG_PBMTE : 0) |                   (!cpu->cfg.ext_svade && cpu->cfg.ext_svadu ?                    MENVCFG_ADUE : 0);    env->henvcfg = 0;    /* Initialized default priorities of local interrupts. */    for (i = 0; i < ARRAY_SIZE(env->miprio); i++) {        iprio = riscv_cpu_default_priority(i);        env->miprio[i] = (i == IRQ_M_EXT) ? 0 : iprio;        env->siprio[i] = (i == IRQ_S_EXT) ? 0 : iprio;        env->hviprio[i] = 0;    }    i = 0;    while (!riscv_cpu_hviprio_index2irq(i, &irq, &rdzero)) {        if (!rdzero) {            env->hviprio[irq] = env->miprio[irq];        }        i++;    }    /*     * Bits 10, 6, 2 and 12 of mideleg are read only 1 when the Hypervisor     * extension is enabled.     */    if (riscv_has_ext(env, RVH)) {        env->mideleg |= HS_MODE_INTERRUPTS;    }    /*     * Clear mseccfg and unlock all the PMP entries upon reset.     * This is allowed as per the priv and smepmp specifications     * and is needed to clear stale entries across reboots.     */    if (riscv_cpu_cfg(env)->ext_smepmp) {        env->mseccfg = 0;    }    pmp_unlock_entries(env);#else    env->priv = PRV_U;    env->senvcfg = 0;    env->menvcfg = 0;#endif    /* on reset elp is clear */    env->elp = false;    /* on reset ssp is set to 0 */    env->ssp = 0;    env->xl = riscv_cpu_mxl(env);    cs->exception_index = RISCV_EXCP_NONE;    env->load_res = -1;    set_default_nan_mode(1, &env->fp_status);    /* Default NaN value: sign bit clear, frac msb set */    set_float_default_nan_pattern(0b01000000, &env->fp_status);    env->vill = true;#ifndef CONFIG_USER_ONLY    if (cpu->cfg.debug) {        riscv_trigger_reset_hold(env);    }    if (cpu->cfg.ext_smrnmi) {        env->rnmip = 0;        env->mnstatus = set_field(env->mnstatus, MNSTATUS_NMIE, false);    }    if (kvm_enabled()) {        kvm_riscv_reset_vcpu(cpu);    }#endif}

It can be seen that env->pc is assigned the value of env->resetvec, and the default value of resetvec is:

123
//cpu_bits.h/* Default Reset Vector address */#define DEFAULT_RSTVEC      0x1000

Reference:

Loading comments…