docs: architecture describes what is built now

kernel (threads, demand paging, TLB shootdowns, dynamic linking, RAM direct map), syscalls, filesystem (/proc, /sys,
/dev), terminals (12, VT handshake, ptys), networking (r8169 through LDLS), LDLS (module sets; Linux code may ship),
devices (what stays in the kernel; hybrid.md's lasting content), boot (the loader's placement of the kernel),
graphics (fbdev, GMMOS, QR 1-40), audio (ALSA over OSS), debugging, usb. New: packages.md (the apk root) and
desktop.md (Xorg on the framebuffer).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
Nexoniarz 2026-10-26 07:57:25 +01:00
commit 2556a955b3
15 changed files with 921 additions and 341 deletions

View file

@ -1,8 +1,8 @@
# Graphics, text and the console
Four things share this area and they are deliberately separate: the kernel's own
drawing surface, the compositor's display interface, the text-rendering layer,
and the crash screen.
Five things share this area and they are deliberately separate: the kernel's own
drawing surface, the `display` class a display server could use, the text-rendering
layer, the crash screen — and `/dev/fb0`, which is how Xorg reaches the screen.
---
@ -13,11 +13,11 @@ first thing here.
| | [`gfx.h`](../../include/gfx.h) | [`display.h`](../../include/display.h) |
|---|---|---|
| **What** | The kernel's own drawing surface | The compositor's interface |
| **What** | The kernel's own drawing surface | A frame interface for a display server |
| **About** | Pixels and text | *Frames* |
| **Operations** | `draw_pixel`, `fill_rect`, `putc`, `text_at` | `acquire`, `present`, `wait_vblank` |
| **Buffering** | None. Direct writes to the scanout. | Double-buffered |
| **Users** | The console and PSOGM | Schcom |
| **Users** | The console, PSOGM, GMMOS | None today — Xorg draws through `/dev/fb0` |
| **Must work when** | **Nothing else in the system does** | Userspace is healthy |
That last row is the reason they are not one interface. The console and the
@ -67,7 +67,7 @@ and `vgpud` mirroring it see what is on the screen.
`struct display_ops`, found with `device_find_class(DEVCLASS_DISPLAY)`.
`acquire()` allocates the back buffer **on first call, never at boot** — it is
around two megabytes at 800×600, and a machine whose compositor never starts
around two megabytes at 800×600, and a machine whose display server never starts
should not pay that. A driver that cannot double-buffer returns 0, and the
caller draws directly and accepts tearing rather than not drawing at all.
`wait_vblank` is optional for the same reason.
@ -133,7 +133,7 @@ A machine with both a stdvga and a virtio-gpu has two displays, and `vgpud` —
the out-of-kernel driver, `cmd/vgpud/main.c` — owns the second one. For a long
time it drew a test card there: colour bands, gradients, a checkerboard, and
its own pid in binary along the bottom. That was scaffolding for
[../plan/hybrid.md](../plan/hybrid.md)'s H2 and H3, where a screenshot had to
the out-of-kernel driver work ([devices.md](devices.md)), where a screenshot had to
answer *did a userspace process put this here* and *did the driver come back*.
Both of those are closed, so the test card became the Shift mark. Which was
@ -223,9 +223,8 @@ shows the top-left corner, which is where a terminal puts everything worth
seeing. Scaling is the alternative and it is the wrong one: text resampled to a
non-integer ratio is text you cannot read.
**This is not the endpoint.** A compositor (Gate 3) will want this head for
itself, and when there is one, the mirror goes away. Until then the honest
thing for a working display to show is the system running on it.
A display server that wants the head for itself can have it; until one does, the
honest thing for a working display to show is the system running on it.
### The F8 resolution is the head's resolution
@ -263,6 +262,23 @@ not apply: two `vgpud`s cannot coexist, because the first holds the PCI claim
and the second is refused the device long before it reaches `bind()`. The claim
is the mutual exclusion; the socket node never was.
## /dev/fb0, and how Xorg draws
Xorg runs on the framebuffer through its `fbdev` driver, so `/dev/fb0` speaks Linux's
fbdev: `FBIOGET_FSCREENINFO`, `FBIOGET_VSCREENINFO`, `FBIOPUT_VSCREENINFO` (accepted
when it asks for the mode already in force), `FBIOPAN_DISPLAY`, `FBIOBLANK`,
`FBIOPUTCMAP`, and `mmap` of the physical framebuffer. `FBIOGET_VSCREENINFO` reports
nominal timings, as Linux's `efifb` does, instead of zeros — a zero pixel clock made
fbdev compute a 0 Hz RandR mode. Pixel layout comes from the same table the kernel
draws with, so a colour a program builds is the colour the kernel would.
Once a program maps the framebuffer the kernel has no way to know when it draws, so
`fb_mapped()` marks the screen as no longer the text console's and `fbclose()` puts it
back. A terminal switched to `KD_GRAPHICS` is not repainted ([terminals.md](terminals.md)),
and the `VT_PROCESS` handshake tells the server before it loses the screen. `cmd/xready`
checks every one of these calls before Xorg is started; the picture itself is the proof
beyond that.
## Text
[`include/gfx/text.h`](../../include/text.h) + `kernel/dev/text.c`.
@ -537,20 +553,59 @@ channel that works when the machine will not come back at all.
machine whose network stack is dead**, and a phone camera is an out-of-band
channel needing no driver on our side.
It encodes the **report itself**, not a URL — it reaches nothing. Deliberately
small in three ways: byte mode only, EC level L only (the screen is a clean,
evenly lit, high-contrast source; the damage QR's stronger levels protect
against does not apply), and versions 1–6 only, stopping short of version 7's
extra version-information blocks and version 10's wider count field.
It encodes the **report itself**, not a URL — it reaches nothing. Byte mode only
and EC level L only (the screen is a clean, evenly lit, high-contrast source; the
damage QR's stronger levels protect against does not apply), but **every version,
1 to 40**: version 40 at level L holds 2,953 bytes, which is what lets GMMOS put the
kernel log on screen as a series of codes. Checked against the `segno` library module
for module at every version.
134 bytes is a large crash report. If a payload ever needs more, the honest fix
is to shorten the report.
It draws on the report unconditionally rather than on a keypress, which is better:
it costs nothing, and requiring input would mean the one case where input is least
trustworthy is also the case where the report is hardest to retrieve. Verified by
`make crashshot && make crashqr`, which decodes the symbol out of the captured
framebuffer.
It draws unconditionally rather than on a keypress, which is better: it costs
nothing, and requiring input would mean the one case where input is least
trustworthy is also the case where the report is hardest to retrieve. Verified
by `make crashshot && make crashqr`, which decodes the symbol out of the
captured framebuffer.
### Every processor stops
A panic used to stop only the processor it happened on; the rest carried on with a
dead kernel, drew over the report, and kept the fans loud until somebody held the power
button. The panicking processor now sends every other one an **NMI**, which arrives even
with interrupts off, and `trap()` halts a processor that takes one while `panic_stop` is
set — first thing, before the canary check, because that stack may be what broke. NMI has
its own per-CPU stack (IST 1): for a few instructions at the top of `syscall_entry` the
live stack is the user's, at ring 0, and a fault pushing the frame there would be a double
fault on the same stack and a reset instead of a report. A second processor that panics
while one is reporting halts.
## GMMOS — the Guru Meditation Micro OS
The report used to be the last thing the kernel did: a picture, blinking keyboard lights,
and a machine that needed its power button. GMMOS keeps the report and goes on living
under it (`kernel/gmmos/`, about 2,000 lines). The real kernel stays dead — nothing of it is
called again that takes a lock, sleeps, allocates or waits for an interrupt. GMMOS has its
own: the framebuffer, a clock read from the TSC, and keyboards it polls itself through
**micro drivers**, cut-down copies of the real ones that do only what a keyboard needs.
| Key | |
|---|---|
| (nothing) | a status line counts down **three minutes** to power-off; any key cancels it |
| **Space** | the last 300 lines of this boot's log as **QR codes**, a page per symbol, numbered so a phone can take them in order; `+` / `-` trade module size for capacity |
| **L** | the log on screen, scrolled with the arrows |
| **T** | a terminal: `why`, `log`, `grep`, `ps`, `cpus`, `pci`, `mem` (page tables walked first, so a bad address cannot fault), `uptime`, `restart`, `poweroff` |
| **R R** / **P P** | restart / power off — twice, so a stray key does neither |
The micro drivers are **PS/2**, **I2C-HID** (the laptop's own keyboard, over a DesignWare
controller — the live driver hands over what it found at boot, because AML cannot run on a
dead kernel) and **xHCI** (USB keyboards: every controller is halted, reset and enumerated
from nothing, root ports and USB 2 hubs up to two deep, a boot-protocol keyboard, no
interrupts, a static 256 KB pool). GMMOS probes all of them and polls every one that answers.
Every buffer is static. A panic inside GMMOS, or a second one in the report, is reported
and then only blinks; GMMOS does not re-enter itself. Checked under QEMU: every screen, two
QR pages read off a screenshot with `zbar` and decoded to the log text exactly, restart
reboots, and left alone it powers off at three minutes. What has been seen on real hardware
is the report and the keyboards the laptops have; the xHCI path is the least exercised.
## One palette
@ -571,16 +626,20 @@ makes it mean something.
## What it does not do
- **The console still has its cell grid.** Cells are now a Unicode codepoint
drawn by the TrueType rasteriser rather than a CP437 byte drawn from a bitmap,
and the cell size follows the font's measured advance — but it is still a
fixed grid, and the text layer's proportional path is unused by it.
- **No mode setting.** Resolution is whatever the loader asked for. There is no
`/dev/dri`-style device and no way to change it at runtime.
- **No double buffering in practice.** `display_ops.acquire` is specified and
the compositor needs it; virtio-gpu binds but exposes no modeset API, and
- **The console still has its cell grid.** Cells are a Unicode codepoint drawn by the
TrueType rasteriser, and the cell size follows the font's measured advance (the font
scales to about 45 rows, 16 to 40 px per em, on whatever mode the firmware booted
in) — but it is a fixed grid, and the text layer's proportional path is unused by it.
- **Mode setting is the loader's and `vgpud`'s.** On real hardware resolution is whatever
the loader chose from its menu — which now asks "Keep this resolution?" and falls back
by itself, because AMD's GOP "sets" smaller modes without leaving the panel's native
timing and the screen showed three or four squashed copies side by side. There is no
`/dev/dri`, no KMS, and Xorg runs at the mode the console is in.
- **No double buffering in practice.** `display_ops.acquire` is specified and nothing
uses it; virtio-gpu exposes a modeset API on `vgpud`'s socket and nothing more, and
`wait_vblank` is unimplemented everywhere.
- **No hardware acceleration of any kind**, and none is planned before 2D blit.
- **No hardware acceleration of any kind**: Xorg draws in software to the framebuffer
(no glamor, no DRI), and GPU drivers are a plan, not a feature ([../plan/gpu.md](../plan/gpu.md)).
- **No CFF/Type 2 charstrings**, so OTF fonts do not render — that is a separate
`font_ops` provider and a bytecode interpreter, not an extension of the `glyf`
path. TrueType hinting is deliberately out of scope.
@ -593,6 +652,6 @@ makes it mean something.
- **No composite point-matching.** Components positioned by matching a point in
the base rather than by an offset are placed at the origin. Fonts that use it
are rare and none in the tree do.
- **PSOGM renders and stops.** The interactive crash mode described in
[../plan/roadmap.md](../plan/roadmap.md) — walk the stack, read another
process's memory, dump page tables — is designed and not built.
- **GMMOS cannot look at a live process by name.** The "interactive crash mode" is built as far
as a dead machine allows — log, a terminal, `ps`, `cpus`, `pci`, memory by physical walk — but
the scheduler is gone, so there is no running a debugger against a process.