Cover image for QEMU Hardware Modeling

QEMU Hardware Modeling

Words 6k
Views
Visitors
Timeline

Timeline

2025-11-23

  1. init
This article introduces the core process and key technical points of QEMU hardware modeling. It first outlines the six stages of QEMU virtual machine creation: command-line parsing, Machine type selection and registration, object instantiation, hardware device initialization, accelerator and CPU initialization, and starting the virtual machine, covering key components such as the main() function, MachineClass, MachineState, and QOM. Then, using the PL011 serial port and the edu teaching device as examples, it analyzes in detail the implementation methods of peripheral device modeling, including device type definition, state structure design, class initialization, MMIO mapping, FIFO management, interrupt generation, DMA transfer, and background thread asynchronous processing. The article also deeply explores the complete interrupt modeling flow, from device-level triggering and PLIC arbitration to CPU interrupt injection and Guest OS handling, and mentions debugging and tracing methods. Overall, this article systematically demonstrates how QEMU accurately simulates hardware behavior through the QOM object model, providing support for full-stack development from bare-metal programs to the Linux kernel.

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*]

gdb

1
$ gdb -args ./build/qemu-system-riscv64 -M virt -device edu,id=edu1 -nographic

Machine Modeling

The core process of QEMU creating a virtual machine can be divided into 6 stages, each involving specific components and operations.

Command-line parsing

Entry function: main() Key operations:

  • Parse the -M parameter to specify the machine type (e.g., virt)
  • Initialize the QOM (QEMU Object Model) type system
  • Create the global state machine MachineState

system/main.c

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
#include "qemu/osdep.h"#include "qemu-main.h"#include "qemu/main-loop.h"#include "system/replay.h"#include "system/system.h"static void *qemu_default_main(void *opaque){    int status;    replay_mutex_lock();    bql_lock();    status = qemu_main_loop();    qemu_cleanup(status);    bql_unlock();    replay_mutex_unlock();    exit(status);}int (*qemu_main)(void);#ifdef CONFIG_DARWINstatic int os_darwin_cfrunloop_main(void){    CFRunLoopRun();    g_assert_not_reached();}int (*qemu_main)(void) = os_darwin_cfrunloop_main;#endifint main(int argc, char **argv){    qemu_init(argc, argv);    bql_unlock();    replay_mutex_unlock();    if (qemu_main) {        QemuThread main_loop_thread;        qemu_thread_create(&main_loop_thread, "qemu_main",                           qemu_default_main, NULL, QEMU_THREAD_DETACHED);        return qemu_main();    } else {        qemu_default_main(NULL);        g_assert_not_reached();    }}

system/vl.c

