Commit graph SchOS/cmd/vgpud
Author SHA1 Message Date
Nexoniarz
617a4aec0a boot: one line when it works, all of it when it does not
A boot printed about thirty lines before reaching a prompt. Every one was
useful once, and printed on every boot for ever they train the person in front
of the machine to stop reading their own console -- which is expensive on the
day one of them changes.

    SchOS UEFI loader
    Press F8 for boot options... booting

    Welcome to SchOS!  (F8 at boot for the full log, or dmesg once you are in)

    root@schos / »

**Nothing was deleted, it was routed.** consputc() already wrote to the klog
ring before anywhere else, so a line can be kept in full while being held back
from the two places a person looks. kinfo() is cprintf() with that routing, and
`dmesg` still has the whole boot with timestamps afterwards. The rule for which
is which is not how interesting a line is -- everything was interesting to
somebody once -- but whether you would want to see it on a machine that is
*working*. `pci: 7 devices` is yes the first time and no every time after;
`e1000: no DMA memory for the rings` is yes for ever and stays a cprintf.

The verbose boot the F8 menu already offered now means something much bigger,
which is the other half of this: it is the only way to see any of it live.

Userspace follows through /proc/bootmode, one word. schinit, schinitd and vgpud
hold back their progress lines and never their failures -- a supervisor that
goes quiet when a job dies is worse than one that chatters.

Two things found by testing rather than by reading:

  - schinit read /proc/bootmode *before* bootstrap() mounted /proc, so it got
    "quiet" every time and the verbose boot was quiet in the one process that
    runs first. It reads after the mount now.
  - resolvconf announced "already exists; -f to replace it" on every boot after
    the first, which is a line about having done nothing. It is quiet when the
    file already says what it would have written, and speaks only when the two
    disagree -- which means the nameserver changed and the machine is using the
    old one.

Verified both ways: quiet boot is six lines, a forced-verbose build prints all
thirty plus the `verbose boot:` steps, and /proc/kmsg holds every line in both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-14 21:19:50 +02:00
Nexoniarz
ef3e191c6f hybrid: a modeset, from a process
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
2026-09-14 01:36:05 +02:00
Nexoniarz
d1f50c4da3 hybrid: the driver is killed, and the driver comes back
H2 of docs/plan/hybrid.md -- the milestone the plan itself calls the one
that proves the whole idea, and the one that cannot be skipped.

    # cat /proc/schinit
    1 4 11 os.schos.vgpud
    # kill 4
    schinitd: restarting os.schos.vgpud
    vgpud: claimed
    vgpud: pid 8 drew a test pattern from userspace

The test pattern ends in a twelve-block tally of the drawing process's
pid, because a second run that redraws an identical picture is
indistinguishable from a first run that never stopped. Read off the
virtio-gpu scanout across four generations it says 4, 8, 10, 12, and the
DMA arena hands back the same physical address every time.

What it took:

* A halt the kernel can apply to a device it does not understand.
  Clearing PCI_CMD_BUS_MASTER is the only one there is, and it has to
  happen before the DMA goes back to the arena -- a dead driver's device
  keeps reading a framebuffer the kernel is about to give to somebody
  else. Claim sets the bit, release clears it; pcitest shows it in a bm
  column, with a third state so a failed read is never printed as off.

* A refusal. vgpu_release() asks vga_fb_restore() whether there is a
  framebuffer to put the console back on, and declines to hand over the
  device if there is not. The rule that the kernel keeps a minimal
  driver for everything on the path to a shell is worth writing down
  only if something enforces it.

* The DMA arena not being owned by two allocators at once. This is the
  bug that stopped H2: freevm() frees every mapped page below PHYSTOP
  and the arena is the top 16 MB of exactly that range, so a DMA block
  mapped into a driver went onto the page allocator's free list when the
  driver died, while pciuser_release_all() handed the same pages back to
  the arena. The restarted vgpud asked for its framebuffer, got pages
  kalloc() had already given out -- including its own page tables -- and
  dma_alloc()'s zeroing wiped them, so it died inside the ioctl on the
  return from a syscall whose stack had stopped existing. do_dma() now
  records a VMA with no_free set, the way shared memory does.

* One exit status. KeepAlive must restart a driver that crashed and must
  not restart one that has looked at the machine and found its device
  absent: on hardware with no virtio-gpu that was fifteen spawns in
  forty-five seconds, each printing a line to the console, for ever.
  SCHINIT_EXIT_NORESTART is 78, sysexits.h's EX_CONFIG. It is not a new
  manifest key -- the wait status has always travelled from PID 1 to the
  supervisor, and note_exit() simply stopped discarding it.

The milestone's third clause, "the compositor reattaches", is not done
and is not claimed: there is no compositor running. That is recorded in
the plan as a Schcom milestone rather than counted here.

Closes the register row "A released PCI device" in docs/plan/1.0.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-14 00:05:43 +02:00
Nexoniarz
ea68ef4b54 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
2026-09-13 18:27:41 +02:00