gfx: draw the console in RAM, and write it to the card write-combined

On the first real machine with a discrete card (an RTX 3060, 800x600 from
the UEFI GOP) the console was slow enough to photograph mid-scroll, and the
boot died on CPU 1's "cpu1: starting": CPU 0 spun 200M times on the console
lock while CPU 1 held it, scrolling, and called it a deadlock.

The framebuffer is device memory in the uncached 3-4 GB page of the direct
map. QEMU's stdvga is RAM underneath, so nothing showed there; on a card,
gfx_scroll_up's memmove read the whole screen back across the bus one byte
at a time, and every anti-aliased glyph read back each pixel it blended.

So the console now draws into a copy of the screen in RAM (vmap_alloc: kalloc
pages behind a page table of their own, since nothing needs it contiguous),
and gfx_flush() copies the changed rectangle onto the card through an
ioremap_wc() mapping -- PAT entry 4 set to write-combining on every CPU.
Flushes happen where drawing ends: a message, a terminal write, an echo, a
terminal switch. Scrolling moves eight bytes at a time instead of one.

/dev/fb0 reads and writes the card itself, as before. The early console's
fb_info now carries the physical address it always should have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-22 23:46:58 +02:00
commit 60d06658f0
9 changed files with 347 additions and 8 deletions

View file

@ -41,6 +41,27 @@ is now a second caller of one function rather than an edit to the crash screen.
left running says so before any bus is enumerated, because the console draws
long before PCI is walked.
### Drawing in RAM
The console does not draw on the card. Once there is a page allocator,
`gfx_accelerate()` gives it a copy of the screen in RAM (`vmap_alloc()`, so it
needs no contiguous memory) and maps the real framebuffer write-combined
(`ioremap_wc()`, PAT entry 4). Every drawing routine reads and writes the copy;
`gfx_flush()` copies the rectangle that changed onto the card, and is called
where drawing ends — after a message, a `write()` to a terminal, an echo, a
terminal switch, the crash screen — not where it happens.
On QEMU's stdvga the framebuffer is RAM, so none of this was visible. On a real
graphics card it is device memory behind the PCI hole, uncached: a scroll is a
`memmove` that reads the whole screen back across the bus a byte at a time,
and an anti-aliased glyph reads every pixel it blends. Measured on an RTX 3060
at 800×600, one scroll took long enough to photograph half done, and a second
CPU waiting to print declared the console lock deadlocked.
`/dev/fb0` still reads and writes the card itself (`gfx_hw_ptr()`), and its
`mmap` still maps the physical framebuffer, so a program drawing on the screen
and `vgpud` mirroring it see what is on the screen.
### `display` — the class
`struct display_ops`, found with `device_find_class(DEVCLASS_DISPLAY)`.