A boot printed about thirty lines before reaching a prompt. Every one was
useful once, and printed on every boot for ever they train the person in front
of the machine to stop reading their own console -- which is expensive on the
day one of them changes.
SchOS UEFI loader
Press F8 for boot options... booting
Welcome to SchOS! (F8 at boot for the full log, or dmesg once you are in)
root@schos / »
**Nothing was deleted, it was routed.** consputc() already wrote to the klog
ring before anywhere else, so a line can be kept in full while being held back
from the two places a person looks. kinfo() is cprintf() with that routing, and
`dmesg` still has the whole boot with timestamps afterwards. The rule for which
is which is not how interesting a line is -- everything was interesting to
somebody once -- but whether you would want to see it on a machine that is
*working*. `pci: 7 devices` is yes the first time and no every time after;
`e1000: no DMA memory for the rings` is yes for ever and stays a cprintf.
The verbose boot the F8 menu already offered now means something much bigger,
which is the other half of this: it is the only way to see any of it live.
Userspace follows through /proc/bootmode, one word. schinit, schinitd and vgpud
hold back their progress lines and never their failures -- a supervisor that
goes quiet when a job dies is worse than one that chatters.
Two things found by testing rather than by reading:
- schinit read /proc/bootmode *before* bootstrap() mounted /proc, so it got
"quiet" every time and the verbose boot was quiet in the one process that
runs first. It reads after the mount now.
- resolvconf announced "already exists; -f to replace it" on every boot after
the first, which is a line about having done nothing. It is quiet when the
file already says what it would have written, and speaks only when the two
disagree -- which means the nameserver changed and the machine is using the
old one.
Verified both ways: quiet boot is six lines, a forced-verbose build prints all
thirty plus the `verbose boot:` steps, and /proc/kmsg holds every line in both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
100 lines
3.7 KiB
C
100 lines
3.7 KiB
C
// 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.
|
|
static struct pci_bar *
|
|
framebuffer_bar(struct pci_dev *d)
|
|
{
|
|
uint64 need = vga_fb_size();
|
|
|
|
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 && bar->base != vbe) {
|
|
// 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,
|
|
};
|