Introduction
In the last lesson, we explored the low-level mechanisms of device emulation: how QEMU intercepts guest I/O through Memory-Mapped I/O (MMIO) and Port I/O (PIO), dispatches these events to C functions, and uses interrupt injection to signal back to the guest. This raises a crucial question for any complex system: how do you manage and organize the hundreds of emulated devices, their states, their interconnections, and their lifecycles in a structured way? A flat collection of callback functions would be unmanageable.
This lesson answers that question by introducing the QEMU Object Model (QOM). We will fulfill the learning outcome: Describe the QEMU Object Model (QOM) for device representation and lifecycle management. Think of QOM as the architectural blueprint or framework that brings order to the device emulation primitives we've already discussed. It's the final piece of theory we need before you implement your first device in the next lesson.
1. What is the QEMU Object Model (QOM)?
At its heart, QEMU is a large C project. C does not natively support object-oriented concepts like classes, inheritance, or polymorphism. To manage the complexity of emulating entire computer systems, the QEMU developers created their own object-oriented framework within C, known as QOM.
[PDF] Tame the (q)emu: debug firmware on custom emulated board
To start, let's get a high-level overview of QOM's purpose from the paper '[PDF] Tame the (q)emu'. This paper provides an excellent practical guide to adding a new board to QEMU, and its introduction to QOM is very clear.
Please read the section 'The QEMU Object Model' (Section 2) on page 3. Focus on how it describes QOM as a system of 'pseudo-classes' that provides modularity for representing components like CPUs, peripherals, and boards.
As the paper states, QOM provides "pseudo-classes" that can be instantiated at runtime. This allows QEMU to be built from modular, reusable components. A virt machine, for example, is composed of QOM objects representing a CPU, a memory controller, a PCIe bus, and various devices attached to that bus.
How QOM Implements "Objects" in C
QOM cleverly simulates inheritance and polymorphism using C structs and function pointers.
-
Inheritance via Nested Structs: A "subclass" is a struct whose first member is an instance of its parent "class" struct. Because C guarantees that the first member of a struct is located at offset 0, a pointer to the child struct can be safely cast to a pointer to the parent struct. This allows generic functions that operate on a parent type to also work on any child type.
-
Polymorphism via Function Pointers: Class-specific behavior (like what to do when a device is "realized" or "reset") is implemented via function pointers stored in the class struct.
Let's look at how this is structured in the code.
[PDF] Tame the (q)emu: debug firmware on custom emulated board
The same paper, '[PDF] Tame the (q)emu', has an excellent section with code listings that illustrate the nested struct technique.
Please read 'Definition of object classes in QEMU' (Section 2.2) on pages 4-7. Pay close attention to: Listing 2 and 3: See how SSIPeripheral contains a DeviceState parent, which in turn contains an ObjectClass parent. Figure 1 and 2: These diagrams are key. They visualize the nested hierarchy and the resulting 'unfolded' memory layout of the final state structure. This is the core mechanism of QOM inheritance.
To provide a more abstract view, here is a diagram illustrating the relationships between the fundamental QOM types.

