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

55 lines
2.5 KiB
Markdown

---
name: schunix-framebuffer-device
description: /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
metadata:
node_type: memory
type: project
originSessionId: a665e14b-86bd-4b41-a228-51377f3e9225
modified: 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`.