gfx: a framebuffer at physical zero is not a framebuffer
Booting with a virtio-gpu as the *only* display reboot-looped. Not a hang, not
a panic: the firmware came back round to the boot menu, started the loader
again, and did it again, for ever, with nothing to say why.
virtio-gpu has no linear aperture, so OVMF's virtio GOP implements Blt and
nothing else. On a machine with one and nothing else, the firmware reports a
width and a height and a FrameBufferBase of **zero**:
schos: framebuffer 0x0000000000000000 0x500x0x320
Two places took that at face value.
**kmain() tested `if (bi.ptr && ...)` with bi.ptr already run through P2V()**,
and P2V(0) is KERNBASE, which is not zero. So the guard could not fail -- and
its own comment, three lines above, said "drawing into address zero is a worse
failure than staying quiet". The intent was exactly right and the test could
never enforce it. It reads the physical value first now and tests that.
**vga_fb_init() attached the scanout regardless**, one layer down, so fixing
kmain alone just moved the same write.
Either one had gfx_clear() write four megabytes over physical 0: the real-mode
IVT, the BIOS data area, the AP trampoline at 0x7000, the early page tables.
Hence a reset with no output -- there was nothing left to run.
Both refuse now, and the refusal says what it is:
vga: firmware reported a 1280x800 display with no framebuffer address;
no graphical console
A machine with no usable linear framebuffer gets no graphical console. That is
the truth about it, and the serial line still has everything.
**It still does not reach a shell on such a machine**, and that is recorded
rather than implied: the console path assumes a framebuffer -- vt_init()
reports a terminal of `0 by 0` cells -- and that path has never been exercised.
What this fixes is a machine that destroyed low memory and reset; what it does
not do is make a headless boot work. Supported remains stdvga, with virtio-gpu
optional beside it, which is what docs/architecture/graphics.md already said.
Both supported configurations verified: stdvga alone and stdvga with
virtio-gpu, one boot each, no traps, socktest and memfdtest green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3f2c0bdf05
commit
ff0acfc913
3 changed files with 66 additions and 2 deletions
|
|
@ -59,7 +59,23 @@ kmain(uint32 fb_addr)
|
|||
// the only time anyone sees this is the only time anyone needs it.
|
||||
{
|
||||
struct fb_info bi;
|
||||
bi.ptr = (volatile uchar *)P2V((uint64)*(uint32*)P2V(SCHBOOT_VBE_LFB));
|
||||
// The *physical* address, tested before it is translated.
|
||||
//
|
||||
// This used to be `if (bi.ptr && ...)` with bi.ptr already run through
|
||||
// P2V -- and P2V(0) is KERNBASE, which is not zero, so the guard could
|
||||
// not fail. The comment below was right and the test could never enforce
|
||||
// it.
|
||||
//
|
||||
// It matters on any machine whose only display is a virtio-gpu. OVMF's
|
||||
// virtio GOP implements Blt and nothing else: there is no linear
|
||||
// aperture to report, so it publishes width and height and a
|
||||
// FrameBufferBase of **zero**. The kernel took that at face value, and
|
||||
// gfx_clear() then wrote four megabytes over physical address 0 -- the
|
||||
// real-mode IVT, the BIOS data area, the AP trampoline at 0x7000 and the
|
||||
// early page tables. The machine reset before printing anything and came
|
||||
// back round to the firmware, for ever.
|
||||
uint32 fb_phys = *(uint32*)P2V(SCHBOOT_VBE_LFB);
|
||||
bi.ptr = (volatile uchar *)P2V((uint64)fb_phys);
|
||||
bi.width = *(uint16*)P2V(SCHBOOT_VBE_WIDTH);
|
||||
bi.height = *(uint16*)P2V(SCHBOOT_VBE_HEIGHT);
|
||||
bi.stride = *(uint16*)P2V(SCHBOOT_VBE_STRIDE);
|
||||
|
|
@ -67,7 +83,7 @@ kmain(uint32 fb_addr)
|
|||
// Only if the loader actually described something. A zero here means the
|
||||
// BIOS path or a firmware that gave no GOP, and drawing into address zero
|
||||
// is a worse failure than staying quiet.
|
||||
if (bi.ptr && bi.width && bi.height && bi.stride && bi.bpp >= 16) {
|
||||
if (fb_phys && bi.width && bi.height && bi.stride && bi.bpp >= 16) {
|
||||
gfx_attach_scanout(&bi);
|
||||
// Before vt_init, which reads the cell size to work out how many rows
|
||||
// and columns there are. The font is in the kernel image, so this needs
|
||||
|
|
|
|||
|
|
@ -83,6 +83,23 @@ vga_fb_init(uint64 phys_addr)
|
|||
if (fb_height == 0) fb_height = 600;
|
||||
if (fb_stride == 0) fb_stride = fb_width * (fb_bpp / 8);
|
||||
|
||||
// A framebuffer at physical zero is not a framebuffer.
|
||||
//
|
||||
// The loader publishes what the firmware's GOP reported, and on a machine
|
||||
// whose only display is a virtio-gpu that is a width, a height and a
|
||||
// FrameBufferBase of **zero**: OVMF's virtio GOP implements Blt and nothing
|
||||
// else, because virtio-gpu has no linear aperture to hand out. Attaching it
|
||||
// anyway pointed the scanout at P2V(0) and every subsequent draw wrote over
|
||||
// the real-mode IVT, the BIOS data area and the AP trampoline.
|
||||
//
|
||||
// Refusing leaves the machine with no graphical console, which is the truth
|
||||
// about it. The serial line still has everything.
|
||||
if (phys_addr == 0) {
|
||||
cprintf("vga: firmware reported a %dx%d display with no framebuffer "
|
||||
"address; no graphical console\n", fb_width, fb_height);
|
||||
return;
|
||||
}
|
||||
|
||||
kinfo("vga: %dx%d, %d bpp, stride %d, LFB: 0x%x\n",
|
||||
fb_width, fb_height, fb_bpp, fb_stride, (uint32)phys_addr);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue