// Display controller -- the first driver to find its hardware by asking. // // It does not draw anything: vga_fb.c in this directory owns the framebuffer, // and it learned where that is from the bootloader's VBE call. What this adds // is a second, independent answer to the same question, from the bus rather // than from the BIOS -- and a complaint when the two disagree. // // No `attached` hook and no per-instance state, unlike the e1000 and // virtio-gpu: there is exactly one framebuffer the loader programmed, however // many VGA-class controllers the bus turns up. A second one binding here // publishes the same scanout, which is the truth -- it is the same screen. // // That is worth more than it sounds. Three places in the kernel used to // hardcode 0xfd000000 as the framebuffer address, and the only reason nobody // noticed was that QEMU's stdvga happens to put it there. A disagreement // between VBE and the card's own BAR is exactly the symptom that would have // shown up first on hardware where the guess was wrong, and until now there // was nothing in the kernel capable of noticing it. #include "types.h" #include "defs.h" #include "param.h" #include "x86.h" #include "spinlock.h" #include "sleeplock.h" #include "fs.h" #include "file.h" #include "pci.h" #include "display.h" // The framebuffer lives in the first memory BAR big enough to hold a screen. // Display controllers commonly expose two apertures -- the framebuffer and a // much smaller register block -- and which index they land on varies, so the // choice is made by size rather than by position. // // Except that the one the loader's mode actually lives in wins outright. An // NVIDIA card's BAR0 is a 16 MB register block, which is big enough to pass // for an 800x600 framebuffer and is not one; the screen is in BAR1. Taking // the first that fits reported a "disagreement" that was the driver's own. static struct pci_bar * framebuffer_bar(struct pci_dev *d) { uint64 need = vga_fb_size(), vbe = vga_fb_phys(); for (int i = 0; i < 6; i++) { struct pci_bar *b = &d->bar[i]; if (b->base && !b->is_io && vbe >= b->base && vbe < b->base + b->size) return b; } for (int i = 0; i < 6; i++) { struct pci_bar *b = &d->bar[i]; if (b->base == 0 || b->is_io) continue; if (b->size >= need) return b; } return 0; } static int vga_pci_probe(struct pci_dev *d) { struct pci_bar *bar = framebuffer_bar(d); uint64 vbe = vga_fb_phys(); // Memory decoding has to be on for the aperture to respond at all. The BIOS // will have done this already to put up a boot splash, but a device we only // reached through enumeration cannot be assumed to have been touched. pci_enable(d, PCI_CMD_MEMORY); if (bar == 0) { kinfo("vga: display controller at %x:%x.%x has no framebuffer aperture\n", d->bus, d->slot, d->func); return 0; // still ours; nothing more to do } if (vbe != 0 && (vbe < bar->base || vbe >= bar->base + bar->size)) { // Trust VBE: it describes the mode that is actually programmed and being // displayed, which is the thing vga.c is writing into. The BAR is the // card's whole aperture and the mode may sit at an offset within it. Say // so rather than silently preferring one. cprintf("vga: framebuffer disagreement -- VBE says %x, BAR says %x\n", (uint32)vbe, (uint32)bar->base); } kinfo("vga: display controller %x:%x, aperture %x (%d MB)\n", d->vendor, d->device, (uint32)bar->base, (uint32)(bar->size / (1024 * 1024))); return 0; } // Matched by class, not by model: every VGA-compatible controller answers // class 0x03, and this driver cares about none of the differences between // them. static const struct pci_match vga_pci_match[] = { { PCI_ANY, PCI_ANY, PCI_CLASS_DISPLAY, 0xFF }, { 0, 0, 0, 0 }, }; // The display vtable lives in kernel/dev/vga.c, which owns the framebuffer; // this file only finds the card on the bus. Publishing it here is what makes // device_find_class(DEVCLASS_DISPLAY) return something a compositor can use. extern const struct display_ops vga_display_ops; const struct pci_driver vga_pci_driver = { .name = "vga", .match = vga_pci_match, .ops = &vga_display_ops, .probe = vga_pci_probe, };