hybrid: the GPU driver now runs in userspace
docs/plan/hybrid.md's first eviction. cmd/vgpud is the virtio-gpu driver as a
process: it finds the device by vendor and device id, asks the kernel for it,
and does the whole virtio 1.0 bring-up from outside -- reset, feature
negotiation, a virtqueue in DMA memory, GET_DISPLAY_INFO, RESOURCE_CREATE_2D,
ATTACH_BACKING, SET_SCANOUT, TRANSFER and FLUSH.
vgpud: virtio-gpu at 00:03.0, currently held by virtio-gpu
virtio-gpu: released to userspace; screen is the VBE framebuffer
vgpud: claimed
vgpud: display is 1280x800
vgpud: scanout is ours, framebuffer at physical 0x3eceb000
vgpud: drew a test pattern from userspace
Verified by screendumping the virtio-gpu head: the pattern is there, with a
checkerboard that would shear if the stride were wrong. The console carries on
in VBE on the other head at the same time, which is the fallback rule from the
plan working rather than being described.
No syscall knows this is a GPU. The kernel handed over a PCI device and the
process decided what it was -- which is the property that makes this a general
mechanism, and the reason the evictions come before anything foreign.
Four things the first real driver needed that the test program had not:
- PCI config space (PCIIOC_CFG). A virtio device says where its registers
are in vendor capabilities, which live in config space, so a driver that
cannot read it cannot find its own registers.
- a way for the incumbent to let go: struct pci_driver gained a release
hook. A driver without one keeps its device, which is always safe and for
anything on the path to a shell is also correct.
- the outgoing driver freeing its DMA. Releasing the device but keeping its
4 MB framebuffer is half a handover -- the incoming driver asks for one and
is told there is no room.
- a 16 MB DMA arena. 4 MB was exactly one framebuffer at this resolution,
and either side of a handover there are two.
And one bug it reproduced on its first working run: vgpud polls the used ring,
so it registered no interrupt handler, and a virtio device interrupts on every
completion. The storm detector said so immediately --
irq 11: 77780 interrupts/second -- a device on this line is not
acknowledging it. handler 'e1000' is attached
-- naming the line, the rate and the neighbour it was drowning. One line fixes
it (VRING_AVAIL_F_NO_INTERRUPT before the queue is enabled), and that detector
was worth every minute it cost.
include/virtio.h moved out of the driver directory. Its own comment said it
should when a second reader appeared; one has.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
72074a960e
commit
ea68ef4b54
12 changed files with 799 additions and 9 deletions
193
include/virtio.h
Normal file
193
include/virtio.h
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#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));
|
||||
|
||||
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));
|
||||
Loading…
Reference in a new issue