// 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: whoever wants the framebuffer cleared // says so, and this function is only about where it is. kmain clears it once, // right after attaching the loader's scanout, and the kernel log runs on screen // from there -- see cons_serial_only in kernel/core/console.c for why the // splash no longer survives the whole boot. 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); kinfo("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/architecture/boot.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, }; // Put the VBE framebuffer back on screen. // // Called when a better display driver lets go of the hardware -- see // virtio-gpu's release hook. gfx holds one scanout, last writer wins, so the // driver that leaves has to name its replacement rather than simply detaching; // detaching alone leaves gfx pointing at a device nobody is driving. // // This is the fallback rule from docs/plan/hybrid.md made real: the kernel // keeps a minimal driver for everything on the path to a shell, so a fancy // driver leaving degrades to a working screen instead of to no screen. int vga_fb_restore(void) { struct fb_info info; // Zero means there is no framebuffer to go back to, and the caller has to // care. It used to return void and say nothing here, which reads as "done" // -- so on a machine whose only display is the one being handed over, the // eviction went ahead and left the console drawing into a driver that had // just freed its memory. The answer is not to try harder; it is to say no, // which is what the caller now does. if (!fb || !fb_width || !fb_height) return 0; 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); return 1; }