12345678910111213141516171819
void qemu_init(int argc, char **argv) {    ...    machine_opts_dict = qdict_new(); // Create the machine parameter dictionary    ...    case QEMU_OPTION_M: // Parse the -M parameter            case QEMU_OPTION_machine:                {                    bool help;                    keyval_parse_into(machine_opts_dict, optarg, "type", &help, &error_fatal);                    if (help) {                        machine_help_func(machine_opts_dict);                        exit(EXIT_SUCCESS);                    }                    break;                }    ..    qemu_create_machine(machine_opts_dict); // Create the machine instance}

Machine type selection and registration

Core component: MachineClass (machine class description)

Key operations:

  • Match the user-specified machine type via select_machine()
  • Look up the corresponding TypeImpl from the global type table type_table
12345678910111213141516171819202122232425262728293031323334
// system/vl.cstatic MachineClass *select_machine(QDict *qdict, Error **errp){    ERRP_GUARD();    const char *machine_type = qdict_get_try_str(qdict, "type");    g_autoptr(GSList) machines = object_class_get_list(TYPE_MACHINE, false);    MachineClass *machine_class = NULL;    if (machine_type) {        machine_class = find_machine(machine_type, machines);        if (!machine_class) {            error_setg(errp, "unsupported machine type: \"%s\"", machine_type);        }        qdict_del(qdict, "type");    } else {        machine_class = find_default_machine(machines);        if (!machine_class) {            error_setg(errp, "No machine specified, and there is no default");        }    }    if (!machine_class) {        error_append_hint(errp,                          "Use -machine help to list supported machines\n");    }    return machine_class;}// qom/object.cGSList *object_class_get_list(const char *implements_type) {    g_hash_table_foreach(type_table, object_class_foreach_tramp, &data);    // Iterate over all registered machine types}

Machine object instantiation

Core component: MachineState (machine runtime state)

Key operations:

  • Through QOM’s object_new_with_class() instantiates the machine
  • Initialize basic properties such as memory and CPU topology
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
// system/vl.cstatic void qemu_create_machine(QDict *qdict){    MachineClass *machine_class = select_machine(qdict, &error_fatal);    object_set_machine_compat_props(machine_class->compat_props);    current_machine = MACHINE(object_new_with_class(OBJECT_CLASS(machine_class)));    object_property_add_child(object_get_root(), "machine",                              OBJECT(current_machine));    qemu_create_machine_containers(OBJECT(current_machine));    object_property_add_child(machine_get_container("unattached"),                              "sysbus", OBJECT(sysbus_get_default()));    if (machine_class->minimum_page_bits) {        if (!set_preferred_target_page_bits(machine_class->minimum_page_bits)) {            /* This would be a board error: specifying a minimum smaller than             * a target's compile-time fixed setting.             */            g_assert_not_reached();        }    }    cpu_exec_init_all();    if (machine_class->hw_version) {        qemu_set_hw_version(machine_class->hw_version);    }    /*     * Get the default machine options from the machine if it is not already     * specified either by the configuration file or by the command line.     */    if (machine_class->default_machine_opts) {        QDict *default_opts =            keyval_parse(machine_class->default_machine_opts, NULL, NULL,                         &error_abort);        qemu_apply_legacy_machine_options(default_opts);        object_set_properties_from_keyval(OBJECT(current_machine), default_opts,                                          false, &error_abort);        qobject_unref(default_opts);    }}// hw/core/machine.cstatic void machine_initfn(Object *obj){    MachineState *ms = MACHINE(obj);    MachineClass *mc = MACHINE_GET_CLASS(obj);    ms->dump_guest_core = true;    ms->mem_merge = (QEMU_MADV_MERGEABLE != QEMU_MADV_INVALID);    ms->enable_graphics = true;    ms->kernel_cmdline = g_strdup("");    ms->ram_size = mc->default_ram_size; // Default memory size    ms->maxram_size = mc->default_ram_size;    if (mc->nvdimm_supported) {        ms->nvdimms_state = g_new0(NVDIMMState, 1);        object_property_add_bool(obj, "nvdimm",                                 machine_get_nvdimm, machine_set_nvdimm);        object_property_set_description(obj, "nvdimm",                                        "Set on/off to enable/disable "                                        "NVDIMM instantiation");        object_property_add_str(obj, "nvdimm-persistence",                                machine_get_nvdimm_persistence,                                machine_set_nvdimm_persistence);        object_property_set_description(obj, "nvdimm-persistence",                                        "Set NVDIMM persistence"                                        "Valid values are cpu, mem-ctrl");    }    if (mc->cpu_index_to_instance_props && mc->get_default_cpu_node_id) {        ms->numa_state = g_new0(NumaState, 1);        object_property_add_bool(obj, "hmat",                                 machine_get_hmat, machine_set_hmat);        object_property_set_description(obj, "hmat",                                        "Set on/off to enable/disable "                                        "ACPI Heterogeneous Memory Attribute "                                        "Table (HMAT)");    }    /* SPCR */    ms->acpi_spcr_enabled = true;    object_property_add_bool(obj, "spcr", machine_get_spcr, machine_set_spcr);    object_property_set_description(obj, "spcr",                                   "Set on/off to enable/disable "                                   "ACPI Serial Port Console Redirection "                                   "Table (spcr)");    /* default to mc->default_cpus */    ms->smp.cpus = mc->default_cpus; // Default CPU count    ms->smp.max_cpus = mc->default_cpus;    ms->smp.drawers = 1;    ms->smp.books = 1;    ms->smp.sockets = 1;    ms->smp.dies = 1;    ms->smp.clusters = 1;    ms->smp.modules = 1;    ms->smp.cores = 1;    ms->smp.threads = 1;    for (int i = 0; i < CACHE_LEVEL_AND_TYPE__MAX; i++) {        ms->smp_cache.props[i].cache = (CacheLevelAndType)i;        ms->smp_cache.props[i].topology = CPU_TOPOLOGY_LEVEL_DEFAULT;    }    machine_copy_boot_config(ms, &(BootConfiguration){ 0 });}

Hardware device initialization

Core phase: machine_run_board_init()

Key operations:

  • Call the machine-specific init() method (e.g., RISC-V virt’s virt_machine_init())
  • Initialize core components such as CPU, memory controller, and bus
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
// system/vl.cvoid machine_run_board_init(MachineState *machine, const char *mem_path, Error **errp){    ERRP_GUARD();    MachineClass *machine_class = MACHINE_GET_CLASS(machine);    /* This checkpoint is required by replay to separate prior clock       reading from the other reads, because timer polling functions query       clock values from the log. */    replay_checkpoint(CHECKPOINT_INIT);    if (!xen_enabled()) {        /* On 32-bit hosts, QEMU is limited by virtual address space */        if (machine->ram_size > (2047 << 20) && HOST_LONG_BITS == 32) {            error_setg(errp, "at most 2047 MB RAM can be simulated");            return;        }    }    if (machine->memdev) {        ram_addr_t backend_size = object_property_get_uint(OBJECT(machine->memdev),                                                           "size",  &error_abort);        if (backend_size != machine->ram_size) {            error_setg(errp, "Machine memory size does not match the size of the memory backend");            return;        }    } else if (machine_class->default_ram_id && machine->ram_size &&               numa_uses_legacy_mem()) {        if (object_property_find(object_get_objects_root(),                                 machine_class->default_ram_id)) {            error_setg(errp, "object's id '%s' is reserved for the default"                " RAM backend, it can't be used for any other purposes",                machine_class->default_ram_id);            error_append_hint(errp,                "Change the object's 'id' to something else or disable"                " automatic creation of the default RAM backend by setting"                " 'memory-backend=%s' with '-machine'.\n",                machine_class->default_ram_id);            return;        }        if (!machine_class->create_default_memdev(current_machine, mem_path,                                                  errp)) {            return;        }    }    if (machine->numa_state) {        numa_complete_configuration(machine);        if (machine->numa_state->num_nodes) {            machine_numa_finish_cpu_init(machine);            if (machine_class->cpu_cluster_has_numa_boundary) {                validate_cpu_cluster_to_numa_boundary(machine);            }        }    }    if (!machine->ram && machine->memdev) {        machine->ram = machine_consume_memdev(machine, machine->memdev);    }    /* Check if the CPU type is supported */    if (machine->cpu_type && !is_cpu_type_supported(machine, errp)) {        return;    }    if (machine->cgs) {        /*         * With confidential guests, the host can't see the real         * contents of RAM, so there's no point in it trying to merge         * areas.         */        machine_set_mem_merge(OBJECT(machine), false, &error_abort);        /*         * Virtio devices can't count on directly accessing guest         * memory, so they need iommu_platform=on to use normal DMA         * mechanisms.  That requires also disabling legacy virtio         * support for those virtio pci devices which allow it.         */        object_register_sugar_prop(TYPE_VIRTIO_PCI, "disable-legacy",                                   "on", true);        object_register_sugar_prop(TYPE_VIRTIO_DEVICE, "iommu_platform",                                   "on", false);    }    accel_init_interfaces(ACCEL_GET_CLASS(machine->accelerator));    machine_class->init(machine); // Call the machine-specific initialization function    phase_advance(PHASE_MACHINE_INITIALIZED);}// hw/riscv/virt.c (RISC-V example)static void virt_machine_init(MachineState *machine) {    // ...    // 1. Initialize CPU topology    for (int i = 0; i < smp_cpus; i++) {        object_initialize_child(OBJECT(machine), "cpu", &s->soc[i], CPU_TYPE);    }	//...    // 2. Initialize memory system    memory_region_init_ram(&s->ram, "riscv_virt_board.ram", ram_size);	//...   // 3. Initialize peripheral bus    sysbus_init_mmio(SYS_BUS_DEVICE(&s->plic), &s->plic_mmio);	//...    // 4. Create device tree (FDT)    create_fdt(s);    //...}

Accelerator and CPU initialization

Core components: AccelState and CPUState

Key operations:

  • Select KVM/TCG accelerator based on the -accel parameter
  • Create vCPU threads and bind them to physical CPUs
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
// accel/accel-system.cint accel_init_machine(AccelState *accel, MachineState *ms){    AccelClass *acc = ACCEL_GET_CLASS(accel);    int ret;    ms->accelerator = accel;    *(acc->allowed) = true;    ret = acc->init_machine(accel, ms);// Initialize accelerator    if (ret < 0) {        ms->accelerator = NULL;        *(acc->allowed) = false;        object_unref(OBJECT(accel));    } else {        object_set_accelerator_compat_props(acc->compat_props);    }    return ret;}// accel/kvm/kvm-all.c (KVM example)static void kvm_init(MachineState *ms) {    kvm_state->fd = open("/dev/kvm", O_RDWR); // Open KVM device    kvm_state->vmfd = ioctl(kvm_state->fd, KVM_CREATE_VM); // Create virtual machine}// system/cpus.cvoid qemu_init_vcpu(CPUState *cpu){    MachineState *ms = MACHINE(qdev_get_machine());    cpu->nr_threads =  ms->smp.threads;    cpu->stopped = true;    cpu->random_seed = qemu_guest_random_seed_thread_part1();    if (!cpu->as) {        /* If the target cpu hasn't set up any address spaces itself,         * give it the default one.         */        cpu->num_ases = 1;        cpu_address_space_init(cpu, 0, "cpu-memory", cpu->memory);    }    /* accelerators all implement the AccelOpsClass */    g_assert(cpus_accel != NULL && cpus_accel->create_vcpu_thread != NULL);    cpus_accel->create_vcpu_thread(cpu);    while (!cpu->created) {        qemu_cond_wait(&qemu_cpu_cond, &bql);    }}

Start virtual machine

Final phase: qemu_main_loop()

qemu_Load firmware in init
qemu_Load firmware in init

Enter the final phase qemu_main_loop
Enter the final phase qemu_main_loop

system/runstate.c

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
int qemu_main_loop(void){    int status = EXIT_SUCCESS;    while (!main_loop_should_exit(&status)) {        main_loop_wait(false);    }    return status;}static bool main_loop_should_exit(int *status){    RunState r;    ShutdownCause request;    if (qemu_debug_requested()) {        vm_stop(RUN_STATE_DEBUG);    }    if (qemu_suspend_requested()) {        qemu_system_suspend();    }    request = qemu_shutdown_requested();    if (request) {        qemu_kill_report();        qemu_system_shutdown(request);        if (shutdown_action == SHUTDOWN_ACTION_PAUSE) {            vm_stop(RUN_STATE_SHUTDOWN);        } else {            if (shutdown_exit_code != EXIT_SUCCESS) {                *status = shutdown_exit_code;            } else if (request == SHUTDOWN_CAUSE_GUEST_PANIC &&                panic_action == PANIC_ACTION_EXIT_FAILURE) {                *status = EXIT_FAILURE;            }            return true;        }    }    request = qemu_reset_requested();    if (request) {        pause_all_vcpus();        qemu_system_reset(request);        resume_all_vcpus();        /*         * runstate can change in pause_all_vcpus()         * as iothread mutex is unlocked         */        if (!runstate_check(RUN_STATE_RUNNING) &&                !runstate_check(RUN_STATE_INMIGRATE) &&                !runstate_check(RUN_STATE_FINISH_MIGRATE)) {            runstate_set(RUN_STATE_PRELAUNCH);        }    }    if (qemu_wakeup_requested()) {        pause_all_vcpus();        qemu_system_wakeup();        notifier_list_notify(&wakeup_notifiers, &wakeup_reason);        wakeup_reason = QEMU_WAKEUP_REASON_NONE;        resume_all_vcpus();        qapi_event_send_wakeup();    }    if (qemu_powerdown_requested()) {        qemu_system_powerdown();    }    if (qemu_vmstop_requested(&r)) {        vm_stop(r);    }    return false;}

Peripheral Modeling (PL011 UART)

The modeling of the PL011 UART in QEMU fully demonstrates the core process of hardware peripheral emulation:

  • Type system integration: implements the device class inheritance hierarchy through QOM
  • Accurate state modeling: full-cycle management of registers, FIFO, and interrupt state
  • Hardware interface implementation: MMIO callbacks handle CPU access, character backend connects to host I/O
  • Interrupt system integration: works with the platform interrupt controller (PLIC)
  • Device lifecycle: implements the complete lifecycle such as reset, migration, and destruction

Serial port
Serial port

Device type definition

Core mechanism: QEMU Object Model (QOM)

Key operations:

  • Define the TypeInfo structure to describe the device type
  • Implement class initialization (class_init) and instance initialization (instance_init)
  • Register into the global type system
12345678910111213141516
 // hw/char/pl011.cstatic const TypeInfo pl011_arm_info = {    .name          = TYPE_PL011,    .parent        = TYPE_SYS_BUS_DEVICE,    .instance_size = sizeof(PL011State),    .instance_init = pl011_init,    .class_init    = pl011_class_init,};static void pl011_register_types(void){    type_register_static(&pl011_arm_info);    type_register_static(&pl011_luminary_info); // pl011_luminary_info (Luminary Micro special version, Luminary was acquired by TI, an early ARM SoC)}type_init(pl011_register_types)

Device state structure design

Core component: PL011State

Key fields:

  • Register state (lcr, imsc, etc.)
  • FIFO buffer
  • Interrupt signal line
  • Character backend (CharBackend)
1234567891011121314151617181920212223242526272829303132
// hw/char/pl011.cstruct PL011State {    SysBusDevice parent_obj;    MemoryRegion iomem;    uint32_t flags;    uint32_t lcr;  // Line control register    uint32_t rsr;  // Receive status/error clear register    uint32_t cr;   // Control register    uint32_t dmacr;    uint32_t int_enabled;    uint32_t int_level;    uint32_t read_fifo[PL011_FIFO_DEPTH]; /* FIFO buffer */    uint32_t ilpr;    uint32_t ibrd;    uint32_t fbrd;    uint32_t ifl;    int read_pos;    int read_count;    int read_trigger;    CharBackend chr; // Connect host terminal or file    qemu_irq irq[6]; // Interrupt signal lines (PL011 supports 6 interrupt sources)    Clock *clk;    bool migrate_clk;    const unsigned char *id;    /*     * Since some users embed this struct directly, we must     * ensure that the C struct is at least as big as the Rust one.     */    uint8_t padding_for_rust[16];};

Device class initialization

Core tasks:

  • Set device properties

  • Bind realize and reset methods

  • Define device migration support

12345678910111213141516
// hw/char/pl011.cstatic void pl011_class_init(ObjectClass *oc, const void *data){    DeviceClass *dc = DEVICE_CLASS(oc);    dc->realize = pl011_realize;    device_class_set_legacy_reset(dc, pl011_reset);    dc->vmsd = &vmstate_pl011;    device_class_set_props(dc, pl011_properties);}static const Property pl011_properties[] = {    DEFINE_PROP_CHR("chardev", PL011State, chr),    DEFINE_PROP_BOOL("migrate-clk", PL011State, migrate_clk, true),};

Device instantiation and hardware connection

Core operations:

  • Initialize memory region (MemoryRegion)
  • Register MMIO callback functions
  • Connect interrupt signals
  • Bind character device backend
123456789101112131415161718192021222324252627282930
// hw/char/pl011.cstatic void pl011_init(Object *obj){    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);    PL011State *s = PL011(obj);    int i;    /* Initialize memory region */    memory_region_init_io(&s->iomem, OBJECT(s), &pl011_ops, s, "pl011", 0x1000);    sysbus_init_mmio(sbd, &s->iomem);    for (i = 0; i < ARRAY_SIZE(s->irq); i++) {        /* Initialize interrupts */        sysbus_init_irq(sbd, &s->irq[i]);    }    s->clk = qdev_init_clock_in(DEVICE(obj), "clk", pl011_clock_update, s,                                ClockUpdate);    s->id = pl011_id_arm;}// hw/char/pl011.cstatic void pl011_realize(DeviceState *dev, Error **errp){    PL011State *s = PL011(dev);    /* Connect character device */    qemu_chr_fe_set_handlers(&s->chr, pl011_can_receive, pl011_receive,                             pl011_event, NULL, s, NULL, true);}

Implement device functional logic

Core components:

  • MMIO read/write callbacks
  • Interrupt generation logic
  • FIFO management
  • Character device communication
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
static void pl011_write(void *opaque, hwaddr offset,                        uint64_t value, unsigned size){    PL011State *s = (PL011State *)opaque;    unsigned char ch;    trace_pl011_write(offset, value, pl011_regname(offset));    switch (offset >> 2) {    case 0: /* UARTDR */  // Data register        ch = value;        pl011_write_txdata(s, ch);        break;    case 1: /* UARTRSR/UARTECR */        s->rsr = 0;        break;    case 6: /* UARTFR */        /* Writes to Flag register are ignored.  */        break;    case 8: /* UARTILPR */        s->ilpr = value;        break;    case 9: /* UARTIBRD */        s->ibrd = value & IBRD_MASK;        pl011_trace_baudrate_change(s);        break;    case 10: /* UARTFBRD */        s->fbrd = value & FBRD_MASK;        pl011_trace_baudrate_change(s);        break;    case 11: /* UARTLCR_H */        /* Reset the FIFO state on FIFO enable or disable */        if ((s->lcr ^ value) & LCR_FEN) {            pl011_reset_rx_fifo(s);            pl011_reset_tx_fifo(s);        }        if ((s->lcr ^ value) & LCR_BRK) {            int break_enable = value & LCR_BRK;            qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_BREAK,                              &break_enable);            pl011_loopback_break(s, break_enable);        }        s->lcr = value;        pl011_set_read_trigger(s);        break;    case 12: /* UARTCR */        /* ??? Need to implement the enable bit.  */        s->cr = value;        pl011_loopback_mdmctrl(s);        break;    case 13: /* UARTIFS */        s->ifl = value;        pl011_set_read_trigger(s);        break;    case 14: /* UARTIMSC */        s->int_enabled = value;        pl011_update(s);        break;    case 17: /* UARTICR */        s->int_level &= ~value;        pl011_update(s);        break;    case 18: /* UARTDMACR */        s->dmacr = value;        if (value & 3) {            qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n");        }        break;    default:        qemu_log_mask(LOG_GUEST_ERROR,                      "pl011_write: Bad offset 0x%x\n", (int)offset);    }}static void pl011_receive(void *opaque, const uint8_t *buf, int size){    trace_pl011_receive(size);    /*     * In loopback mode, the RX input signal is internally disconnected     * from the entire receiving logics; thus, all inputs are ignored,     * and BREAK detection on RX input signal is also not performed.     */    if (pl011_loopback_enabled(opaque)) {        return;    }    for (int i = 0; i < size; i++) {        pl011_fifo_rx_put(opaque, buf[i]);    }}

Interrupt handling and status update

1234567891011
static void pl011_update(PL011State *s){    uint32_t flags;    int i;    flags = s->int_level & s->int_enabled;    trace_pl011_irq_state(flags != 0);    for (i = 0; i < ARRAY_SIZE(s->irq); i++) {        qemu_set_irq(s->irq[i], (flags & irqmask[i]) != 0);  /* Update interrupt line */    }}

A simple code example for PL011 device modeling

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
/* hw/char/pl011.c - Full implementation of PL011 UART */#define TYPE_PL011 "pl011"#define PL011(obj) OBJECT_CHECK(PL011State, (obj), TYPE_PL011)/* Register offset definitions */enum {    PL011_DR    = 0x00, // Data register    PL011_FR    = 0x18, // Flag register    PL011_ILPR  = 0x20, // Low-power register    PL011_IBRD  = 0x24, // Integer baud rate    PL011_FBRD  = 0x28, // Fractional baud rate    PL011_LCRH  = 0x2C, // Line control    PL011_CR    = 0x30, // Control register    PL011_IMSC  = 0x38, // Interrupt mask};/* Device state structure */typedef struct PL011State {    SysBusDevice parent_obj;    MemoryRegion iomem;    CharBackend chr;    qemu_irq irq;    /* Register state */    uint32_t readbuff;    uint32_t flags;    uint32_t lcr;    uint32_t cr;    uint32_t imsc;    uint32_t int_level;    /* FIFO state */    uint8_t read_fifo[PL011_FIFO_DEPTH];    uint32_t read_pos;    uint32_t read_count;    uint32_t read_trigger;} PL011State;/* MMIO operation callbacks */static const MemoryRegionOps pl011_ops = {    .read = pl011_read,    .write = pl011_write,    .endianness = DEVICE_NATIVE_ENDIAN,    .impl.min_access_size = 4,    .impl.max_access_size = 4,};/* Read operation implementation */static uint64_t pl011_read(void *opaque, hwaddr offset, unsigned size) {    PL011State *s = opaque;    uint32_t ret = 0;    switch (offset) {    case PL011_DR:        if (s->read_count > 0) {            ret = s->read_fifo[s->read_pos];            s->read_pos = (s->read_pos + 1) % PL011_FIFO_DEPTH;            s->read_count--;            pl011_update(s); // Update interrupt state        }        break;    case PL011_FR:        ret = s->flags;        break;    case PL011_IMSC:        ret = s->imsc;        break;    }    return ret;}/* Write operation implementation */static void pl011_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) {    PL011State *s = opaque;    switch (offset) {    case PL011_DR:        qemu_chr_fe_write(&s->chr, (uint8_t*)&value, 1);        break;    case PL011_LCRH:        s->lcr = value;        break;    case PL011_IMSC:        s->imsc = value;        pl011_update(s);        break;    }}/* Character device receive callback */static void pl011_receive(void *opaque, const uint8_t *buf, int size) {    PL011State *s = opaque;    if (s->read_count < PL011_FIFO_DEPTH) {        int slot = (s->read_pos + s->read_count) % PL011_FIFO_DEPTH;        s->read_fifo[slot] = *buf;        s->read_count++;        if (s->read_count >= s->read_trigger) {            s->int_level |= INT_RX;            qemu_set_irq(s->irq, 1);        }    }}/* Interrupt state update */static void pl011_update(PL011State *s) {    uint32_t int_level = 0;    // Receive interrupt (RX)    if ((s->imsc & INT_RX) && (s->read_count > 0)) {        int_level |= INT_RX;    }    // Transmit interrupt (TX)    if ((s->imsc & INT_TX) && (s->xmit_count < PL011_FIFO_DEPTH)) {        int_level |= INT_TX;    }    qemu_set_irq(s->irq, int_level ? 1 : 0);}/* Device reset */static void pl011_reset(DeviceState *dev) {    PL011State *s = PL011(dev);    s->lcr = 0;    s->cr = 0x300;    s->imsc = 0;    s->read_count = 0;    s->read_pos = 0;    s->int_level = 0;    qemu_set_irq(s->irq, 0);}/* Device initialization */static void pl011_realize(DeviceState *dev, Error **errp) {    PL011State *s = PL011(dev);    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);    memory_region_init_io(&s->iomem, OBJECT(s), &pl011_ops, s, "pl011", 0x1000);    sysbus_init_mmio(sbd, &s->iomem);    sysbus_init_irq(sbd, &s->irq);    qemu_chr_fe_set_handlers(&s->chr, pl011_can_receive, pl011_receive,                             NULL, NULL, s, NULL, true);}/* Type registration */static const TypeInfo pl011_info = {    .name = TYPE_PL011,    .parent = TYPE_SYS_BUS_DEVICE,    .instance_size = sizeof(PL011State),    .class_init = pl011_class_init,};static void pl011_register_types(void) {    type_register_static(&pl011_info);}type_init(pl011_register_types)

Instantiating PL011 in the RISC-V virt machine:

12345678910111213141516171819
/* hw/riscv/virt.c - Machine initialization */static void virt_machine_init(MachineState *machine) {    // ...    /* Create PL011 serial port */    DeviceState *dev = qdev_new(TYPE_PL011);    qdev_prop_set_chr(dev, "chardev", serial_hd(0));    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0x10000000); // Map to 0x10000000    sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, plic_irq[UART0_IRQ]); // Connect interrupt    // Add device tree node    qemu_fdt_add_subnode(fdt, "/soc/serial");    qemu_fdt_setprop_string(fdt, "/soc/serial", "compatible", "arm,pl011");    qemu_fdt_setprop_cells(fdt, "/soc/serial", "reg", 0x10000000, 0x1000);    qemu_fdt_setprop_cells(fdt, "/soc/serial", "interrupts", UART0_IRQ);}

Key implementation points

Precise register modeling:

  • Implement precise behavior of all 32 registers
  • Handle special bits (e.g., FIFO enable bit, interrupt mask bit)
  • Support baud rate calculation (IBRD/FBRD)

FIFO state machine:

12345678910111213
static void fifo_push(PL011State *s, uint32_t value) {    if (s->read_count < PL011_FIFO_DEPTH) {        uint32_t slot = (s->read_pos + s->read_count) % PL011_FIFO_DEPTH;        s->read_fifo[slot] = value;        s->read_count++;        // Interrupt trigger conditions        if (s->read_count >= s->read_trigger) {            s->int_level |= INT_RX;            qemu_set_irq(s->irq, 1);        }    }}

Clock domain handling:

  • Implement pl011_clock_update callback
  • Handle baud rate change events
  • Support dynamic clock adjustment

Migration support:

1234567891011
cstatic const VMStateDescription vmstate_pl011 = {    .name = "pl011",    .version_id = 2,    .fields = (VMStateField[]) {        VMSTATE_UINT32(lcr, PL011State),        VMSTATE_UINT32_ARRAY(read_fifo, PL011State, PL011_FIFO_DEPTH),        VMSTATE_UINT32(read_pos, PL011State),        VMSTATE_UINT32(read_count, PL011State),        VMSTATE_END_OF_LIST()    }};

Performance optimization:

  • Use bit operations instead of division to calculate baud rate
  • Use masks instead of modulo operations for FIFO operations
  • Cache interrupt status to reduce computation count

This modeling approach enables PL011 to accurately simulate real hardware behavior in QEMU, supporting full-stack development from bare-metal programs to the Linux kernel.

Peripheral modeling (edu device)

In a computer system, the interaction between CPU and peripherals is essentially address access and interrupt notification:

  • Address access: CPU reads and writes device registers through MMIO (Memory-Mapped I/O) or PIO (Port I/O)
  • Interrupt notification: Peripherals send event signals to the CPU via the interrupt controller
12345678
// Pseudocode: CPU and device interaction flowwhile (1) {    if (中断触发) {        读取设备状态寄存器;        处理数据;    }    写入控制寄存器启动操作;}

Introduction to the edu device

edu is an official QEMU teaching virtual device, whose functional design is streamlined yet complete:

  • Basic functionality: implements factorial calculation (fact register)
  • Advanced functionality: DMA transfer, interrupt triggering
  • Typical application: demonstrates full lifecycle management of PCI devices

Define device properties

Key point: By inheriting PCIDevice, it automatically gains PCI configuration space management capability.

1234567891011121314151617181920212223242526272829303132
struct EduState {    PCIDevice pdev;  // Inherit the PCI device base class    MemoryRegion mmio;// MMIO memory region    QemuThread thread; // Background computation thread    QemuMutex thr_mutex;// Thread lock    QemuCond thr_cond;    bool stopping;  // Thread stop flag    uint32_t addr4;    uint32_t fact;#define EDU_STATUS_COMPUTING    0x01#define EDU_STATUS_IRQFACT      0x80    uint32_t status;   // Status register (bit0=computing, bit7=interrupt enable)    uint32_t irq_status;#define EDU_DMA_RUN             0x1#define EDU_DMA_DIR(cmd)        (((cmd) & 0x2) >> 1)# define EDU_DMA_FROM_PCI       0# define EDU_DMA_TO_PCI         1#define EDU_DMA_IRQ             0x4    struct dma_state {        dma_addr_t src;        dma_addr_t dst;        dma_addr_t cnt;        dma_addr_t cmd;    } dma;    QEMUTimer dma_timer;    char dma_buf[DMA_SIZE];    uint64_t dma_mask;};

Initialize PCI configuration space

1234567891011121314
static void edu_class_init(ObjectClass *class, const void *data){    DeviceClass *dc = DEVICE_CLASS(class);    PCIDeviceClass *k = PCI_DEVICE_CLASS(class);    k->realize = pci_edu_realize;    k->exit = pci_edu_uninit;    k->vendor_id = PCI_VENDOR_ID_QEMU;// Vendor ID    k->device_id = 0x11e8;// Device ID    k->revision = 0x10;    k->class_id = PCI_CLASS_OTHERS; // Device class    set_bit(DEVICE_CATEGORY_MISC, dc->categories);}

This step allows the operating system to identify the device as “Edu Device” via PCI.

Map MMIO region

12345678910111213141516171819202122
static void pci_edu_realize(PCIDevice *pdev, Error **errp){    EduState *edu = EDU(pdev);    uint8_t *pci_conf = pdev->config;    pci_config_set_interrupt_pin(pci_conf, 1);    if (msi_init(pdev, 0, 1, true, false, errp)) {        return;    }    timer_init_ms(&edu->dma_timer, QEMU_CLOCK_VIRTUAL, edu_dma_timer, edu);    qemu_mutex_init(&edu->thr_mutex);    qemu_cond_init(&edu->thr_cond);    qemu_thread_create(&edu->thread, "edu", edu_fact_thread,                       edu, QEMU_THREAD_JOINABLE);    memory_region_init_io(&edu->mmio, OBJECT(edu), &edu_mmio_ops, edu,                    "edu-mmio", 1 * MiB);// 1MB address space    pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &edu->mmio);}

Address layout:

1234
0x00 : 阶乘输入寄存器 (可写)0x04 : 阶乘结果寄存器 (只读)0x80 : DMA 源地址寄存器0x88 : DMA 目标地址寄存器

Implement MMIO callback functions

Taking factorial calculation as an example:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
static uint64_t edu_mmio_read(void *opaque, hwaddr addr, unsigned size){    EduState *edu = opaque;    uint64_t val = ~0ULL;    if (addr < 0x80 && size != 4) {        return val;    }    if (addr >= 0x80 && size != 4 && size != 8) {        return val;    }    switch (addr) {    case 0x00:        val = 0x010000edu;        break;    case 0x04:// Read result register        val = edu->addr4;        break;    case 0x08:        qemu_mutex_lock(&edu->thr_mutex);        val = edu->fact;        qemu_mutex_unlock(&edu->thr_mutex);        break;    case 0x20:        val = qatomic_read(&edu->status);        break;    case 0x24:        val = edu->irq_status;        break;    case 0x80:        dma_rw(edu, false, &val, &edu->dma.src, false);        break;    case 0x88:        dma_rw(edu, false, &val, &edu->dma.dst, false);        break;    case 0x90:        dma_rw(edu, false, &val, &edu->dma.cnt, false);        break;    case 0x98:        dma_rw(edu, false, &val, &edu->dma.cmd, false);        break;    }    return val;}static void edu_mmio_write(void *opaque, hwaddr addr, uint64_t val,                unsigned size){    EduState *edu = opaque;    if (addr < 0x80 && size != 4) {        return;    }    if (addr >= 0x80 && size != 4 && size != 8) {        return;    }    switch (addr) {    case 0x04:        edu->addr4 = ~val;        break;    case 0x08:        if (qatomic_read(&edu->status) & EDU_STATUS_COMPUTING) {            break;        }        /* EDU_STATUS_COMPUTING cannot go 0->1 concurrently, because it is only         * set in this function and it is under the iothread mutex.         */        qemu_mutex_lock(&edu->thr_mutex);        edu->fact = val;        qatomic_or(&edu->status, EDU_STATUS_COMPUTING);        qemu_cond_signal(&edu->thr_cond);        qemu_mutex_unlock(&edu->thr_mutex);        break;    case 0x20:        if (val & EDU_STATUS_IRQFACT) {            qatomic_or(&edu->status, EDU_STATUS_IRQFACT);            /* Order check of the COMPUTING flag after setting IRQFACT.  */            smp_mb__after_rmw();        } else {            qatomic_and(&edu->status, ~EDU_STATUS_IRQFACT);        }        break;    case 0x60:        edu_raise_irq(edu, val);        break;    case 0x64:        edu_lower_irq(edu, val);        break;    case 0x80:        dma_rw(edu, true, &val, &edu->dma.src, false);        break;    case 0x88:        dma_rw(edu, true, &val, &edu->dma.dst, false);        break;    case 0x90:        dma_rw(edu, true, &val, &edu->dma.cnt, false);        break;    case 0x98:        if (!(val & EDU_DMA_RUN)) {            break;        }        dma_rw(edu, true, &val, &edu->dma.cmd, true);        break;    }}

Implement background computation thread

Key design: avoid blocking the main thread, use condition variables for asynchronous computation.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
/* * We purposely use a thread, so that users are forced to wait for the status * register. */static void *edu_fact_thread(void *opaque){    EduState *edu = opaque;    while (1) {        uint32_t val, ret = 1;        qemu_mutex_lock(&edu->thr_mutex);        while ((qatomic_read(&edu->status) & EDU_STATUS_COMPUTING) == 0 &&                        !edu->stopping) {            qemu_cond_wait(&edu->thr_cond, &edu->thr_mutex);        }        if (edu->stopping) {            qemu_mutex_unlock(&edu->thr_mutex);            break;        }        val = edu->fact;        qemu_mutex_unlock(&edu->thr_mutex);        while (val > 0) {            ret *= val--;        }        /*         * We should sleep for a random period here, so that students are         * forced to check the status properly.         */        qemu_mutex_lock(&edu->thr_mutex);        edu->fact = ret;        qemu_mutex_unlock(&edu->thr_mutex);        qatomic_and(&edu->status, ~EDU_STATUS_COMPUTING);        /* Clear COMPUTING flag before checking IRQFACT.  */        smp_mb__after_rmw();        if (qatomic_read(&edu->status) & EDU_STATUS_IRQFACT) {            bql_lock();            edu_raise_irq(edu, FACT_IRQ);//Trigger interrupt            bql_unlock();        }    }    return NULL;}

Interrupt trigger mechanism

123456789101112
static void edu_raise_irq(EduState *edu, uint32_t val){    edu->irq_status |= val;    if (edu->irq_status) {        if (edu_msi_enabled(edu)) {// Use MSI interrupts            msi_notify(&edu->pdev, 0);        } else {// Use traditional INTx interrupts            pci_set_irq(&edu->pdev, 1);        }    }}

DMA transfer implementation

12345678910111213141516171819202122232425262728293031323334
static void edu_dma_timer(void *opaque){    EduState *edu = opaque;    bool raise_irq = false;    if (!(edu->dma.cmd & EDU_DMA_RUN)) {        return;    }    // Perform DMA copy    if (EDU_DMA_DIR(edu->dma.cmd) == EDU_DMA_FROM_PCI) {        uint64_t dst = edu->dma.dst;        edu_check_range(dst, edu->dma.cnt, DMA_START, DMA_SIZE);        dst -= DMA_START;        pci_dma_read(&edu->pdev, edu_clamp_addr(edu, edu->dma.src),                edu->dma_buf + dst, edu->dma.cnt);    } else {        uint64_t src = edu->dma.src;        edu_check_range(src, edu->dma.cnt, DMA_START, DMA_SIZE);        src -= DMA_START;        pci_dma_write(&edu->pdev, edu_clamp_addr(edu, edu->dma.dst),                edu->dma_buf + src, edu->dma.cnt);    }    edu->dma.cmd &= ~EDU_DMA_RUN;    if (edu->dma.cmd & EDU_DMA_IRQ) {        raise_irq = true;    }    if (raise_irq) {        // Trigger interrupt        edu_raise_irq(edu, DMA_IRQ);    }}

Device interaction from the guest’s perspective

When the guest program’s driver accesses the edu device:

  1. Probe device: identify by PCI ID 0x1234:11e8
  2. Map MMIO: ioremap() gets the virtual address of registers
  3. Calculate factorial
  4. Handle interrupt: clear status flags in the interrupt service routine

Key points summary

edu encapsulates the 5 core elements of peripheral modeling:

1
寄存器操作 → 中断通知 → DMA 传输 → 多线程协同 → PCI 规范

Mainly involves:

  • State machine model: represent device state through register bits (e.g., computing/completed)
  • Asynchronous processing: hand off time-consuming operations to a background thread to avoid blocking the vCPU
  • Address isolation: each device has its own independent MMIO space
  • Interrupt abstraction: supports both traditional INTx and modern MSI modes
  • DMA security: address validation prevents virtual machine escape

Interrupt modeling

The complete handling flow of PL011 interrupts in QEMU
The complete handling flow of PL011 interrupts in QEMU

  • Device layer: PL011 triggers an interrupt signal based on hardware state
  • Interrupt controller layer: PLIC implements priority arbitration and interrupt distribution
  • CPU layer: RISC-V architecture responds to external interrupts and switches context
  • Guest layer: The guest program or operating system’s interrupt handler completes device service

Below, we analyze the process step by step:

PL011 triggers an interrupt

12345678910111213
// hw/char/pl011.cstatic void pl011_update(PL011State *s){    uint32_t flags;    int i;    flags = s->int_level & s->int_enabled;    trace_pl011_irq_state(flags != 0);    for (i = 0; i < ARRAY_SIZE(s->irq); i++) {        qemu_set_irq(s->irq[i], (flags & irqmask[i]) != 0);// Trigger interrupt    }}

s->irq is connected to a specific interrupt line of the PLIC (e.g., UART0_IRQ=10), and the PLIC then routes the interrupt to the CPU.

PLIC receives the interrupt

PLIC’s GPIO input handler updates the interrupt status:

12345678910111213141516
// hw/intc/sifive_plic.cstatic void sifive_plic_irq_request(void *opaque, int irq, int level){    SiFivePLICState *s = opaque;    if (level > 0) {        sifive_plic_set_pending(s, irq, true);// Set the pending bit        sifive_plic_update(s); // Trigger update    }}static void sifive_plic_set_pending(SiFivePLICState *plic, int irq, bool level){    atomic_set_masked(&plic->pending[irq >> 5], 1 << (irq & 31), -!!level);}

PLIC arbitration logic

In sifive_plic_In update(), priority arbitration is implemented:

1234567891011121314151617181920212223
static void sifive_plic_update(SiFivePLICState *plic){    int addrid;    /* raise irq on harts where this irq is enabled */    for (addrid = 0; addrid < plic->num_addrs; addrid++) {        uint32_t hartid = plic->addr_config[addrid].hartid;        PLICMode mode = plic->addr_config[addrid].mode;        bool level = !!sifive_plic_claimed(plic, addrid);        switch (mode) {        case PLICMode_M:            qemu_set_irq(plic->m_external_irqs[hartid - plic->hartid_base], level);            break;        case PLICMode_S:            qemu_set_irq(plic->s_external_irqs[hartid - plic->hartid_base], level);            break;        default:            break;        }    }}

CPU interrupt injection

Set the CPU interrupt request flag via cpu_interrupt:

1234567891011121314151617181920212223
// system/cpus.cvoid cpu_interrupt(CPUState *cpu, int mask){    g_assert(bql_locked());    cpus_accel->handle_interrupt(cpu, mask);}// accel/tcg/tcg-accel-ops.cvoid tcg_handle_interrupt(CPUState *cpu, int mask){    cpu->interrupt_request |= mask;    /*     * If called from iothread context, wake the target cpu in     * case its halted.     */    if (!qemu_cpu_is_self(cpu)) {        qemu_cpu_kick(cpu);    } else {        qatomic_set(&cpu->neg.icount_decr.u16.high, -1);    }}
  • Set CPU_INTERRUPT_HARD flag
  • Modify icount to force exit the current TB

CPU interrupt check

Detect interrupt requests in the vCPU main loop:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
static inline bool cpu_handle_interrupt(CPUState *cpu,                                        TranslationBlock **last_tb){    /*     * If we have requested custom cflags with CF_NOIRQ we should     * skip checking here. Any pending interrupts will get picked up     * by the next TB we execute under normal cflags.     */    if (cpu->cflags_next_tb != -1 && cpu->cflags_next_tb & CF_NOIRQ) {        return false;    }    /* Clear the interrupt flag now since we're processing     * cpu->interrupt_request and cpu->exit_request.     * Ensure zeroing happens before reading cpu->exit_request or     * cpu->interrupt_request (see also smp_wmb in cpu_exit())     */    qatomic_set_mb(&cpu->neg.icount_decr.u16.high, 0);    if (unlikely(qatomic_read(&cpu->interrupt_request))) {        int interrupt_request;        bql_lock();        interrupt_request = cpu->interrupt_request;        if (unlikely(cpu->singlestep_enabled & SSTEP_NOIRQ)) {            /* Mask out external interrupts for this step. */            interrupt_request &= ~CPU_INTERRUPT_SSTEP_MASK;        }        if (interrupt_request & CPU_INTERRUPT_DEBUG) {            cpu->interrupt_request &= ~CPU_INTERRUPT_DEBUG;            cpu->exception_index = EXCP_DEBUG;            bql_unlock();            return true;        }#if !defined(CONFIG_USER_ONLY)        if (replay_mode == REPLAY_MODE_PLAY && !replay_has_interrupt()) {            /* Do nothing */        } else if (interrupt_request & CPU_INTERRUPT_HALT) {            replay_interrupt();            cpu->interrupt_request &= ~CPU_INTERRUPT_HALT;            cpu->halted = 1;            cpu->exception_index = EXCP_HLT;            bql_unlock();            return true;        } else {            const TCGCPUOps *tcg_ops = cpu->cc->tcg_ops;            if (interrupt_request & CPU_INTERRUPT_RESET) {                replay_interrupt();                tcg_ops->cpu_exec_reset(cpu);                bql_unlock();                return true;            }            /*             * The target hook has 3 exit conditions:             * False when the interrupt isn't processed,             * True when it is, and we should restart on a new TB,             * and via longjmp via cpu_loop_exit.             */            if (tcg_ops->cpu_exec_interrupt(cpu, interrupt_request)) {// Call architecture-specific interrupt handling                if (!tcg_ops->need_replay_interrupt ||                    tcg_ops->need_replay_interrupt(interrupt_request)) {                    replay_interrupt();                }                /*                 * After processing the interrupt, ensure an EXCP_DEBUG is                 * raised when single-stepping so that GDB doesn't miss the                 * next instruction.                 */                if (unlikely(cpu->singlestep_enabled)) {                    cpu->exception_index = EXCP_DEBUG;                    bql_unlock();                    return true;                }                cpu->exception_index = -1;                *last_tb = NULL;            }            /* The target hook may have updated the 'cpu->interrupt_request';             * reload the 'interrupt_request' value */            interrupt_request = cpu->interrupt_request;        }#endif /* !CONFIG_USER_ONLY */        if (interrupt_request & CPU_INTERRUPT_EXITTB) {            cpu->interrupt_request &= ~CPU_INTERRUPT_EXITTB;            /* ensure that no TB jump will be modified as               the program flow was changed */            *last_tb = NULL;        }        /* If we exit via cpu_loop_exit/longjmp it is reset in cpu_exec */        bql_unlock();    }    /* Finally, check if we need to exit to the main loop.  */    if (unlikely(qatomic_read(&cpu->exit_request)) || icount_exit_request(cpu)) {        qatomic_set(&cpu->exit_request, 0);        if (cpu->exception_index == -1) {            cpu->exception_index = EXCP_INTERRUPT;        }        return true;    }    return false;}

RISC-V interrupt handling

riscv_cpu_do_interrupt implements interrupt context switching:

123456789101112131415161718192021222324
// target/riscv/cpu_helper.cvoid riscv_cpu_do_interrupt(CPUState *cs){    RISCVCPU *cpu = RISCV_CPU(cs);    CPURISCVState *env = &cpu->env;    // 1. Save PC to mepc    env->mepc = env->pc;    // 2. Set interrupt cause    env->mcause = MCAUSE_INTR | (IRQ_M_EXT << MCAUSE_CODE_SHIFT);    // 3. Jump to interrupt vector    if (env->mtvec & MTVEC_MODE_MASK) {        // Vectored mode        env->pc = (env->mtvec & ~MTVEC_MODE_MASK) + 4 * cause;    } else {        // Direct mode        env->pc = env->mtvec & ~MTVEC_MODE_MASK;    }    // 4. Update privilege level    env->priv = PRV_M;}

Guest OS interrupt handling

Take the Linux interrupt handling flow as an example:

1234567891011121314151617181920212223
// arch/riscv/kernel/entry.SENTRY(handle_arch_irq)    SAVE_ALL    csrr a0, mcause    csrr a1, mepc    mv a2, sp    call riscv_intc_irq  // Jump to the C handler    RESTORE_ALLEND(handle_arch_irq)// drivers/irqchip/irq-sifive-plic.cstatic void plic_irq_handler(struct irq_desc *desc){    u32 hwirq = readl(plic->regs + PRIORITY_THRESHOLD);    struct irq_chip *chip = irq_desc_get_chip(desc);    chained_irq_enter(chip, desc);    generic_handle_irq(irq_find_mapping(domain, hwirq));    chained_irq_exit(chip, desc);    // Complete interrupt handling    writel(hwirq, plic->regs + INTERRUPT_COMPLETE);}

Debugging and tracing

Enable PLIC debugging:

1
qemu-system-riscv64 -d int,guest_errors -D plic.log

Key trace points (trace-event):

1
qemu-system-riscv64 --trace "trace_sifive_plic_*"

Related functions

123
// hw/intc/sifive_plic.ctrace_sifive_plic_set_irq(irq, level);trace_sifive_plic_update(cpu_index, max_irq, max_prio);

Reference:

Loading comments…