2. Device Representation in QOM
So, how do you define a new device using this model? It involves three key components:
-
The State Struct: This is your device's "instance data." It's a C struct that holds all the state for a single instance of your device, such as register values, a pointer to its memory region, and crucially, an instance of its parent's state struct as its first member.
Example:struct LilboardMachineStatefrom Listing 5 in thetame_the_qemupaper. -
The Class Struct and
class_initFunction: This holds data shared across all instances of a class. More importantly, its initialization function,_class_init(), is where you "register" your device's methods (like therealizefunction) and define its properties. This function is called only once, the first time the type is used.
Example:lilboard_machine_class_init()from Listing 8, which sets therealizefunction pointermc->init. -
The
TypeInfoStruct: This is a static struct that acts as the "registration form" for your new type. You provide QEMU with the type's name, its parent's name, the size of its instance state struct, and pointers to its initialization functions (class_initandinstance_init). QEMU'stype_register_static()uses this to add your type to its internal type system.
Example:lilboard_infofrom Listing 7.
3. Device Lifecycle Management
One of QOM's most important roles is managing the lifecycle of a device. A device isn't just allocated and ready to go. It goes through a specific sequence of states, giving the creator of the device (e.g., the machine initialization code) a chance to configure it before it becomes "live".
The primary lifecycle stages are:
- Instantiation: The memory for the object's state struct is allocated, and its
instance_initcallback is run. - Configuration: The code that created the object now sets its properties (e.g., what size of RAM to use, which character device to connect to a serial port).
- Realization: The device's
realizecallback is run. This is where the device "comes to life." It acquires resources like memory regions, connects itself to buses, and registers for I/O callbacks. This step can fail. - Unrealization: For hot-unpluggable devices, the
unrealizecallback is run to release resources and disconnect the device.
Deep Dive: instance_init() vs. realize()
A common point of confusion for new QEMU developers is what code should go in instance_init versus realize. Getting this wrong leads to subtle bugs. This distinction is also a frequent topic in advanced technical interviews.
QEMU's instance_init() vs. realize() - Red Hat People
Thomas Huth's blog post 'QEMU's instance_init() vs. realize()' is the definitive explanation of this topic. It clarifies the purpose of each function and the reasoning behind their separation.
Please read the entire blog post. It's short but dense with critical information. Focus on the core reason for the split: device introspection. The command qemu-system-x86_64 --device xyz,help instantiates a device to query its properties but does not realize it. Understanding this will make the rules for each function clear.
To summarize the critical rules from the article:
-
instance_init():- Purpose: Perform initial, non-failable setup. This is the place to create child objects and define dynamic properties.
- Context: Runs before properties are set. Must not assume it's running in any particular machine or that any other devices exist.
- Constraints: Must not fail (no
abort()orexit()). Crashing here would mean a user couldn't even ask for help on a device. - Cleanup: Anything allocated or registered here should be cleaned up in a corresponding
instance_finalize()function, because the object might be temporary.
-
realize():- Purpose: Make the device "live." This is where you connect to buses, map MMIO regions, request interrupts, and perform any setup that might fail.
- Context: Runs after properties have been set. The device has been attached to a machine and can be wired to other devices.
- Constraints: Can and should fail by returning an
Errorobject if configuration is invalid or resources are unavailable.
Interview Scenario
An interviewer poses this problem:
"I've written a new QEMU device that connects to the host's serial port /dev/ttyS0 to communicate with a physical modem. In my device's instance_init function, I have fd = open(\"/dev/ttyS0\", O_RDWR); if (fd < 0) { exit(1); }. The device works perfectly when I boot a VM. However, my colleague reports that just running qemu-system-aarch64 -M virt --device my-modem,help crashes QEMU. What is the bug and how would you fix it?"
Model Response
-
Identify the Root Cause: The problem is a fundamental misunderstanding of the QOM lifecycle. The command
--device my-modem,helpperforms introspection. It creates a temporary instance of the device to query its properties (like its name, description, and available options) but it does not realize the device. The crash occurs because the failing code is ininstance_init, which is executed during this introspection phase. -
Explain the Specific Errors: There are two errors in the
instance_initfunction:- It performs a failable operation: Opening a device file can fail for many reasons (e.g., permissions, device not present).
instance_initmust not perform operations that can fail. - It calls
exit(1): This violates the rule thatinstance_initmust not fail. It terminates the entire QEMU process when the user was only asking for information.
- It performs a failable operation: Opening a device file can fail for many reasons (e.g., permissions, device not present).
-
Propose the Solution: The fix is to move the logic into the correct lifecycle function.
- The
open()call and its error handling should be moved frominstance_initto the device'srealizefunction. - If the
open()call fails withinrealize, it should not callexit(). Instead, it should use QEMU's error reporting mechanism to return anErrorobject to the caller (e.g.,error_setg(errp, "Failed to open /dev/ttyS0: %s", strerror(errno));). This allows QEMU to gracefully fail the machine's startup with a clear error message, rather than crashing. - The
instance_initfunction should be cleaned up to only contain non-failable setup, if any is needed.
- The
Conclusion
In this lesson, we demystified the QEMU Object Model, the foundational framework that provides structure and scalability to QEMU's C codebase.
Key Takeaways:
- QOM is an object-oriented framework for C that provides modularity and reusability by representing system components as objects.
- Device Representation is achieved through a combination of a
TypeInfostruct for registration, a per-instanceStatestruct, and a per-classClassstruct. Inheritance is simulated with nested structs. - Device Lifecycle follows a strict path: instantiation (
instance_init), property configuration, and then activation (realize). - The distinction between
instance_init(for non-failable, context-free setup) andrealize(for failable, machine-specific activation) is critical and driven by QEMU's need to support device introspection without a full machine boot.
Preview of the Next Lesson:
You now understand both the low-level primitives of emulation (MMIO, interrupts) and the high-level framework for organizing devices (QOM). You are fully equipped to put theory into practice. In our next lesson, we will implement a simple emulated memory-mapped (MMIO) device in QEMU and make it accessible to a guest. You will write the TypeInfo, state struct, and lifecycle functions for a basic hardware device from scratch.