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
|
|
@ -253,8 +253,18 @@ do_claim(uint64 argp)
|
|||
// has started lying. The eviction path (docs/plan/hybrid.md) is for the
|
||||
// kernel to *hand over* a device deliberately; until a driver can be told to
|
||||
// let go, the honest answer to "may I have the one you are using" is no.
|
||||
if (d->driver)
|
||||
return -EBUSY;
|
||||
if (d->driver) {
|
||||
// Ask it to let go. A driver with no release hook keeps its device, which
|
||||
// is always safe and, for anything on the path to a shell, correct: the
|
||||
// rule in docs/plan/hybrid.md is that the kernel keeps a minimal driver
|
||||
// for everything needed to reach a prompt and userspace owns the good one.
|
||||
if (!d->drv || !d->drv->release)
|
||||
return -EBUSY;
|
||||
if (d->drv->release((struct pci_dev *)d) != 0)
|
||||
return -EBUSY;
|
||||
((struct pci_dev *)d)->driver = 0;
|
||||
((struct pci_dev *)d)->drv = 0;
|
||||
}
|
||||
|
||||
acquire(&pciuser_lock);
|
||||
if (owner[idx] && owner[idx] != myproc()) {
|
||||
|
|
@ -391,6 +401,39 @@ do_irq(uint64 argp)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// PCI configuration space.
|
||||
//
|
||||
// The first real driver asked for this and the test program never had to: a
|
||||
// virtio device describes where its registers are in *vendor capabilities*,
|
||||
// which live in config space, so a driver that cannot read config space cannot
|
||||
// find its own registers. Anything modern is discovered the same way -- MSI-X
|
||||
// tables, PCIe extended capabilities, power management.
|
||||
//
|
||||
// Reads are unrestricted for a claimed device; writes are not offered at all
|
||||
// yet. Nothing needs them, and a write to the wrong offset re-programs a BAR
|
||||
// or disables bus mastering under the kernel's feet.
|
||||
static int
|
||||
do_cfg(uint64 argp)
|
||||
{
|
||||
struct pciuser_cfg *c = (struct pciuser_cfg *)user_ptr(argp, sizeof(*c));
|
||||
if (!c)
|
||||
return -EFAULT;
|
||||
|
||||
unsigned int idx = c->index;
|
||||
if (idx >= (unsigned int)pci_count() || idx >= NPCI)
|
||||
return -ENODEV;
|
||||
if (owner[idx] != myproc())
|
||||
return -EACCES;
|
||||
if (c->offset > 0xFC)
|
||||
return -EINVAL;
|
||||
|
||||
const struct pci_dev *d = pci_get((int)idx);
|
||||
if (!d)
|
||||
return -ENODEV;
|
||||
c->value = pci_read32(d->bus, d->slot, d->func, (uint8)(c->offset & 0xFC));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
pciuser_ioctl(uint64 request, uint64 argp)
|
||||
{
|
||||
|
|
@ -405,6 +448,7 @@ pciuser_ioctl(uint64 request, uint64 argp)
|
|||
case PCIIOC_RELEASE: return do_release(argp);
|
||||
case PCIIOC_DMA: return do_dma(argp);
|
||||
case PCIIOC_IRQ: return do_irq(argp);
|
||||
case PCIIOC_CFG: return do_cfg(argp);
|
||||
default: return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue