gfx: the console and the crash screen stop drawing on a driver by name

Same disease as the network stack, one subsystem over. kernel/dev/vga.c
was three things wearing one name: the owner of the framebuffer, a
bitmap drawing library, and the text console. Only the first is a
driver, and because they shared a file they shared a prefix -- so
panic.c drew the crash screen with vga_fill_rect, console.c printed with
vga_putc, /dev/fb0 handed out vga_fb_phys, and every one of those files
named a display driver it had no business knowing about.

Split three ways:

  - kernel/drivers/gpu/vga/vga_fb.c owns the VBE framebuffer -- the mode
    the loader programmed and handed over, the aperture, and the display
    class vtable. It is a driver and now lives with the driver that
    finds the card on the bus.
  - kernel/dev/gfx.c draws: pixels, shapes, glyphs, the cursor, scroll,
    the ANSI erase operations. It names no driver. The framebuffer
    arrives through gfx_attach_scanout(), so a driver publishes what it
    owns and this file draws on it without knowing what it is.
  - include/gfx.h is the interface, and says why it is deliberately not
    the display class: that one is about frames -- acquire, present,
    vblank -- and the crash screen has to work when nothing else does.

main() no longer calls a display driver. platform_register_devices()
takes the framebuffer address and brings up whatever owns it, which is
where driver names belong; kernel/drivers/registry.c is the file whose
entire job is naming every driver in the kernel.

virtio-gpu got the e1000 treatment as well: instances in an array, the
instance hung on dev->priv by an `attached` hook, and every op reading
it rather than a file-static. It refused the second adapter outright
before, which is not hypothetical -- integrated plus discrete is an
ordinary laptop. The VGA driver deliberately has no such hook, and says
so: there is one framebuffer the loader programmed however many
VGA-class controllers the bus turns up, and a second one publishing the
same scanout is the truth rather than a bug.

Private headers moved next to their drivers: include/virtio.h and
include/xhci.h each had exactly one includer. include/ is for headers
the rest of the kernel needs.

Deleted rather than moved: consoleinit() programmed the sixteen ANSI
colours into the VGA DAC and disabled text-mode blink, by writing ports
0x3C0-0x3C9 from the console. The comment three lines above the colour
table already said this does nothing in a 32bpp linear mode -- there is
no palette to index -- so it was both dead and in the wrong file.

Verified, and this is a refactor so the evidence is that nothing moved:
  - bootcheck -Fb -Net: 59 [ok], no failures. That covers the console
    through every command, /dev/fb0's geometry, writing pixels through
    write() and mapping the framebuffer through mmap.
  - make crashshot: the crash screen renders exactly as before --
    shapes, glyphs, logo and QR code -- which is the whole of gfx.c
    exercised at once, in a panic, with the scanout attached by a driver.
  - make schcom-test: five pixel assertions from the host oracle still
    pass, so the display class and the compositor above it are unmoved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-08-15 17:04:10 +02:00
commit 817d666068
22 changed files with 942 additions and 752 deletions

View file

