virtio — VirtIO Device Framework
The virtio sub-module implements the VirtIO paravirtualized device standard for gem5. Instead of emulating complex real hardware, VirtIO provides a clean, efficient guest-host interface using shared-memory ring buffers (VirtQueues). It includes block storage, serial console, random number generator, and 9P filesystem sharing devices.
VirtQueue Block Device Console 9P / RNG
📚 What is this?
VirtIO is a standardized interface for paravirtualized devices in virtual machines. Unlike traditional device emulation (e.g., emulating a real Intel NIC register-by-register), VirtIO defines a Atomic and Timing Simple CPUs — gem5 Wiki" href="../../cpu_doc/simple/index.html">simple, efficient contract between guest and host. The guest knows it is virtualized and uses a specialized VirtIO driver to communicate through shared memory ring buffers. This dramatically reduces the overhead of device simulation.
💡 Analogy: A Translator / Interpreter
Imagine you need to communicate with someone who speaks a different language. The traditional approach (hardware emulation) is like pretending you speak their language — you laboriously translate every word and gesture to match their expectations. The VirtIO approach is like hiring a professional interpreter: both sides agree on a simple, efficient communication protocol. The guest says "here's a buffer with data I need written to disk" and the host processes it directly. No need to emulate register timing, bus protocols, or hardware quirks. The VirtQueue is the shared notebook the interpreter uses — a ring buffer where both sides can leave messages without waiting for the other.
📜 Table of Contents
VirtIO Architecture
The VirtIO framework in gem5 has three layers: the transport mechanism (MMIO or PCI), the base device with VirtQueues, and the concrete device implementations:
flowchart TD
subgraph Guest["Guest OS"]
DRIVER["VirtIO Driver\n(in kernel)"]
VRING_G["VirtQueue Ring\n(shared memory)"]
end
subgraph Transport["Transport Layer"]
MMIO["VirtIOMMIO\n(memory-mapped)"]
VPCI["VirtIOPCI\n(PCI device)"]
end
subgraph Framework["VirtIO Base"]
VDB["VirtIODeviceBase\n(feature negotiation,\nstatus, config)"]
VQ["VirtQueue\n(descriptor ring,\navail ring, used ring)"]
VD["VirtDescriptor\n(buffer chain)"]
end
subgraph Devices["Concrete Devices"]
BLK["VirtIOBlock\n(disk I/O)"]
CON["VirtIOConsole\n(serial terminal)"]
RNG["VirtIORng\n(random numbers)"]
FS9P["VirtIO9P\n(filesystem sharing)"]
end
DRIVER <-->|"kicks / interrupts"| MMIO
DRIVER <-->|"kicks / interrupts"| VPCI
DRIVER <-->|"shared memory"| VRING_G
MMIO --> VDB
VPCI --> VDB
VDB --> VQ
VQ --> VD
VDB --> BLK
VDB --> CON
VDB --> RNG
VDB --> FS9P
VirtQueue — The Core Mechanism
The VirtQueue is a shared-memory ring buffer consisting of three parts that reside in guest physical memory:
Vring Structure
flowchart LR
subgraph DescriptorTable["Descriptor Table (N entries)"]
D0["desc[0]\naddr, len, flags, next"]
D1["desc[1]\naddr, len, flags, next"]
D2["desc[2]\n..."]
DN["desc[N-1]"]
end
subgraph AvailRing["Available Ring (guest → host)"]
AF["flags"]
AI["idx (next to add)"]
AR0["ring[0] → desc index"]
AR1["ring[1] → desc index"]
ARN["..."]
end
subgraph UsedRing["Used Ring (host → guest)"]
UF["flags"]
UI["idx (next to add)"]
UR0["ring[0]: {id, len}"]
UR1["ring[1]: {id, len}"]
URN["..."]
end
AvailRing -->|"Guest adds\ndescriptor chains"| DescriptorTable
DescriptorTable -->|"Host processes\nand returns"| UsedRing
| Component | Owner | Structure | Purpose |
|---|---|---|---|
| Descriptor Table | Shared | vring_desc[N]: addr, len, flags, next | Pool of buffer descriptors that can be chained together |
| Available Ring | Guest writes, Host reads | vring_avail: flags, idx, ring[N] | Guest publishes descriptor chain heads for host to consume |
| Used Ring | Host writes, Guest reads | vring_used: flags, idx, ring[N]{id, len} | Host returns processed descriptors to guest |
Descriptor Chains
A single I/O request is represented as a chain of descriptors linked by their next field. Each descriptor in the chain can be either read-only (data flowing guest→host) or write-only (data flowing host→guest):
// vring_desc structure (from virtio_ring.h)
struct vring_desc {
uint64_t addr; // Guest physical address of buffer
uint32_t len; // Length of buffer in bytes
uint16_t flags; // VRING_DESC_F_NEXT, VRING_DESC_F_WRITE, VRING_DESC_F_INDIRECT
uint16_t next; // Index of next descriptor in chain
};
// VirtDescriptor wrapper in gem5
class VirtDescriptor {
public:
Index index() const; // Descriptor index in table
bool isIncoming() const; // Guest→Host (read-only)
bool isOutgoing() const; // Host→Guest (write-only)
bool hasNext() const; // More descriptors in chain
VirtDescriptor *next() const; // Next descriptor
void read(off_t offset, uint8_t *dst, size_t size) const;
void write(off_t offset, const uint8_t *src, size_t size);
void chainRead(off_t offset, uint8_t *dst, size_t size) const;
void chainWrite(off_t offset, const uint8_t *src, size_t size);
};
VirtIODeviceBase
All VirtIO devices inherit from VirtIODeviceBase, which manages the device lifecycle, feature negotiation, and VirtQueue registration:
class VirtIODeviceBase : public SimObject {
public:
// Device identification
virtual DeviceId deviceId() const = 0;
// Feature negotiation
void setGuestFeatures(FeatureBits features);
FeatureBits deviceFeatures;
FeatureBits guestFeatures;
// Device status (ACKNOWLEDGE, DRIVER, DRIVER_OK, etc.)
void setDeviceStatus(DeviceStatus status);
DeviceStatus getDeviceStatus() const;
// Configuration space read/write
virtual void readConfig(PacketPtr pkt, Addr cfgOffset);
virtual void writeConfig(PacketPtr pkt, Addr cfgOffset);
// Queue management
void registerQueue(VirtQueue &queue);
// Kick — notification from guest that new buffers are available
virtual void kick();
};
Device Lifecycle
stateDiagram-v2
[*] --> Reset : Device created
Reset --> Acknowledge : Guest sets ACKNOWLEDGE
Acknowledge --> Driver : Guest sets DRIVER
Driver --> FeaturesOK : Feature negotiation complete
FeaturesOK --> DriverOK : Guest sets DRIVER_OK
DriverOK --> Running : Device operational
Running --> DriverOK : Queue kick / interrupt
Running --> Reset : Guest resets device
Running --> Failed : Unrecoverable error
Transport Mechanisms
VirtIO MMIO
MmioVirtIO (in dev/arm/vio_mmio.hh) exposes VirtIO devices through memory-mapped I/O registers. This is the primary transport for AArch32, AArch64, TrustZone security extensions, SVE/SVE2 vector processing, PMU, and hardware transactional memory." href="../../arch_doc/arm/index.html">ARM platforms. The guest accesses device registers at a fixed MMIO address, and notifications are delivered through a platform interrupt.
VirtIO PCI
PciVirtIO (in dev/virtio/pci.hh) exposes VirtIO devices as PCI devices. The device registers are mapped through PCI BARs, and interrupts use PCI INTx or MSI. This transport is used on x86 and other PCI-based platforms.
// PciVirtIO — PCI transport for VirtIO devices
class PciVirtIO : public PciEndpoint {
protected:
VirtIODeviceBase &vio; // The wrapped VirtIO device
Tick readDevice(PacketPtr pkt) override;
Tick writeDevice(PacketPtr pkt) override;
// BAR0 register layout:
// 0x00-0x13: Device features, queue select, queue notify
// 0x14-0x17: Device status
// 0x18-0x1F: ISR, config MSI vector
// 0x20+: Device-specific config
};
Concrete Device Implementations
VirtIOBlock — Block Storage
Provides a paravirtualized block device (virtual disk). The guest sends read/write requests through VirtQueues, and the device processes them against a DiskImage backend (same as used by IDE).
| Queue | Purpose |
|---|---|
| Queue 0 (requestq) | Block I/O requests: read, write, flush |
// VirtIOBlock request header
struct BlkRequest {
uint32_t type; // VIRTIO_BLK_T_IN (read), VIRTIO_BLK_T_OUT (write)
uint32_t reserved;
uint64_t sector; // Starting sector number
// Followed by data buffer descriptors
// Last descriptor: 1-byte status (0=OK, 1=IOERR, 2=UNSUPP)
};
VirtIOConsole — Serial Console
Provides a paravirtualized serial console. Used for guest terminal I/O without emulating a full UART. Connects to gem5's Terminal for I/O.
| Queue | Purpose |
|---|---|
| Queue 0 (receiveq) | Data from host → guest (keyboard input) |
| Queue 1 (transmitq) | Data from guest → host (console output) |
VirtIORng — Random Number Generator
Provides a paravirtualized entropy source. The guest requests random bytes by submitting empty buffers; the host fills them with random data from the simulation's RNG.
| Queue | Purpose |
|---|---|
| Queue 0 | Buffers to fill with random data |
VirtIO9P — 9P Filesystem Sharing
Implements the 9P2000.L protocol over VirtIO for sharing a host directory with the guest. This allows the guest to mount a host filesystem, which is extremely useful for sharing binaries and data with the simulated system without creating disk images.
| Queue | Purpose |
|---|---|
| Queue 0 (requestq) | 9P protocol messages (Tread, Twrite, Twalk, etc.) |
// Python configuration for 9P filesystem sharing
vio_9p = VirtIO9PProxy(
root='/path/to/shared/directory',
tag='gem5_shared' // Guest mount tag
)
vio_9p_pci = PciVirtIO(vio=vio_9p)
system.pci_host.attach(vio_9p_pci)
// Guest mount command:
// mount -t 9p -o trans=virtio gem5_shared /mnt/host
VirtIO Data Flow — Block Read Example
sequenceDiagram
participant GUEST as Guest Driver
participant AVAIL as Available Ring
participant DESC as Descriptor Table
participant USED as Used Ring
participant HOST as VirtIOBlock (Host)
participant IMG as DiskImage
GUEST->>DESC: Write descriptor chain: header + data buffer + status
GUEST->>AVAIL: Add chain head index to available ring
GUEST->>HOST: Kick (write to notify register)
HOST->>AVAIL: Read new available index
HOST->>DESC: Walk descriptor chain
HOST->>DESC: Read BlkRequest header (type=READ, sector=N)
HOST->>IMG: DiskImage.read(sector_N, data)
IMG-->>HOST: Sector data
HOST->>DESC: Write data to output descriptor
HOST->>DESC: Write status byte (0 = success)
HOST->>USED: Add chain head to used ring with byte count
HOST->>GUEST: Raise interrupt
GUEST->>USED: Read used ring entry
GUEST->>GUEST: Process completed request
Key Classes Reference
| Class | File | Inherits | Role |
|---|---|---|---|
VirtIODeviceBase | base.hh | SimObject | Abstract base: features, status, config, queue management |
VirtIODummyDevice | base.hh | VirtIODeviceBase | Stub device for testing transport layer |
VirtQueue | base.hh | Serializable | VirtQueue implementation: descriptor table, avail/used rings |
VirtDescriptor | base.hh | — | Descriptor chain wrapper with read/write/chain operations |
VirtIOBlock | block.hh | VirtIODeviceBase | Block storage device backed by DiskImage |
VirtIOConsole | console.hh | VirtIODeviceBase | Serial console (terminal I/O) |
VirtIORng | rng.hh | VirtIODeviceBase | Random number generator entropy source |
VirtIO9PBase | fs9p.hh | VirtIODeviceBase | 9P filesystem base class |
VirtIO9PProxy | fs9p.hh | VirtIO9PBase | 9P proxy forwarding to host filesystem |
PciVirtIO | pci.hh | PciEndpoint | PCI transport wrapper for VirtIO devices |
Configuration Reference
| Parameter | Python Class | Default | Description |
|---|---|---|---|
vio | PciVirtIO | — | VirtIODeviceBase object to wrap with PCI transport |
image | VirtIOBlock | — | DiskImage backend for block device |
qSize | VirtIODeviceBase | 256 | VirtQueue size (number of descriptors) |
root | VirtIO9PProxy | — | Host directory path to share via 9P |
tag | VirtIO9PProxy | 'gem5' | Mount tag for the guest to identify the share |
terminal | VirtIOConsole | — | Terminal object for console I/O |
entropy_source | VirtIORng | '/dev/random' | Host entropy source file for RNG device |
Creating a New VirtIO Device
To add a new VirtIO device type, inherit from VirtIODeviceBase and override the key methods:
// my_virtio_device.hh
#include "dev/virtio/base.hh"
namespace gem5 {
class MyVirtIODevice : public VirtIODeviceBase
{
public:
MyVirtIODevice(const Params &p);
// Unique VirtIO device ID (assigned by OASIS VirtIO TC)
DeviceId deviceId() const override { return 0x42; }
protected:
// Called when guest kicks the queue
void kick() override {
// Consume descriptors from queue
VirtDescriptor *desc;
while ((desc = queue.consumeDescriptor()) != nullptr) {
// Read request from input descriptor
MyRequest req;
desc->chainRead(0, (uint8_t*)&req, sizeof(req));
// Process request...
MyResponse resp = processRequest(req);
// Write response to output descriptor
desc->next()->chainWrite(0, (uint8_t*)&resp, sizeof(resp));
// Return descriptor to guest
queue.produceDescriptor(desc, sizeof(resp));
}
// Notify guest of completed requests
kick_guest();
}
private:
VirtQueue queue;
MyResponse processRequest(const MyRequest &req);
};
} // namespace gem5
File Map
virtio/ directory structure
src/dev/virtio/
├── base.hh / base.cc ← VirtIODeviceBase, VirtQueue, VirtDescriptor
├── block.hh / block.cc ← VirtIOBlock (paravirtualized disk)
├── console.hh / console.cc ← VirtIOConsole (serial terminal)
├── rng.hh / rng.cc ← VirtIORng (random number generator)
├── fs9p.hh / fs9p.cc ← VirtIO9PBase, VirtIO9PProxy (filesystem sharing)
├── pci.hh / pci.cc ← PciVirtIO (PCI transport for VirtIO devices)
├── virtio_ring.h ← vring_desc, vring_avail, vring_used (from Linux)
├── VirtIO.py ← Python config for VirtIODeviceBase, PciVirtIO
├── VirtIOBlock.py ← Python config for VirtIOBlock
├── VirtIOConsole.py ← Python config for VirtIOConsole
├── VirtIORng.py ← Python config for VirtIORng
├── VirtIO9P.py ← Python config for VirtIO9P
└── SConscript ← Build definitions
Glossary
| Term | Definition |
|---|---|
| VirtIO | OASIS standard for paravirtualized device I/O in virtual machines |
| Paravirtualization | Technique where guest OS is aware of virtualization and cooperates with host |
| VirtQueue | Shared-memory ring buffer for guest-host data exchange |
| Vring | The concrete ring buffer structure: descriptor table + available ring + used ring |
| Descriptor Chain | Linked list of buffer descriptors forming a single I/O request |
| Kick | Notification from guest to host that new buffers are available for processing |
| 9P / 9P2000.L | Network filesystem protocol originally from Plan 9; Linux variant for VirtIO |
| MMIO Transport | VirtIO device registers mapped into physical memory (ARM-style) |
| PCI Transport | VirtIO device exposed as a PCI device with BAR-mapped registers |
| Feature Negotiation | Protocol where guest and host agree on supported device capabilities |