SchOS/.claude/memory/schunix-framebuffer-device.md
Nexoniarz 3182da3965 Carry project memory in the repo, with tools/memory-sync.sh
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.
2026-08-09 14:27:22 +02:00

2.5 KiB

name description metadata
schunix-framebuffer-device /dev/fb0 is a real device with the Linux fbdev ABI; nothing may assume where the framebuffer is. Test with bootcheck.ps1 -Fb and tools/fbshot.ps1
node_type type originSessionId modified
memory project a665e14b-86bd-4b41-a228-51377f3e9225 2026-08-04T16:16:17.735Z

/dev/fb0 (major 2) is a real framebuffer device as of 2026-08-04: kernel/dev/fb.c (read/write by byte offset, plus fb_ioctl), declared in include/fb.h. Before this it was wired to { nullread, nullwrite } — writing pixels to it succeeded and did nothing.

include/fb.h holds Linux's fbdev request numbers and struct layouts (FBIOGET_VSCREENINFO 0x4600, fb_var_screeninfo 160 bytes, fb_fix_screeninfo 80 bytes). Same reasoning as schunix-linux-open-flags: a shared number must mean the shared thing, or a program gets a differently-shaped answer to a request it thought it understood.

Nothing outside vga.c may assume where the framebuffer is. Ask vga_fb_phys(), vga_fb_size(), vga_fb_stride(), vga_fb_bpp(), vga_fb_ptr(). Three places used to hardcode 0xfd000000, two of them also hardcoding 800*600*4 — right only on QEMU stdvga at one resolution.

Two traps that cost time here:

  • setupkvm() runs before vga_init(), so it cannot know the framebuffer address. It used to identity-map one anyway, user-writable, into every process ever created. That mapping is now gone entirely — nothing used it, since userspace reaches the framebuffer through mmap("/dev/fb0").
  • sys_ioctl answers every non-tty request with 0. Any new device ioctl must be routed before that catch-all, or the caller is told "success" and reads an untouched buffer — a 0x0 screen that looks like a driver bug rather than a missing one.

To check it:

pwsh -File tools/bootcheck.ps1 -Fb     # 9 assertions on the reported geometry
pwsh -File tools/fbshot.ps1 -Mode fill # gradient via mmap, screendumped
pwsh -File tools/fbshot.ps1 -Mode bars # colour bars via write()

cmd/fbinfo is the program those drive. fill goes through mmap and bars through the device's write() on purpose: when one works and the other does not, the failure says which half broke.

Serial output cannot catch a stride bug — a framebuffer written with the wrong row pitch reports every byte accepted and merely shears the picture. That is why fbshot takes an actual screenshot; see schunix-verify-by-driving-shell. Convert with convert in the WSL box, not magick.