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

@ -74,6 +74,13 @@ void gfx_putc(int c, uint32 color);
void gfx_puts(int x, int y, char *s, uint32 color);
void gfx_set_bg(uint32 color);
void gfx_screen_reset(void);
// See kernel/dev/gfx.c: drawing goes to a copy of the screen in RAM once
// gfx_accelerate() has run, and reaches the display at gfx_flush().
void gfx_accelerate(void);
void gfx_flush(void);
void gfx_flush_all(void); // the crash screen: unconditionally
volatile uchar *gfx_hw_ptr(void);
int gfx_cols(void);
int gfx_rows(void);

21
include/vmap.h Normal file
View file

@ -0,0 +1,21 @@
#pragma once
// Kernel mappings that are neither the direct map nor plain device registers.
// Both take one 1 GB slot of the device window ioremap() hands out, so each is
// limited to a gigabyte and meant to be called a handful of times at boot.
// Program this CPU's PAT so that a mapping can ask for write-combining. Every
// CPU runs it before it touches a write-combined mapping -- the PAT is per
// processor, and one left at its reset value reads the same bits as
// write-back, which on device memory the MTRRs then turn back into uncached.
void pat_init(void);
// Device memory, write-combined: stores are gathered into bursts rather than
// sent one at a time, and reads are as slow as uncached. For a framebuffer,
// never for registers. Returns 0 if there is no window left.
void *ioremap_wc(uint64 pa, uint64 size);
// `size` bytes of ordinary RAM, contiguous in the kernel's address space and
// not physically: kalloc() pages behind a page table of their own. Zeroed.
// For buffers too big for the DMA arena that no device reads. Never freed.
void *vmap_alloc(uint64 size);