virtio-gpu: a modern virtio 1.0 display driver, bring-up to a flushed frame (v2 V3)

The first native scanout backend's driver half. The device manager matches the
virtio-gpu PCI function by its Display/Other class triple and spawns the driver,
which claims the device, confirms vendor 0x1AF4/device 0x1050 from config space,
enables memory-space + bus mastering, and walks the virtio vendor capabilities to
find the common-config and notify structures in its BAR.

From there it is the standard modern-virtio bring-up: reset, negotiate VERSION_1,
stand up the control virtqueue in coherent DMA, then drive the GPU end to end —
RESOURCE_CREATE_2D, ATTACH_BACKING (a coherent DMA buffer; V4 swaps in the shm-
shared surface), SET_SCANOUT, paint a known pattern, TRANSFER_TO_HOST_2D,
RESOURCE_FLUSH, and wait for the device's used-ring ack. Reading the backing back
proves it is CPU-visible RAM; the ack proves the device consumed the frame —
together the automated stand-in for "it's on screen", no screenshot.

- system/drivers/virtio-gpu/: the driver, plus virtio-gpu-protocol.zig (control
  commands) and virtio-pci.zig (the 1.0 PCI transport + split-virtqueue), both with
  host-tested struct sizes.
- device-manager matches the display/other class triple to "virtio-gpu"; the driver
  self-confirms the vendor/device id, since the class alone cannot distinguish it.
- ServiceId.scanout (11): the driver registers it so the compositor finds it in V4.

Gate: python3 test/qemu_test.py virtio-gpu (QEMU -device virtio-gpu-pci) — the
device-manager stack discovers the function, the driver brings up a 640x480 scanout
and flushes a test pattern: "virtio-gpu: scanout 640x480 online" + "flush acked,
pixel check ok". host tests, display-service, shm, and device-list still pass.

Note: the pre-existing pci-scan case triple-faults on main (verified at 88ad432,
before this change); it is unrelated and tracked separately.
This commit is contained in:
Daniel Samson
2026-07-14 11:29:53 +01:00
parent 88ad432758
commit 6e0e0a62c6
9 changed files with 772 additions and 13 deletions
+105
View File
@@ -0,0 +1,105 @@
//! virtio 1.0 PCI transport — the vendor capabilities in PCI config space that point at the
//! device's structures (common config, notify, ISR) in a BAR, the common-config register
//! block, and the split-virtqueue layout. `extern` structs matching the spec. Host-tested
//! for size. See docs/display-v2.md.
const std = @import("std");
/// PCI vendor-specific capability id (0x09) — virtio 1.0 structures are advertised as these.
pub const pci_cap_vendor: u8 = 0x09;
/// virtio_pci_cap `cfg_type`: which structure a vendor capability points at.
pub const cfg_common: u8 = 1;
pub const cfg_notify: u8 = 2;
pub const cfg_isr: u8 = 3;
pub const cfg_device: u8 = 4;
pub const cfg_pci: u8 = 5;
/// virtio_pci_cap — a vendor capability naming a structure at (bar, offset, length) within
/// a PCI BAR. Read straight out of config space.
pub const PciCap = extern struct {
cap_vndr: u8, // 0x09
cap_next: u8, // next capability's offset in config space (0 = end)
cap_len: u8,
cfg_type: u8, // cfg_common / cfg_notify / ...
bar: u8, // which BAR the structure lives in
padding: [3]u8,
offset: u32, // offset within the BAR
length: u32, // length of the structure
};
/// virtio_pci_notify_cap: a notify capability carries a multiplier after the base cap; the
/// per-queue notify address is `notify_base + queue_notify_off * notify_off_multiplier`.
pub const NotifyCap = extern struct {
cap: PciCap,
notify_off_multiplier: u32,
};
/// virtio_pci_common_cfg — the common configuration register block (little-endian MMIO).
pub const CommonCfg = extern struct {
device_feature_select: u32,
device_feature: u32,
driver_feature_select: u32,
driver_feature: u32,
msix_config: u16,
num_queues: u16,
device_status: u8,
config_generation: u8,
queue_select: u16,
queue_size: u16,
queue_msix_vector: u16,
queue_enable: u16,
queue_notify_off: u16,
queue_desc: u64,
queue_driver: u64,
queue_device: u64,
};
/// device_status bits (written to `CommonCfg.device_status` during bring-up).
pub const status_acknowledge: u8 = 1;
pub const status_driver: u8 = 2;
pub const status_driver_ok: u8 = 4;
pub const status_features_ok: u8 = 8;
/// VIRTIO_F_VERSION_1 — feature bit 32 (in the second 32-bit feature word). Required for a
/// modern device; we negotiate exactly this bit and nothing else.
pub const feature_version_1_word: u32 = 1; // device_feature_select value for bits 32..63
pub const feature_version_1_bit: u32 = 1 << 0; // bit 32 within that word
// --- split virtqueue -------------------------------------------------------
pub const Desc = extern struct {
addr: u64, // guest-physical
len: u32,
flags: u16,
next: u16,
};
pub const desc_flag_next: u16 = 1; // buffer continues in `next`
pub const desc_flag_write: u16 = 2; // device-writable (else driver-writable/device-readable)
/// The available ring's fixed header; a `[queue_size]u16` ring and a trailing `used_event`
/// u16 follow it in memory (laid out by the driver).
pub const AvailHdr = extern struct {
flags: u16,
idx: u16,
};
/// One entry of the used ring.
pub const UsedElem = extern struct {
id: u32,
len: u32,
};
/// The used ring's fixed header; a `[queue_size]UsedElem` ring and a trailing `avail_event`
/// u16 follow it.
pub const UsedHdr = extern struct {
flags: u16,
idx: u16,
};
test "virtio-pci struct sizes match the spec" {
try std.testing.expectEqual(@as(usize, 16), @sizeOf(PciCap));
try std.testing.expectEqual(@as(usize, 56), @sizeOf(CommonCfg));
try std.testing.expectEqual(@as(usize, 16), @sizeOf(Desc));
try std.testing.expectEqual(@as(usize, 8), @sizeOf(UsedElem));
}