virtio — VirtIO Device Framework

Paravirtualized Device Interface with VirtQueues

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
ComponentOwnerStructurePurpose
Descriptor TableSharedvring_desc[N]: addr, len, flags, nextPool of buffer descriptors that can be chained together
Available RingGuest writes, Host readsvring_avail: flags, idx, ring[N]Guest publishes descriptor chain heads for host to consume
Used RingHost writes, Guest readsvring_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).

QueuePurpose
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.

QueuePurpose
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.

QueuePurpose
Queue 0Buffers 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.

QueuePurpose
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

ClassFileInheritsRole
VirtIODeviceBasebase.hhSimObjectAbstract base: features, status, config, queue management
VirtIODummyDevicebase.hhVirtIODeviceBaseStub device for testing transport layer
VirtQueuebase.hhSerializableVirtQueue implementation: descriptor table, avail/used rings
VirtDescriptorbase.hh—Descriptor chain wrapper with read/write/chain operations
VirtIOBlockblock.hhVirtIODeviceBaseBlock storage device backed by DiskImage
VirtIOConsoleconsole.hhVirtIODeviceBaseSerial console (terminal I/O)
VirtIORngrng.hhVirtIODeviceBaseRandom number generator entropy source
VirtIO9PBasefs9p.hhVirtIODeviceBase9P filesystem base class
VirtIO9PProxyfs9p.hhVirtIO9PBase9P proxy forwarding to host filesystem
PciVirtIOpci.hhPciEndpointPCI transport wrapper for VirtIO devices

Configuration Reference

ParameterPython ClassDefaultDescription
vioPciVirtIO—VirtIODeviceBase object to wrap with PCI transport
imageVirtIOBlock—DiskImage backend for block device
qSizeVirtIODeviceBase256VirtQueue size (number of descriptors)
rootVirtIO9PProxy—Host directory path to share via 9P
tagVirtIO9PProxy'gem5'Mount tag for the guest to identify the share
terminalVirtIOConsole—Terminal object for console I/O
entropy_sourceVirtIORng'/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

TermDefinition
VirtIOOASIS standard for paravirtualized device I/O in virtual machines
ParavirtualizationTechnique where guest OS is aware of virtualization and cooperates with host
VirtQueueShared-memory ring buffer for guest-host data exchange
VringThe concrete ring buffer structure: descriptor table + available ring + used ring
Descriptor ChainLinked list of buffer descriptors forming a single I/O request
KickNotification from guest to host that new buffers are available for processing
9P / 9P2000.LNetwork filesystem protocol originally from Plan 9; Linux variant for VirtIO
MMIO TransportVirtIO device registers mapped into physical memory (ARM-style)
PCI TransportVirtIO device exposed as a PCI device with BAR-mapped registers
Feature NegotiationProtocol where guest and host agree on supported device capabilities