virtio_init(device, bus, device_id, config_size, recv)
if PCI bus:
register PCI device, BAR, capabilities
DMA via pci_device_get_dma_ptr()
else:
cpu_register_device(bus->mem_map, bus->addr, VIRTIO_PAGE_SIZE)
DMA via phys_mem_get_ram_ptr()
store device_recv callback
virtio_reset()
Common initialization chooses MMIO or PCI`virtio_init()` creates the register surface, selects DMA access, and resets queues.
Queue notification is the central dispatch pointGuest writes to `QUEUE_NOTIFY`; TinyEMU walks available descriptors and calls the device handler.
guest driver: writes desc/avail/used addresses fills descriptor chain in guest RAM increments avail->idx writes VIRTIO_MMIO_QUEUE_NOTIFY TinyEMU: virtio_mmio_write() -> queue_notify(queue) -> get desc_idx from avail ring -> get_desc_rw_size() -> device_recv(device, queue, desc_idx, read_size, write_size)
Descriptor chains are copied page by pageQueue helpers follow descriptor `next` links and cross page boundaries safely through `get_ram_ptr`.
`memcpy_to_from_queue()` finds the descriptor that contains the requested offset, validates read/write direction, and copies between host buffers and guest RAM through `virtio_memcpy_from_ram()` or `virtio_memcpy_to_ram()`.
descriptor chain -> get_desc() -> locate offset -> verify VRING_DESC_F_WRITE direction -> copy chunk -> follow VRING_DESC_F_NEXT -> repeat until count is consumed
Completion writes the used ring and raises an IRQDevice handlers report consumed descriptors back to the guest with one shared routine.
virtio_consume_desc(device, queue_idx, desc_idx, desc_len)
used->idx++
used->ring[index] = { desc_idx, desc_len }
int_status |= 1
set_irq(device->irq, 1)
On RISC-V MMIO VirtIO, `set_irq()` enters the PLIC path. The guest later acknowledges with `INTERRUPT_ACK`, which clears `int_status` and lowers the IRQ when no bits remain.
Device handlers specialize the payloadBlock, network, console, input, and 9P all sit behind the same queue ABI.
Block
Reads a block header, calls `BlockDevice` read/write callbacks, writes a status byte, and completes the descriptor.
Network
Transmit queue copies a packet to `EthernetDevice.write_packet`; receive queue is manual and filled when the host network backend has a packet.
Console
Queue 1 writes guest output to the host character device; queue 0 is manual and receives host terminal bytes.
Input
Keyboard and pointer events are encoded as 8-byte VirtIO input events followed by SYN events.
9P filesystem
The 9P handler unmarshals messages, maps FIDs to `FSFile` handles, calls the backing `FSDevice`, marshals replies, and completes the queue.