--- name: schunix-virtio-gpu description: "virtio-gpu 2D driver — the display class's second implementation; how to run it and the transport details that matter." metadata: node_type: memory type: project originSessionId: 9d8a2aef-bcf0-47a0-96c6-f706efab4a95 modified: 2026-08-08T19:59:27.522Z --- Written 2026-08-08 from the OASIS virtio 1.1 specification (chapters 2, 4.1, 5.7). `include/virtio.h` + `kernel/drivers/virtio_gpu.c`. Worked on the first boot. virtio-gpu: 1280x800, 4000 KB framebuffer, queue 64 display: 1280x800 32bpp stride 5120 display: presented a full frame **Run it with:** `-vga std -device virtio-gpu-pci`. Keep `-vga std` — the bootloader draws the splash through VBE and there is no virtio path that early, so a machine with *only* virtio-gpu boots blind until the kernel is up. Both register as `DEVCLASS_DISPLAY`; `registry.c` lists virtio-gpu **first** so `device_find_class()` returns it, and vga_pci still claims the stdvga card underneath for the splash and text console. **Why it matters over VBE:** presenting is explicit. VBE is a flat aperture with no notion of a finished frame, so a compositor races the scanout. Here you `TRANSFER_TO_HOST_2D` a rectangle then `RESOURCE_FLUSH` it, and the host never shows a half-drawn frame. `acquire()` returns the guest-side framebuffer directly — no separate back buffer is needed, because the host cannot see it until the transfer. `wait_vblank` is deliberately null: whole rectangles are atomic, so there is nothing to synchronise against, and the wrappers in device.c handle a null op. **Transport details that are easy to get wrong:** - Structures live at an offset inside *whichever BAR the device names* in a vendor-specific PCI capability (`PCI_CAP_ID_VNDR`, 0x09). Assuming BAR 0 works on QEMU and is exactly the assumption that breaks on the next device. - The status handshake order is not negotiable: reset → ACK → DRIVER → read features → write features → FEATURES_OK → **read FEATURES_OK back** (the device clears it to reject, and that readback is the only way it ever tells you) → queues → DRIVER_OK. - `VIRTIO_F_VERSION_1` is bit 32, so it lives in the *second* 32-bit window — hence the feature-select registers. - Notify address is `notify_base + queue_notify_off * notify_off_multiplier`; the multiplier is a trailing field of the notify capability only. - MMIO accessed at exact widths. The common config has 8/16/32-bit fields adjacent, and a device may ignore a wrong-width access silently. - 64-bit queue address registers are written as two 32-bit halves, low first. Polled, not interrupt-driven: every caller wants the answer before continuing, and the wait is microseconds. The poll loop is bounded so a wedged device cannot hang boot. `/proc/devices` name and driver columns are 14 wide because `virtio-gpu0` is eleven characters and overran ten. **Not done:** cursor queue, multiple scanouts, mode setting (the resolution is whatever the host reports), resource reuse on resolution change, and 3D/virgl.