Claude Code keeps per-project memory at <home>/.claude/projects/<key>/,
where <key> is the project's absolute path with every separator and the
drive colon turned into a dash. That makes it both rename-fragile and
machine-specific, and this project has hit each of those once:
- D:\DEV\Schunix -> D:\DEV\SchOS changed the key, and 43 memories were
invisible until moved across by hand.
- Development happens from Windows and from Linux, and each looks in a
different place for the same checkout.
Committing .claude/memory/ fixes both -- git carries it across renames
and between hosts -- and `import` puts it wherever the local machine
expects. .claude/settings.local.json stays ignored: those are per-machine
permissions.
The colon maps to a dash rather than being dropped. Deleting it gives
D-DEV-SchOS, which matches nothing and silently finds zero memories; that
was the first attempt. On Git Bash the key comes from `cygpath -w`,
because Claude Code is a Windows program and sees D:\DEV\SchOS, not
/d/DEV/SchOS.
3 KiB
| name | description | metadata | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| schunix-virtio-gpu | virtio-gpu 2D driver — the display class's second implementation; how to run it and the transport details that matter. |
|
Written 2026-08-08 from the OASIS virtio 1.1 specification (chapters 2, 4.1,
5.7). include/virtio.h + kernel/drivers/virtio_gpu.c. Worked on the first
boot.
virtio-gpu: 1280x800, 4000 KB framebuffer, queue 64
display: 1280x800 32bpp stride 5120
display: presented a full frame
Run it with: -vga std -device virtio-gpu-pci. Keep -vga std — the
bootloader draws the splash through VBE and there is no virtio path that early,
so a machine with only virtio-gpu boots blind until the kernel is up. Both
register as DEVCLASS_DISPLAY; registry.c lists virtio-gpu first so
device_find_class() returns it, and vga_pci still claims the stdvga card
underneath for the splash and text console.
Why it matters over VBE: presenting is explicit. VBE is a flat aperture with
no notion of a finished frame, so a compositor races the scanout. Here you
TRANSFER_TO_HOST_2D a rectangle then RESOURCE_FLUSH it, and the host never
shows a half-drawn frame. acquire() returns the guest-side framebuffer
directly — no separate back buffer is needed, because the host cannot see it
until the transfer. wait_vblank is deliberately null: whole rectangles are
atomic, so there is nothing to synchronise against, and the wrappers in
device.c handle a null op.
Transport details that are easy to get wrong:
- Structures live at an offset inside whichever BAR the device names in a
vendor-specific PCI capability (
PCI_CAP_ID_VNDR, 0x09). Assuming BAR 0 works on QEMU and is exactly the assumption that breaks on the next device. - The status handshake order is not negotiable: reset → ACK → DRIVER → read features → write features → FEATURES_OK → read FEATURES_OK back (the device clears it to reject, and that readback is the only way it ever tells you) → queues → DRIVER_OK.
VIRTIO_F_VERSION_1is bit 32, so it lives in the second 32-bit window — hence the feature-select registers.- Notify address is
notify_base + queue_notify_off * notify_off_multiplier; the multiplier is a trailing field of the notify capability only. - MMIO accessed at exact widths. The common config has 8/16/32-bit fields adjacent, and a device may ignore a wrong-width access silently.
- 64-bit queue address registers are written as two 32-bit halves, low first.
Polled, not interrupt-driven: every caller wants the answer before continuing, and the wait is microseconds. The poll loop is bounded so a wedged device cannot hang boot.
/proc/devices name and driver columns are 14 wide because virtio-gpu0 is
eleven characters and overran ten.
Not done: cursor queue, multiple scanouts, mode setting (the resolution is whatever the host reports), resource reuse on resolution change, and 3D/virgl.