TinyEMU Data FlowVirtIO devices

VirtIO converts guest queues into host callbacks

All VirtIO devices share the same queue and MMIO/PCI register machinery. Device-specific code only needs to interpret descriptor payloads and complete requests.

Common initialization chooses MMIO or PCI`virtio_init()` creates the register surface, selects DMA access, and resets queues.
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()
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.