Timeline
Timeline
2025-11-21
- 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); machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS); 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_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:

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])) { /* 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"); } 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 value
riscv_default_firmware_name(&s->soc[0]) - start_addr is assigned
s->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 |
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 terminal,The 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 corresponding initialization location in the source code is:
target/riscv/cpu.c
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 | static void riscv_cpu_reset_hold(Object *obj, ResetType type){ uint8_t iprio; int i, irq, rdzero; 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); } 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); env->priv = PRV_U; env->senvcfg = 0; env->menvcfg = 0; /* 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; 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); }} |
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 */ |
Reference:
