boot: a framebuffer address is sixty-four bits, and there is more than one GPU
The first piece of real-hardware work, and it is three bugs that QEMU cannot show you. The report was an ASUS laptop -- Ryzen 7000-series APU, Radeon integrated, RTX discrete -- booting to a white underscore on a black screen, and unable to be offered 1920x1080. **The framebuffer address was truncated.** The loader handed over `(UINT32)gop->Mode->FrameBufferBase` and the kernel read a 32-bit field out of a VBE mode-info block -- a *BIOS* structure the UEFI path had been filling in because the kernel already knew how to read one. A machine that puts its aperture above 4 GB, which is ordinary on a laptop with a discrete GPU, got a plausible-looking address pointing at nothing. The splash went into it, the kernel was handed it, and the firmware's own cursor kept blinking on an otherwise black screen. Every step in that sequence succeeded on a number that was wrong, which is why there is no error message anywhere in it. SCHBOOT_FB_BASE is the loader's own display block: 64-bit address, 64-bit size, 32-bit width, height and stride, and the pixel format. The VBE block is still written for the BIOS loader, but anything that does not fit is left at zero rather than wrapped -- a truncated address is this bug, and a zero is at least honest. The kernel then has a problem the loader does not: its direct map covers the low 4 GB, so a framebuffer above it needs ioremap(), which needs kpdpt, which kvmalloc() has not allocated when the early console starts. A low framebuffer is attached exactly as before; a high one is attached immediately after kvmalloc(), still thousands of lines before anything else. **There was more than one adapter, and the first one was taken.** LocateProtocol answers with *a* GOP and the specification says nothing about which. QEMU with a stdvga and a virtio-gpu has **three** handles and two of them are Blt-only with a framebuffer address of zero -- so this is not a hardware-only hazard, it was a one-in-three chance of drawing into address 0 that happened to keep coming up heads. Every handle is scored now: no aperture, no candidacy; a readable current mode beats one that needs changing; size breaks the tie. **mode_usable() rejected modes real machines have.** A padded scanline (every GPU that aligns its rows -- and very likely the reason 1920x1080 could not be offered), a width not divisible by eight (1366x768, which is half the laptops in the world), and anything below 800x600 (the only mode some firmware gives over a KVM). All three passed on QEMU, which is why they survived. The mode list now offers 1366x768 and 640x480 where it did not. The loader also prints what it found, to the firmware's text console, which is cleared before the splash: invisible on a boot that works, and the only thing on the screen on a boot that dies before the splash. One process note, recorded because it cost a wrong test: `make UEFI_KEEP=1` ships the loader that is already built. Its own comment says "a build that quietly ships a stale bootloader is how you spend an evening debugging code that is not running", and that is exactly what happened -- a regression run that looked clean had tested the old loader. Loader work gets a full build. Both supported configurations verified after a clean build: one boot each, no traps, no panics, socktest and memfdtest green, and the adapter report shows the right one being chosen out of three. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c84d06c5b1
commit
35ea398334
4 changed files with 358 additions and 24 deletions
|
|
@ -112,6 +112,39 @@
|
|||
#define SCHBOOT_FONT_ADDR (SCHBOOT_BASE + 0x608) /* uint64, physical */
|
||||
#define SCHBOOT_FONT_LEN (SCHBOOT_BASE + 0x610) /* uint32, bytes */
|
||||
|
||||
// The display, described by the loader in its own words rather than in VBE's.
|
||||
//
|
||||
// The VBE mode-info block above is a *BIOS* structure, and the UEFI loader has
|
||||
// been filling one in because the kernel already knew how to read it. Three of
|
||||
// its fields cannot describe a real machine:
|
||||
//
|
||||
// - the framebuffer address is **32 bits**. A laptop with a discrete GPU
|
||||
// routinely puts its aperture above 4 GB, and the loader was casting
|
||||
// `(UINT32)gop->Mode->FrameBufferBase` -- so the splash was drawn into a
|
||||
// truncated address, nothing appeared, and the firmware's own text cursor
|
||||
// was left blinking on an otherwise black screen. That is the whole of the
|
||||
// "it boots to a white underscore" report.
|
||||
// - the stride is 16 bits, which runs out at 16383 pixels of 32-bit colour.
|
||||
// - there is nowhere to say whether the pixels are RGBX or BGRX, and GOP
|
||||
// reports both.
|
||||
//
|
||||
// So the UEFI path says it properly. The VBE block is still written, for the
|
||||
// BIOS loader and for any kernel that has not been taught to look here; a
|
||||
// non-zero FB_BASE means this block is authoritative and the other is a copy.
|
||||
//
|
||||
// FB_BASE is *physical*, and deliberately not translated: above 4 GB it is
|
||||
// outside the kernel's direct map and has to be ioremap()ed, which cannot
|
||||
// happen until there are page tables to put it in.
|
||||
#define SCHBOOT_FB_BASE (SCHBOOT_BASE + 0x618) /* uint64, physical */
|
||||
#define SCHBOOT_FB_SIZE (SCHBOOT_BASE + 0x620) /* uint64, bytes */
|
||||
#define SCHBOOT_FB_WIDTH (SCHBOOT_BASE + 0x628) /* uint32, pixels */
|
||||
#define SCHBOOT_FB_HEIGHT (SCHBOOT_BASE + 0x62C) /* uint32, pixels */
|
||||
#define SCHBOOT_FB_STRIDE (SCHBOOT_BASE + 0x630) /* uint32, bytes */
|
||||
// 0 = RGBX8888, 1 = BGRX8888 -- the two GOP formats with a linear layout, in
|
||||
// EFI's own numbering. Everything in the kernel draws 0x00RRGGBB, which is
|
||||
// BGRX in memory, so format 0 is the one that needs its channels swapped.
|
||||
#define SCHBOOT_FB_FORMAT (SCHBOOT_BASE + 0x634) /* uint32 */
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
|
||||
struct sch64_header {
|
||||
|
|
|
|||
Loading…
Reference in a new issue