H3 of docs/plan/hybrid.md -- "a modeset API, which is the thing an
out-of-kernel GPU driver makes affordable and an in-kernel one never got".
# schmode modes
1280x800 *
1280x720
1024x768
800x600
640x480
# schmode 1024 768
1024x768 ok
Captured off the virtio-gpu head, the frames really come back 1280x800,
1024x768, 640x480 and 1280x800 again, with the checkerboard band showing no
shear at any of them.
Nothing in the kernel changed for this. No syscall on the path has heard of
a scanout: schmode writes a line to a socket and vgpud sends five commands
to the device. The comparison is the point -- the same five commands in the
kernel would have been the same five commands *and a reason to be nervous*,
because a wrong one there takes the machine instead of one process. That is
why the in-kernel driver never grew one: not difficulty, exposure.
The framebuffer is allocated once, at the largest mode, and every smaller
mode reuses it. PCIIOC_DMA has no free -- a driver's blocks come back when
it exits and not before -- so a modeset that allocated would spend sixteen
slots and then the arena, and the failure would look like a GPU bug rather
than like counting. A larger mode is refused and says why.
vgpud used to end in for(;;) sleep(100) under a comment arguing that
building a control socket before there is a compositor to judge it is the
mistake this plan warns about. That was right about a *compositor protocol*
and wrong as a reason to have no socket: a modeset has a consumer that
exists, somebody changing the mode and looking at the screen, and schmode
is small enough to judge an interface this small. What is deliberately not
on that socket is everything a compositor would need -- no buffer
submission, no damage, no vblank -- which still waits, for the original
reason.
H4, the USB eviction, is now the only milestone left on the plan and is by
some distance the largest thing on the register.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
202 lines
6.8 KiB
C
202 lines
6.8 KiB
C
#pragma once
|
|
|
|
// virtio 1.0 over PCI — the transport, and virtio-gpu's 2D protocol.
|
|
//
|
|
// It lived next to the driver while exactly one driver read it, with a note
|
|
// saying it should move when a second reader appeared. One has: cmd/vgpud is
|
|
// the same device driven from userspace (docs/plan/hybrid.md's first
|
|
// eviction), and it needs the same register offsets and the same commands.
|
|
//
|
|
// Still one file rather than transport-and-protocol split in two. Both readers
|
|
// want both halves, so a seam here would be drawn for the sake of having one.
|
|
// virtio-net or virtio-blk arriving is when that changes.
|
|
//
|
|
// Written from the OASIS specification (virtio 1.1, chapters 2, 4.1 and 5.7),
|
|
// which is a public document with no licence on what you write from it. Same
|
|
// reasoning as kernel/drivers/xhci.c.
|
|
//
|
|
// Why virtio-gpu at all, when there is already a framebuffer: the VBE
|
|
// framebuffer is a flat block of memory the loader happened to be told about.
|
|
// It has no mode setting, no notion of a frame being complete, and no way to
|
|
// ask the host to present anything -- so a compositor drawing into it is
|
|
// racing the scanout with no way to synchronise. virtio-gpu has resources,
|
|
// explicit transfer and an explicit flush, which is the model every real GPU
|
|
// driver has. It is the smallest device that teaches the right shape.
|
|
|
|
// --- PCI transport ---------------------------------------------------------
|
|
|
|
#define VIRTIO_VENDOR 0x1AF4
|
|
// Modern (1.0) device IDs are 0x1040 + the virtio device type. GPU is type 16.
|
|
#define VIRTIO_DEV_GPU 0x1050
|
|
|
|
// Vendor-specific PCI capability, which is how a virtio device says where in
|
|
// its BARs each configuration structure lives.
|
|
#define PCI_CAP_ID_VNDR 0x09
|
|
|
|
#define VIRTIO_PCI_CAP_COMMON_CFG 1
|
|
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2
|
|
#define VIRTIO_PCI_CAP_ISR_CFG 3
|
|
#define VIRTIO_PCI_CAP_DEVICE_CFG 4
|
|
|
|
// Offsets within the common configuration structure. Written out rather than
|
|
// declared as a struct: the fields are little-endian by specification and of
|
|
// mixed width, and a struct invites the compiler to pad or a caller to forget
|
|
// that these are MMIO and must be accessed at exactly the stated width.
|
|
#define VCOMMON_DFEAT_SEL 0x00 // le32
|
|
#define VCOMMON_DFEAT 0x04 // le32
|
|
#define VCOMMON_GFEAT_SEL 0x08 // le32
|
|
#define VCOMMON_GFEAT 0x0C // le32
|
|
#define VCOMMON_MSIX_CFG 0x10 // le16
|
|
#define VCOMMON_NUM_QUEUES 0x12 // le16
|
|
#define VCOMMON_STATUS 0x14 // u8
|
|
#define VCOMMON_CFG_GEN 0x15 // u8
|
|
#define VCOMMON_Q_SELECT 0x16 // le16
|
|
#define VCOMMON_Q_SIZE 0x18 // le16
|
|
#define VCOMMON_Q_MSIX 0x1A // le16
|
|
#define VCOMMON_Q_ENABLE 0x1C // le16
|
|
#define VCOMMON_Q_NOTIFY_OFF 0x1E // le16
|
|
#define VCOMMON_Q_DESC 0x20 // le64
|
|
#define VCOMMON_Q_DRIVER 0x28 // le64
|
|
#define VCOMMON_Q_DEVICE 0x30 // le64
|
|
|
|
// device_status bits, set in this order during bring-up.
|
|
#define VSTATUS_ACK 1
|
|
#define VSTATUS_DRIVER 2
|
|
#define VSTATUS_DRIVER_OK 4
|
|
#define VSTATUS_FEATURES_OK 8
|
|
#define VSTATUS_FAILED 0x80
|
|
|
|
#define VIRTIO_F_VERSION_1 32 // feature bit: this is a 1.0 device
|
|
|
|
// --- split virtqueue -------------------------------------------------------
|
|
|
|
#define VRING_DESC_F_NEXT 1
|
|
|
|
// Set in vring_avail.flags to tell the device not to interrupt on completion.
|
|
//
|
|
// A driver that polls the used ring wants this, and wants it before the queue
|
|
// is enabled. Without it the device raises its interrupt line on every
|
|
// completed command -- and if nothing is registered to acknowledge it, a
|
|
// level-triggered PCI line stays asserted and re-fires for ever. That is the
|
|
// failure in docs/history/kernel.md section 13, and cmd/vgpud reproduced it
|
|
// exactly on its first working run.
|
|
#define VRING_AVAIL_F_NO_INTERRUPT 1
|
|
#define VRING_DESC_F_WRITE 2 // device writes it; i.e. this is our buffer
|
|
|
|
struct vring_desc {
|
|
uint64 addr;
|
|
uint32 len;
|
|
uint16 flags;
|
|
uint16 next;
|
|
} __attribute__((packed));
|
|
|
|
struct vring_avail {
|
|
uint16 flags;
|
|
uint16 idx;
|
|
uint16 ring[];
|
|
} __attribute__((packed));
|
|
|
|
struct vring_used_elem {
|
|
uint32 id;
|
|
uint32 len;
|
|
} __attribute__((packed));
|
|
|
|
struct vring_used {
|
|
uint16 flags;
|
|
uint16 idx;
|
|
struct vring_used_elem ring[];
|
|
} __attribute__((packed));
|
|
|
|
// --- virtio-gpu, 2D --------------------------------------------------------
|
|
|
|
#define VIRTIO_GPU_CMD_GET_DISPLAY_INFO 0x0100
|
|
#define VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 0x0101
|
|
#define VIRTIO_GPU_CMD_RESOURCE_UNREF 0x0102
|
|
#define VIRTIO_GPU_CMD_SET_SCANOUT 0x0103
|
|
#define VIRTIO_GPU_CMD_RESOURCE_FLUSH 0x0104
|
|
#define VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D 0x0105
|
|
#define VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING 0x0106
|
|
|
|
#define VIRTIO_GPU_RESP_OK_NODATA 0x1100
|
|
#define VIRTIO_GPU_RESP_OK_DISPLAY_INFO 0x1101
|
|
|
|
#define VIRTIO_GPU_MAX_SCANOUTS 16
|
|
|
|
// B8G8R8X8, which is what a 32-bit little-endian 0x00RRGGBB word looks like in
|
|
// memory -- the same layout vga.c already draws into, so a framebuffer can be
|
|
// handed over without a conversion pass.
|
|
#define VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM 2
|
|
|
|
struct virtio_gpu_ctrl_hdr {
|
|
uint32 type;
|
|
uint32 flags;
|
|
uint64 fence_id;
|
|
uint32 ctx_id;
|
|
uint32 padding;
|
|
} __attribute__((packed));
|
|
|
|
struct virtio_gpu_rect {
|
|
uint32 x, y, width, height;
|
|
} __attribute__((packed));
|
|
|
|
struct virtio_gpu_display_one {
|
|
struct virtio_gpu_rect r;
|
|
uint32 enabled;
|
|
uint32 flags;
|
|
} __attribute__((packed));
|
|
|
|
struct virtio_gpu_resp_display_info {
|
|
struct virtio_gpu_ctrl_hdr hdr;
|
|
struct virtio_gpu_display_one pmodes[VIRTIO_GPU_MAX_SCANOUTS];
|
|
} __attribute__((packed));
|
|
|
|
struct virtio_gpu_resource_create_2d {
|
|
struct virtio_gpu_ctrl_hdr hdr;
|
|
uint32 resource_id;
|
|
uint32 format;
|
|
uint32 width;
|
|
uint32 height;
|
|
} __attribute__((packed));
|
|
|
|
// Releasing a resource, which a modeset has to do before it can create one of
|
|
// a different size. The backing pages are detached with it; they belong to the
|
|
// driver and are not freed by this.
|
|
struct virtio_gpu_resource_unref {
|
|
struct virtio_gpu_ctrl_hdr hdr;
|
|
uint32 resource_id;
|
|
uint32 padding;
|
|
} __attribute__((packed));
|
|
|
|
struct virtio_gpu_mem_entry {
|
|
uint64 addr;
|
|
uint32 length;
|
|
uint32 padding;
|
|
} __attribute__((packed));
|
|
|
|
struct virtio_gpu_resource_attach_backing {
|
|
struct virtio_gpu_ctrl_hdr hdr;
|
|
uint32 resource_id;
|
|
uint32 nr_entries;
|
|
} __attribute__((packed));
|
|
|
|
struct virtio_gpu_set_scanout {
|
|
struct virtio_gpu_ctrl_hdr hdr;
|
|
struct virtio_gpu_rect r;
|
|
uint32 scanout_id;
|
|
uint32 resource_id;
|
|
} __attribute__((packed));
|
|
|
|
struct virtio_gpu_transfer_to_host_2d {
|
|
struct virtio_gpu_ctrl_hdr hdr;
|
|
struct virtio_gpu_rect r;
|
|
uint64 offset;
|
|
uint32 resource_id;
|
|
uint32 padding;
|
|
} __attribute__((packed));
|
|
|
|
struct virtio_gpu_resource_flush {
|
|
struct virtio_gpu_ctrl_hdr hdr;
|
|
struct virtio_gpu_rect r;
|
|
uint32 resource_id;
|
|
uint32 padding;
|
|
} __attribute__((packed));
|