display: a 64-bit framebuffer address, no mirroring onto itself

Three fixes for real machines that broke at any resolution but 800x600:

- kmain() took the loader's framebuffer address as uint32, and
  vga_fb_init() re-published the scanout from that and the 16-bit VBE
  block. An aperture above 4 GB moved the console onto the wrong memory
  right after the vga: line. Both now use the loader's 64-bit display
  block, mapped with ioremap().
- The loader mirrored onto the console splitter's GOP, which forwards to
  the primary itself, and onto GOPs that share the primary's aperture.
  After a mode change that rewrote the screen over itself with another
  mode's stride. Handles without a device path and overlapping apertures
  are skipped, and a mode change re-matches the remaining mirrors.
- A mode whose reported size, stride or aperture does not add up after
  SetMode is refused, and the loader returns to the previous one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-23 15:46:42 +02:00
commit fe32833ade
5 changed files with 193 additions and 16 deletions

View file

@ -88,6 +88,8 @@ static EFI_BOOT_SERVICES *BS;
// ---------------------------------------------------------------------------
static void mirrors_show(void); /* see "the other displays", further down */
static void mirrors_match(EFI_GRAPHICS_OUTPUT_PROTOCOL *primary);
static void describe_gop(EFI_GRAPHICS_OUTPUT_PROTOCOL *g, const CHAR16 *tag);
/* Once the splash is up, nothing may write to the console.
*
@ -793,6 +795,64 @@ res_read_number(const char *prompt)
/* Set a mode, and keep it only if the firmware's own account of it adds up.
*
* A GOP can accept a mode and then describe it in a way the display does not
* scan out: a row length that is not the one being shown, or an aperture too
* small for the rows it claims. Drawn into anyway, that is the picture sheared
* into dotted lines, or a logo repeated across the screen, both of which a
* desktop with an NVIDIA card showed at sizes other than 800x600. The numbers
* cannot prove a mode is right, but they can prove one is wrong, and a wrong
* one is refused here, in the menu, where there is still a screen to say so on.
*/
static int
mode_set_checked(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, UINT32 mode)
{
UINT32 was = gop->Mode->Mode;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *want = 0;
UINTN sz = 0;
if (EFI_ERROR(gop->QueryMode(gop, mode, &sz, &want)) || !want)
return 0;
if (EFI_ERROR(gop->SetMode(gop, mode)))
return 0;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *got = gop->Mode->Info;
UINT64 need = (UINT64)got->PixelsPerScanLine * 4 * got->VerticalResolution;
if (got->HorizontalResolution == want->HorizontalResolution &&
got->VerticalResolution == want->VerticalResolution &&
got->PixelsPerScanLine >= got->HorizontalResolution &&
gop->Mode->FrameBufferBase &&
gop->Mode->FrameBufferSize >= need)
return 1;
describe_gop(gop, L"schos: mode does not add up, going back:");
gop->SetMode(gop, was);
return 0;
}
/* Said on the screen that is still working, then back to the menu. */
static void
res_refused(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
{
struct ui_fb uifb;
EFI_INPUT_KEY key;
const char *msg[1] = { "The display refused that size -- keeping this one" };
uifb.base = (ui_u8 *)(UINTN)gop->Mode->FrameBufferBase;
uifb.width = gop->Mode->Info->HorizontalResolution;
uifb.height = gop->Mode->Info->VerticalResolution;
uifb.stride = gop->Mode->Info->PixelsPerScanLine * 4;
ui_init(&uifb);
ui_menu("Resolution", msg, 1, 0, "any key to go back");
mirrors_show();
for (;;) {
if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
break;
BS->Stall(10000);
}
}
static void
res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
{
@ -850,7 +910,13 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
}
if (c == L'\r' || c == L'\n') {
if (sel < RES_N) {
gop->SetMode(gop, RES_MODE[sel]);
if (!mode_set_checked(gop, RES_MODE[sel])) {
res_refused(gop);
return;
}
// The one other time the mirrors' modes are worth touching:
// the person just said what size the display is.
mirrors_match(gop);
return;
}
/* Typed. Matched against the same filtered list, so a size that
@ -865,7 +931,11 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
continue;
if (mi->HorizontalResolution == w &&
mi->VerticalResolution == h) {
gop->SetMode(gop, i);
if (!mode_set_checked(gop, i)) {
res_refused(gop);
return;
}
mirrors_match(gop);
return;
}
}
@ -1236,6 +1306,7 @@ static EFI_GRAPHICS_OUTPUT_PROTOCOL *primary_gop;
static UINTN mirror_n;
static void mirrors_match(EFI_GRAPHICS_OUTPUT_PROTOCOL *primary);
static void describe_gop(EFI_GRAPHICS_OUTPUT_PROTOCOL *g, const CHAR16 *tag);
static void
mirrors_find(EFI_GRAPHICS_OUTPUT_PROTOCOL *primary)
@ -1269,6 +1340,32 @@ mirrors_find(EFI_GRAPHICS_OUTPUT_PROTOCOL *primary)
continue;
if (!g || g == primary || !g->Blt || !g->Mode || !g->Mode->Info)
continue;
// Not a second display: the console splitter. Firmware installs one
// GOP of its own on a virtual handle and forwards every Blt to all of
// the real ones -- the primary included. Mirroring onto it copied the
// primary onto itself through the splitter's idea of the mode, which
// stops being the primary's the moment the resolution menu changes
// it: the picture landed again, offset and with the wrong row length,
// over the one already there. That was the doubled, sheared screen with
// the logo's edges repeated down it, at every size but the one the
// firmware booted in. A real adapter's GOP is on a handle with a path
// to the device; the splitter's has none.
void *dp = 0;
EFI_GUID dp_guid = EFI_DEVICE_PATH_PROTOCOL_GUID;
if (EFI_ERROR(BS->HandleProtocol(handles[i], &dp_guid, &dp)) || !dp)
continue;
// Nor is a second GOP whose aperture is the primary's: one panel
// reached twice, which laptops do. Blitting onto it rewrites the
// primary's own memory in another mode's layout.
UINT64 pb = primary->Mode->FrameBufferBase;
UINT64 pe = pb + primary->Mode->FrameBufferSize;
UINT64 gb = g->Mode->FrameBufferBase;
UINT64 ge = gb + g->Mode->FrameBufferSize;
if (gb && pb && gb < pe && pb < ge)
continue;
mirror_gop[mirror_n++] = g;
}
@ -1292,8 +1389,9 @@ mirrors_find(EFI_GRAPHICS_OUTPUT_PROTOCOL *primary)
static void
mirrors_match(EFI_GRAPHICS_OUTPUT_PROTOCOL *primary)
{
// Called exactly once, from mirrors_find(), before anything has been
// drawn. It used to be called from mirrors_show() -- which is to say on
// Called from mirrors_find(), before anything has been drawn, and again
// when the resolution menu sets a mode -- never per frame. It used to be
// called from mirrors_show() -- which is to say on
// every menu redraw and on every segment of the kernel load -- and that is
// not a safe thing to do to another display adapter.
//