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:
parent
20cc673a41
commit
817d666068
22 changed files with 942 additions and 752 deletions
216
kernel/drivers/gpu/vga/vga_fb.c
Normal file
216
kernel/drivers/gpu/vga/vga_fb.c
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
// The VBE linear framebuffer: the display the loader left running.
|
||||
//
|
||||
// This is the driver half of what used to be kernel/dev/vga.c. That file was
|
||||
// three things at once -- the owner of the framebuffer, a bitmap drawing
|
||||
// library, and the text console -- and only the first is a driver. The other
|
||||
// two are now kernel/dev/gfx.c and do not name any driver at all, so the crash
|
||||
// screen and the console draw on whatever hardware is bound rather than on
|
||||
// this one by name. See include/gfx.h.
|
||||
//
|
||||
// What stays here is everything that knows *where the pixels are*: the mode the
|
||||
// loader programmed and handed over in the boot block, the aperture it lives
|
||||
// in, and the display class vtable that presents all of it as one of possibly
|
||||
// several displays.
|
||||
|
||||
#include "types.h"
|
||||
#include "x86.h"
|
||||
#include "memlayout.h"
|
||||
#include "defs.h"
|
||||
#include "device.h"
|
||||
#include "display.h"
|
||||
#include "gfx.h"
|
||||
#include "dma.h"
|
||||
#include "schboot.h" /* the hand-off block */
|
||||
|
||||
static volatile uchar *fb;
|
||||
// Physical base of the linear framebuffer, as reported by VBE. Everything that
|
||||
// needs to map the framebuffer -- fork's VMA copy, mmap of /dev/fb0 -- asks
|
||||
// rather than assuming a value. Three places used to hardcode 0xfd000000,
|
||||
// which is only where QEMU's stdvga happens to put it.
|
||||
static uint64 fb_phys;
|
||||
static uint32 fb_stride;
|
||||
static uint32 fb_bpp;
|
||||
static uint32 fb_width = 800;
|
||||
static uint32 fb_height = 600;
|
||||
|
||||
// Bytes of framebuffer, measured in scanlines rather than as
|
||||
// width * height * bpp/8 -- the two differ whenever the card pads its rows,
|
||||
// and it is the stride that says how far apart two rows really are.
|
||||
uint64
|
||||
vga_fb_size(void)
|
||||
{
|
||||
if (!fb)
|
||||
return 0;
|
||||
return (uint64)fb_stride * (uint64)fb_height;
|
||||
}
|
||||
|
||||
// The framebuffer address VBE reported, for the PCI half of this driver to
|
||||
// cross-check against the aperture it finds on the bus. Zero before init.
|
||||
uint64
|
||||
vga_fb_phys(void)
|
||||
{
|
||||
return fb_phys;
|
||||
}
|
||||
|
||||
// Read the mode the loader programmed and publish it as a scanout.
|
||||
//
|
||||
// Called from platform_register_devices(), because the framebuffer is a fact
|
||||
// about the machine that the loader established before any bus was walked --
|
||||
// the PCI display driver binds much later, and the console has to be able to
|
||||
// draw long before that.
|
||||
//
|
||||
// Deliberately does NOT clear the screen. The loader has already drawn the
|
||||
// splash, and wiping it here is what produced the old two-act boot: a purple
|
||||
// splash for an instant, then an abrupt cream screen filling with log lines.
|
||||
// The splash stays up for the whole of boot, and console_takeover() clears it
|
||||
// exactly once, when userspace starts and the display changes owner.
|
||||
void
|
||||
vga_fb_init(uint64 phys_addr)
|
||||
{
|
||||
struct fb_info info;
|
||||
|
||||
if (phys_addr == 0)
|
||||
phys_addr = *(uint32*)(P2V(SCHBOOT_VBE_LFB));
|
||||
|
||||
fb = (volatile uchar*)P2V(phys_addr);
|
||||
fb_phys = phys_addr;
|
||||
fb_stride = *(ushort*)(P2V(SCHBOOT_VBE_STRIDE));
|
||||
fb_width = *(ushort*)(P2V(SCHBOOT_VBE_WIDTH));
|
||||
fb_height = *(ushort*)(P2V(SCHBOOT_VBE_HEIGHT));
|
||||
fb_bpp = *(uchar*)(P2V(SCHBOOT_VBE_BPP));
|
||||
|
||||
if (fb_width == 0) fb_width = 800;
|
||||
if (fb_height == 0) fb_height = 600;
|
||||
if (fb_stride == 0) fb_stride = fb_width * (fb_bpp / 8);
|
||||
|
||||
cprintf("vga: %dx%d, %d bpp, stride %d, LFB: 0x%x\n",
|
||||
fb_width, fb_height, fb_bpp, fb_stride, (uint32)phys_addr);
|
||||
|
||||
info.ptr = fb;
|
||||
info.phys = fb_phys;
|
||||
info.size = vga_fb_size();
|
||||
info.stride = fb_stride;
|
||||
info.bpp = fb_bpp;
|
||||
info.width = fb_width;
|
||||
info.height = fb_height;
|
||||
gfx_attach_scanout(&info);
|
||||
}
|
||||
|
||||
// --- display class implementation ------------------------------------------
|
||||
//
|
||||
// See include/display.h. The VBE framebuffer presenting itself as a generic
|
||||
// display, so a compositor is written against the class rather than against
|
||||
// this file.
|
||||
|
||||
static uchar *back; // shadow buffer in RAM, or 0 if never acquired
|
||||
static uint64 back_bytes;
|
||||
|
||||
static void
|
||||
vga_disp_mode(struct device *d, struct display_mode *m)
|
||||
{
|
||||
(void)d;
|
||||
m->width = (int)fb_width;
|
||||
m->height = (int)fb_height;
|
||||
m->bpp = (int)fb_bpp;
|
||||
m->stride = fb_stride;
|
||||
}
|
||||
|
||||
static void *
|
||||
vga_disp_acquire(struct device *d, uint32 *stride_out)
|
||||
{
|
||||
(void)d;
|
||||
if (stride_out)
|
||||
*stride_out = fb_stride;
|
||||
if (back)
|
||||
return back;
|
||||
if (!fb || fb_height == 0 || fb_stride == 0)
|
||||
return 0;
|
||||
|
||||
back_bytes = (uint64)fb_stride * fb_height;
|
||||
|
||||
// From the DMA arena, which is the wrong pool and is used here because it is
|
||||
// the only *physically contiguous* allocator in the kernel -- kalloc hands
|
||||
// out single pages whose neighbours are adjacent only by chance, and a
|
||||
// framebuffer wants one flat span.
|
||||
//
|
||||
// At 800x600x32 this is 1.9 MB of a 4 MB arena meant for device rings, which
|
||||
// is tolerable only because nothing else large lives there yet. The real fix
|
||||
// is a contiguous page allocator, or reserving this at boot from the memory
|
||||
// map -- which is work that belongs with the E820 change in docs/roadmap.md
|
||||
// §3b, since that is what stops the kernel pretending 224 MB exists.
|
||||
//
|
||||
// Allocated on first use rather than at boot on purpose: a machine that never
|
||||
// starts a compositor should not pay two megabytes for one.
|
||||
back = dma_alloc(back_bytes, 4096);
|
||||
if (!back) {
|
||||
cprintf("display: no room for a %d KB back buffer; drawing will tear\n",
|
||||
(int)(back_bytes / 1024));
|
||||
return 0;
|
||||
}
|
||||
memset(back, 0, back_bytes);
|
||||
cprintf("display: %d KB back buffer, double buffered\n",
|
||||
(int)(back_bytes / 1024));
|
||||
return back;
|
||||
}
|
||||
|
||||
// Blit a rectangle from the shadow buffer to the framebuffer.
|
||||
//
|
||||
// Row at a time rather than one memmove for the whole rectangle: only a
|
||||
// full-width rectangle is contiguous in either buffer, and a compositor
|
||||
// updating one window is the case worth being right about.
|
||||
static void
|
||||
vga_disp_present(struct device *d, int x, int y, int w, int h)
|
||||
{
|
||||
(void)d;
|
||||
if (!back || !fb)
|
||||
return;
|
||||
|
||||
if (x < 0) { w += x; x = 0; }
|
||||
if (y < 0) { h += y; y = 0; }
|
||||
if (x + w > (int)fb_width) w = (int)fb_width - x;
|
||||
if (y + h > (int)fb_height) h = (int)fb_height - y;
|
||||
if (w <= 0 || h <= 0)
|
||||
return;
|
||||
|
||||
int bytes = w * (int)(fb_bpp / 8);
|
||||
uint32 off = (uint32)x * (fb_bpp / 8);
|
||||
for (int row = y; row < y + h; row++)
|
||||
memmove((void *)(fb + row * fb_stride + off),
|
||||
back + row * fb_stride + off, bytes);
|
||||
}
|
||||
|
||||
// Wait for vertical retrace, by polling the VGA input status register.
|
||||
//
|
||||
// Bit 3 of 0x3DA is set during vertical retrace. This is the original 1987 VGA
|
||||
// interface and it still works on everything that emulates a VGA, QEMU's
|
||||
// stdvga included -- VBE's own protected-mode interface for this is optional
|
||||
// and frequently absent, so the old port is the portable answer.
|
||||
//
|
||||
// Two loops, not one: waiting only for the flag to *be* set would return
|
||||
// immediately if a retrace is already in progress, having just missed the
|
||||
// start of it. Waiting for the current one to end and the next to begin is
|
||||
// what actually lands the blit between frames.
|
||||
//
|
||||
// The counters are a deadlock guard. On hardware that never reports retrace
|
||||
// this returns after a bounded spin instead of hanging the caller forever,
|
||||
// which matters because this can be called with the console locked.
|
||||
static void
|
||||
vga_disp_wait_vblank(struct device *d)
|
||||
{
|
||||
int guard;
|
||||
|
||||
(void)d;
|
||||
for (guard = 0; guard < 1000000; guard++)
|
||||
if (!(inb(0x3DA) & 0x08))
|
||||
break;
|
||||
for (guard = 0; guard < 1000000; guard++)
|
||||
if (inb(0x3DA) & 0x08)
|
||||
break;
|
||||
}
|
||||
|
||||
const struct display_ops vga_display_ops = {
|
||||
.mode = vga_disp_mode,
|
||||
.acquire = vga_disp_acquire,
|
||||
.present = vga_disp_present,
|
||||
.wait_vblank = vga_disp_wait_vblank,
|
||||
};
|
||||
Loading…
Reference in a new issue