@ -1,6 +1,7 @@
#include <stdarg.h>
#include "types.h"
#include "defs.h"
#include "gfx.h"
#include "theme.h"
#include "irq.h"
#include "param.h"
@ -35,13 +36,13 @@ int panicked = 0;
// and the display genuinely belongs to the console. The F8 verbose boot never
// sets it, so "show me everything" still works.
int cons_serial_only = 1;
extern void vga_screen_reset(void);
extern void gfx_screen_reset(void);
// Text colour used when drawing to the framebuffer. Changed by SGR escapes
// (see sgr() below); reset to the default by ESC[0m and ESC c.
//
// This was initialised to 7, a leftover from when it was a VGA text-mode
// attribute byte. vga_putc takes a 32-bit RGB value, so 7 meant "very dark
// attribute byte. gfx_putc takes a 32-bit RGB value, so 7 meant "very dark
// blue" -- it looked black against the cream background purely by accident.
static uint32 cons_fg = SCH_FG;
@ -166,7 +167,7 @@ console_takeover(void)
if (!cons_serial_only)
return; // verbose boot: the console never left
cons_serial_only = 0;
vga_screen_reset();
gfx_screen_reset();
}
// Foreground process group of the console, i.e. the controlling terminal.
@ -197,6 +198,11 @@ int tty_fg_pgrp = 1;
// consoleinit() used to program these into the VGA DAC with set_vga_palette(),
// which does nothing at all in a 32bpp linear mode -- there is no palette to
// index. The colours had to live somewhere real, and this is it.
//
// That DAC code is now gone rather than merely unused, along with the blink
// disable next to it: both were writes to VGA hardware registers made from the
// console, which is neither a driver nor allowed to know it was running on a
// VGA. Nothing observable changed, because nothing observable depended on it.
static const uint32 ansi_rgb[16] = {
0x000000, 0xAA0000, 0x00AA00, 0xAA5500, // black red green yellow(brown)
0x0000AA, 0xAA00AA, 0x00AAAA, 0xAAAAAA, // blue magenta cyan white
@ -324,7 +330,7 @@ sgr(void)
cons_bg = ansi_rgb[(p - 100) + 8];
}
}
vga_set_bg(cons_bg);
gfx_set_bg(cons_bg);
}
// One final byte of a CSI sequence, with esc_parm[] already filled in.
@ -335,32 +341,32 @@ csi_dispatch(int final)
int n = esc_nparm > 0 ? esc_parm[0] : 0;
int n1 = n ? n : 1; // most sequences default to 1
vga_cursor_get(&col, &row);
gfx_cursor_get(&col, &row);
switch (final) {
case 'A': vga_cursor_set(col, row - n1); break;
case 'B': vga_cursor_set(col, row + n1); break;
case 'C': vga_cursor_set(col + n1, row); break;
case 'D': vga_cursor_set(col - n1, row); break;
case 'E': vga_cursor_set(0, row + n1); break;
case 'F': vga_cursor_set(0, row - n1); break;
case 'G': vga_cursor_set(n1 - 1, row); break;
case 'd': vga_cursor_set(col, n1 - 1); break;
case 'A': gfx_cursor_set(col, row - n1); break;
case 'B': gfx_cursor_set(col, row + n1); break;
case 'C': gfx_cursor_set(col + n1, row); break;
case 'D': gfx_cursor_set(col - n1, row); break;
case 'E': gfx_cursor_set(0, row + n1); break;
case 'F': gfx_cursor_set(0, row - n1); break;
case 'G': gfx_cursor_set(n1 - 1, row); break;
case 'd': gfx_cursor_set(col, n1 - 1); break;
case 'H': case 'f': {
int r = esc_nparm > 0 && esc_parm[0] ? esc_parm[0] : 1;
int c = esc_nparm > 1 && esc_parm[1] ? esc_parm[1] : 1;
vga_cursor_set(c - 1, r - 1); // ANSI counts from 1
gfx_cursor_set(c - 1, r - 1); // ANSI counts from 1
break;
}
case 'J': vga_erase_display(n); break;
case 'K': vga_erase_line(n); break;
case 'J': gfx_erase_display(n); break;
case 'K': gfx_erase_line(n); break;
case 'm': sgr(); break;
case 's': saved_col = col; saved_row = row; break;
case 'u': vga_cursor_set(saved_col, saved_row); break;
case 'u': gfx_cursor_set(saved_col, saved_row); break;
// Cursor visibility (DECTCEM) and the other private modes. There is no
// hardware cursor being drawn, so there is nothing to show or hide --
@ -375,7 +381,7 @@ csi_dispatch(int final)
// Feed one output byte to the framebuffer side. Returns nothing; the serial
// side has already had the raw byte.
static void
vga_feed(int c)
gfx_feed(int c)
{
switch (esc_state) {
case E_ESC:
@ -390,7 +396,7 @@ vga_feed(int c)
if (c == 'c') { // RIS -- full reset
cons_fg = CONS_DEFAULT_FG;
cons_bg = CONS_DEFAULT_BG;
vga_screen_reset();
gfx_screen_reset();
}
return; // other two-byte escapes: ignored
@ -421,23 +427,23 @@ vga_feed(int c)
if (u8_need > 0) {
if ((c & 0xC0) != 0x80) { // malformed: abandon the sequence
u8_need = 0;
vga_putc('?', cons_fg);
gfx_putc('?', cons_fg);
return;
}
u8_cp = (u8_cp << 6) | (c & 0x3F);
if (--u8_need > 0)
return;
int g = glyph_for(u8_cp);
vga_putc(g ? g : '?', cons_fg);
gfx_putc(g ? g : '?', cons_fg);
return;
}
if (c >= 0xF0) { u8_cp = c & 0x07; u8_need = 3; return; }
else if (c >= 0xE0) { u8_cp = c & 0x0F; u8_need = 2; return; }
else if (c >= 0xC0) { u8_cp = c & 0x1F; u8_need = 1; return; }
else if (c >= 0x80) { vga_putc('?', cons_fg); return; } // stray continuation
else if (c >= 0x80) { gfx_putc('?', cons_fg); return; } // stray continuation
vga_putc(c, cons_fg);
gfx_putc(c, cons_fg);
}
void
@ -451,13 +457,13 @@ consputc(int c)
if (c == BACKSPACE) {
uartputc('\b'); uartputc(' '); uartputc('\b');
if (!cons_serial_only)
vga_putc(c, cons_fg);
gfx_putc(c, cons_fg);
return;
}
uartputc(c);
if (!cons_serial_only)
vga_feed(c & 0xff);
gfx_feed(c & 0xff);
}
#define INPUT_BUF 256
@ -747,25 +753,6 @@ consolewrite(struct inode *ip, uint64 off, char *buf, int n)
return n;
}
void
disable_blink(void)
{
inb(0x3DA);
outb(0x3C0, 0x10 | 0x20);
uchar dummy = inb(0x3C1);
outb(0x3C0, dummy & ~0x08);
}
static void
set_vga_palette(uchar index, uchar r, uchar g, uchar b)
{
outb(0x3C6, 0xFF);
outb(0x3C8, index);
outb(0x3C9, r & 0x3F);
outb(0x3C9, g & 0x3F);
outb(0x3C9, b & 0x3F);
}
// The keyboard handler lives in kbd.c, but the console is what owns the
// keyboard as an input source, so this is where the line gets claimed.
static void
@ -787,24 +774,5 @@ consoleinit(void)
devsw[CONSOLE].read = consoleread;
cons.locking = 1;
disable_blink();
set_vga_palette(0, 0, 0, 0); // Black
set_vga_palette(1, 0, 0, 42); // Blue
set_vga_palette(2, 0, 42, 0); // Green
set_vga_palette(3, 0, 42, 42); // Cyan
set_vga_palette(4, 42, 0, 0); // Red
set_vga_palette(5, 42, 0, 42); // Magenta
set_vga_palette(6, 42, 21, 0); // Brown
set_vga_palette(7, 42, 42, 42); // Light Gray
set_vga_palette(8, 21, 21, 21); // Dark Gray
set_vga_palette(9, 21, 21, 63); // Light Blue
set_vga_palette(10, 21, 63, 21); // Light Green
set_vga_palette(11, 21, 63, 63); // Light Cyan
set_vga_palette(12, 63, 21, 21); // Light Red
set_vga_palette(13, 21, 21, 63); // Light Magenta
set_vga_palette(14, 63, 63, 21); // Yellow
set_vga_palette(15, 63, 63, 63); // White
irq_register(IRQ_KBD, kbd_irq, 0, "ps2-kbd", 0);
}