Skip to content

The panel paints the cells that moved, and says what it cost - #444

Draft
Japabu wants to merge 5 commits into
mainfrom
wt/toyos-panel
Draft

The panel paints the cells that moved, and says what it cost#444
Japabu wants to merge 5 commits into
mainfrom
wt/toyos-panel

Conversation

@Japabu

@Japabu Japabu commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Draft. The panel's cost is being measured on the T14 before and after; the
before/after table and the ceilings it sets go here when the machine has
answered.

  • 20c87afc gives the panel a census — paints, pixels, time in the painter,
    slowest single paint — on both channels a boot can end on, and prices the
    slowest paint per boot in tests/metal-profile.toml.
  • 4ab5a65f keeps the grid the panel painted in RAM and writes only the cells
    that moved, instead of clearing the whole scanout per record.

🤖 Generated with Claude Code

Japabu and others added 5 commits September 8, 2026 17:44
The on-screen console is the one live window a person has into a machine
that stopped, and nothing counted what it costs to keep. This adds the
count: how often the panel painted, how many pixels it put on the glass,
how long it spent inside the painter and the slowest single paint of the
boot.

The reading is taken in ticks of the cycle counter and converted once, at
the census: every boot's first paints happen before `clock::init`, so a
panel instrumented in nanoseconds would report the paints it most needs to
report as zero.

Two channels carry the line, because the panel outlives one of them. A boot
that hands the machine back writes it as an ordinary record beside `irq:`
and `nvme:`, and `logd` files it; a boot a bound ended has no `logd` left,
so the same line goes into the black-box page the next loader pass prints
back. It is written into the page's head and not its tail, which is where
the page is cut when the ring does not fit.

`tests/metal-profile.toml` prices the slowest single paint per boot, under
the one ceiling mechanism the suite already has. The ceiling is the widest
true bound there is before the machine has answered: two seconds, which is
`panic_console`'s own CHECKPOINT budget — past it a process claiming the
framebuffer stops waiting for the painter and says so. The other three
numbers are printed beside the boot as evidence and are not a gate.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
Every paint cleared the whole scanout and drew the text back over it: one
framebuffer written per record the panel was asked to show, whatever had
changed. On the T14 that is 2,073,600 stores of four bytes each, and the
first paints of every boot make them through the mapping the loader left,
where nothing combines them.

So the panel now keeps the grid it painted. A paint composes each row's
cells — its display row's text, ground past the end of it, the footer on
the bottom row of a paged report — and writes only the cells whose
character or colour moved, ground and ink together, so nothing has to be
cleared first. A record added to a screen that is not yet full costs the
one row it adds; a scroll costs the cells whose text moved and not the
blank right-hand end of any row. The fill is left for the paints that need
it: the first of a boot, a change of colour or geometry, and every paint
after something else may have reached the glass — a userland claimant, a
new framebuffer descriptor, a fatal report taking the screen back, or the
report's own probes finding it drawn over.

What the panel shows does not change: same font, same grid, same text, same
colours. A cell holds the character the font draws and not the byte that
asked for it, so what the grid says is on the glass is what the glyph
lookup would have put there.

`screen_late_panic` gets the assertion the grid owes: on a machine halted
for good, with its console whole, every row on the panel is text the log
carries — which is what a cell the paint failed to write would not be, past
the end of a line that replaced a longer one. The pager cycling a report
across that screen is where a screen of long lines becomes one of short
lines.

`put_pixel` and its per-pixel clamp go: every write to the scanout is now a
row of a cell through one bounds-proved pointer, or the fill.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
`src/sourcegate.rs` counts them per file so that an added one reds beside a
permitted one; the grid the painter diffs against is the third.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Run 32 measured the panel's slowest paint at 442-449 ms on every arm of every
boot, before the grid and after it: one whole-screen fill of 1920x1080 through
the uncacheable 2 MiB pages the loader hands the scanout over in, two million
stores nothing combines. The other nine paints of that boot cost about 2 ms
each through write-combining, which is the speed this hardware has. The fix
kept the first paint as a fill by design, so the fill is what has to stop being
uncacheable.

So the memory type is decided before the panel paints rather than after
`mm::init`. `control_regs::init_cr0` and `pat::init` move to the top of
`kernel_main`: two register writes and no memory, before any channel exists,
because the panel's own first record is its first paint. Neither logs, so the
boot's first record is still the panel's own and every reader of the record
order is unchanged; the `PAT:` line stays where the boot's other facts are and
reads the live register.

`mm::paging::boot_map_write_combining` then switches the scanout's 2 MiB leaves
in the tables this CPU is running under — the loader's — to the write-combining
PAT entry, both of the windows the loader opened onto it, because one physical
page may not hold two memory types. It refuses by name where the walk finds no
2 MiB leaf, and the panel's armed record says which mapping it got.

`tests/metal-profile.toml` holds `panel_max_us` to one frame of the panel this
bench has: the T14 takes a whole 1080p redraw at 60 Hz, so 16,667 us is what a
paint of every cell may cost and still keep pace with the display. The two
seconds those rows carried was a bound nothing could ever red on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
The two-lines-a-second crawl on the T14 was never the kernel's panel: run 32
measured ten paints on a boot that sat wedged for two minutes, the same ten a
2.7 s boot makes. It is the loader's pass after the reset, which prints the
black box's ring tail line by line through the firmware's text console — 187
lines on run 32's `deadlinewedge`, every one of them stamped `[1.xxx cpu0]`,
which is why the timings on the glass stood still. The firmware scrolls a 1080p
screen by moving the whole frame, about three lines a second, and the wedge
boots came back 104 s and 114 s after their own bounds against 37-49 s for a
boot with no record to print.

So the screen gets the head and the file gets the tail. A line that opens a log
record — `toyos_blackbox::RECORD_OPENS_WITH`, the same declaration the kernel
cuts the page on — is tail; the reason the boot ended, what was dropped, a
fault's registers are head, and the head carries the count of what it does not
carry. `loader.log` is unchanged: every line still reaches it, which is where
every reader of these lines has always looked.

`boot_deadline_ends_a_wedge`'s QEMU half asserted the tail off the console,
because in a guest both channels are one wire. It now asserts the count there,
and `deadline_wedge_chain` — the metal judge, which reads `loader.log` off the
stick — is where the records themselves are still held to `WEDGE_STAGED`.

Filed while reading those readbacks:
issues/diagnostics/loader-log-stops-at-sixteen-kilobytes.md. Every wedge boot's
`loader.log` ends mid-tail near 16 KiB with none of the lines the loader writes
after it, and the one statement of why goes to the console alone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant