Files
danos/system/drivers/virtio-gpu/virtio-gpu-protocol.zig
T
Daniel Samson 6e0e0a62c6 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.
2026-07-14 11:29:53 +01:00

136 lines
3.7 KiB
Zig

//! The virtio-gpu control protocol — the command/response structs the driver exchanges with
//! the device over its control virtqueue (virtio spec, "GPU Device"). `extern` structs, so
//! the layout matches the little-endian wire format exactly. Host-tested for size. See
//! docs/display-v2.md.
const std = @import("std");
/// Control command / response types (virtio_gpu_ctrl_type). Commands are 0x01xx, responses
/// 0x11xx (ok) / 0x12xx (error).
pub const CmdType = enum(u32) {
get_display_info = 0x0100,
resource_create_2d = 0x0101,
resource_unref = 0x0102,
set_scanout = 0x0103,
resource_flush = 0x0104,
transfer_to_host_2d = 0x0105,
resource_attach_backing = 0x0106,
resource_detach_backing = 0x0107,
get_edid = 0x010a,
resp_ok_nodata = 0x1100,
resp_ok_display_info = 0x1101,
resp_ok_edid = 0x1104,
resp_err_unspec = 0x1200,
_,
};
/// Set in a command's `flags` to request a fence; the device echoes `fence_id` in the
/// response and does not report completion until the command's effects are visible.
pub const flag_fence: u32 = 1 << 0;
/// virtio_gpu_ctrl_hdr — the header on every command and response.
pub const CtrlHdr = extern struct {
type: u32,
flags: u32 = 0,
fence_id: u64 = 0,
ctx_id: u32 = 0,
ring_idx: u8 = 0,
padding: [3]u8 = .{ 0, 0, 0 },
};
pub const Rect = extern struct {
x: u32,
y: u32,
width: u32,
height: u32,
};
/// 2D pixel formats. QEMU's virtio-gpu host default is B8G8R8X8 (matches our bgrx).
pub const format_b8g8r8x8_unorm: u32 = 2;
pub const format_r8g8b8x8_unorm: u32 = 134;
pub const ResourceCreate2d = extern struct {
hdr: CtrlHdr,
resource_id: u32,
format: u32,
width: u32,
height: u32,
};
/// One scatter-gather entry of a resource's guest backing (a physical span).
pub const MemEntry = extern struct {
addr: u64,
length: u32,
padding: u32 = 0,
};
/// Header for RESOURCE_ATTACH_BACKING; `nr_entries` `MemEntry` follow it inline.
pub const ResourceAttachBacking = extern struct {
hdr: CtrlHdr,
resource_id: u32,
nr_entries: u32,
};
pub const SetScanout = extern struct {
hdr: CtrlHdr,
rect: Rect,
scanout_id: u32,
resource_id: u32,
};
pub const ResourceFlush = extern struct {
hdr: CtrlHdr,
rect: Rect,
resource_id: u32,
padding: u32 = 0,
};
/// Copy the guest backing into the host resource for `rect` (2D resources must transfer
/// before a flush shows the update).
pub const TransferToHost2d = extern struct {
hdr: CtrlHdr,
rect: Rect,
offset: u64,
resource_id: u32,
padding: u32 = 0,
};
pub const max_scanouts = 16;
pub const DisplayOne = extern struct {
rect: Rect,
enabled: u32,
flags: u32,
};
pub const RespDisplayInfo = extern struct {
hdr: CtrlHdr,
pmodes: [max_scanouts]DisplayOne,
};
pub const GetEdid = extern struct {
hdr: CtrlHdr,
scanout: u32,
padding: u32 = 0,
};
pub const RespEdid = extern struct {
hdr: CtrlHdr,
size: u32,
padding: u32 = 0,
edid: [1024]u8,
};
test "virtio-gpu struct sizes match the wire layout" {
try std.testing.expectEqual(@as(usize, 24), @sizeOf(CtrlHdr));
try std.testing.expectEqual(@as(usize, 16), @sizeOf(Rect));
try std.testing.expectEqual(@as(usize, 40), @sizeOf(ResourceCreate2d));
try std.testing.expectEqual(@as(usize, 16), @sizeOf(MemEntry));
try std.testing.expectEqual(@as(usize, 32), @sizeOf(ResourceAttachBacking));
try std.testing.expectEqual(@as(usize, 48), @sizeOf(SetScanout));
try std.testing.expectEqual(@as(usize, 48), @sizeOf(ResourceFlush));
try std.testing.expectEqual(@as(usize, 56), @sizeOf(TransferToHost2d));
try std.testing.expectEqual(@as(usize, 24 + 4 + 4 + 1024), @sizeOf(RespEdid));
}