Data Flow
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);