SchOS/.claude/memory/schunix-framebuffer-device.md
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +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/gfx/fb.h. Before this it was wired to { nullread, nullwrite } — writing pixels to it succeeded and did nothing.

include/gfx/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.