VirtIO Devices

VirtIO transport, queues, console, block, net, 9P, and input

Data Flow

graph TD A[Guest Driver] --> B[VirtIO MMIO/PCI] B --> C[Queue Descriptor Ring] C --> D[Available Ring] D --> E[Host virt_queue handler] E --> F{Device type} F -->|Console| G[CharacterDevice read/write] F -->|Block| H[BlockDevice read_async/write_async] F -->|Net| H2[EthernetDevice write_packet] F -->|9P| I[FSDevice fs_walk/fs_read/fs_write] F -->|Input| J[virtio_input_send_key_event] E --> K[set_irq -> PLIC]

VirtIO Transport

TinyEMU supports both MMIO and PCI transports. The MMIO registers are defined in virtio.c and mapped into the guest physical address space. The guest writes queue addresses, notifies the host, and reads interrupt status.

/* MMIO addresses - from the Linux kernel */
#define VIRTIO_MMIO_MAGIC_VALUE		0x000
#define VIRTIO_MMIO_VERSION		0x004
#define VIRTIO_MMIO_DEVICE_ID		0x008
#define VIRTIO_MMIO_VENDOR_ID		0x00c
#define VIRTIO_MMIO_DEVICE_FEATURES	0x010
#define VIRTIO_MMIO_DRIVER_FEATURES	0x020
#define VIRTIO_MMIO_QUEUE_SEL		0x030
#define VIRTIO_MMIO_QUEUE_NUM		0x038
#define VIRTIO_MMIO_QUEUE_READY		0x044
#define VIRTIO_MMIO_QUEUE_NOTIFY	0x050
#define VIRTIO_MMIO_INTERRUPT_STATUS	0x060
#define VIRTIO_MMIO_INTERRUPT_ACK	0x064
#define VIRTIO_MMIO_STATUS		0x070
#define VIRTIO_MMIO_QUEUE_DESC_LOW	0x080
#define VIRTIO_MMIO_QUEUE_AVAIL_LOW	0x090
#define VIRTIO_MMIO_QUEUE_USED_LOW	0x0a0
#define VIRTIO_MMIO_CONFIG		0x100

VirtIOBusDef & Device Creation

Each VirtIO device is initialized with a VIRTIOBusDef that specifies either a PCI bus or an MMIO memory map and IRQ line. The machine init code walks through configured drives, filesystems, network interfaces, and input devices, creating one VirtIO device per resource.

typedef struct {
    /* PCI only: */
    PCIBus *pci_bus;
    /* MMIO only: */
    PhysMemoryMap *mem_map;
    uint64_t addr;
    IRQSignal *irq;
} VIRTIOBusDef;

Console Device

The VirtIO console bridges guest serial I/O to a host CharacterDevice. When the guest writes to the transmit queue, the host callback forwards bytes to the console. Keyboard input is injected via virtio_console_write_data().

VIRTIODevice *virtio_console_init(VIRTIOBusDef *bus, CharacterDevice *cs);
BOOL virtio_console_can_write_data(VIRTIODevice *s);
int virtio_console_get_write_len(VIRTIODevice *s);
int virtio_console_write_data(VIRTIODevice *s, const uint8_t *buf, int buf_len);
void virtio_console_resize_event(VIRTIODevice *s, int width, int height);

Block Device

The VirtIO block device translates guest block requests into asynchronous host I/O via the BlockDevice interface. read_async and write_async take a completion callback that signals the VirtIO queue when the operation finishes.

typedef struct BlockDevice BlockDevice;

struct BlockDevice {
    int64_t (*get_sector_count)(BlockDevice *bs);
    int (*read_async)(BlockDevice *bs,
                      uint64_t sector_num, uint8_t *buf, int n,
                      BlockDeviceCompletionFunc *cb, void *opaque);
    int (*write_async)(BlockDevice *bs,
                       uint64_t sector_num, const uint8_t *buf, int n,
                       BlockDeviceCompletionFunc *cb, void *opaque);
    void *opaque;
};

Network Device

The VirtIO net device connects to an EthernetDevice. Packets from the guest are forwarded via write_packet(). Incoming packets are injected via device_write_packet() after checking device_can_write_packet().

struct EthernetDevice {
    uint8_t mac_addr[6];
    void (*write_packet)(EthernetDevice *net, const uint8_t *buf, int len);
    void *opaque;
    /* set by device */
    void *device_opaque;
    BOOL (*device_can_write_packet)(EthernetDevice *net);
    void (*device_write_packet)(EthernetDevice *net,
                                const uint8_t *buf, int len);
    void (*device_set_carrier)(EthernetDevice *net, BOOL carrier_state);
};

9P Filesystem Device

The VirtIO 9P device exposes a host FSDevice to the guest. It uses the 9P protocol over VirtIO queues, translating file operations into fs_walk, fs_read, fs_write, etc.

VIRTIODevice *virtio_9p_init(VIRTIOBusDef *bus, FSDevice *fs,
                             const char *mount_tag);

Input Device

VirtIO input devices (keyboard and tablet) receive host events via virtio_input_send_key_event and virtio_input_send_mouse_event. These are enqueued for the guest driver to consume.

int virtio_input_send_key_event(VIRTIODevice *s, BOOL is_down,
                                uint16_t key_code);
int virtio_input_send_mouse_event(VIRTIODevice *s, int dx, int dy, int dz,
                                  unsigned int buttons);

VIRTIODevice *virtio_input_init(VIRTIOBusDef *bus, VirtioInputTypeEnum type);