boot/uefi: choose a resolution with R, the same as the BIOS path
GOP instead of VBE, and the same reasoning. There are no mode numbers worth hardcoding here: the firmware publishes however many modes the hardware can do and which index means what varies by machine, so the menu is built by walking them. Press R within four seconds for a list, then either a mode number or Enter to type a size. 800x600 stays the floor. Fixes a header bug this is the first code to notice. The spec's order in EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE is MaxMode then Mode; efi.h had them swapped. Info, SizeOfInfo and FrameBufferBase sit at the same offsets either way because both fields are UINT32, so the framebuffer worked perfectly and nothing was wrong until something asked how many modes existed -- and was told the current mode number instead. Zero, on a machine reporting thirty. QueryMode and SetMode were void* padding and now have real prototypes; ConIn likewise, so the loader can read a key. Under OVMF the console includes the serial line, which is where `make qemu-uefi` puts it and where the person actually is -- the same mistake the BIOS menu made first time round. Verified over serial against OVMF: 30 modes enumerated; picking listed mode 14 gives 1280x1024; typing 1024x768 gives 1024x768; no key keeps the firmware's mode and boots. All reach schinit. bootcheck 108 [ok]. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
03a867f43e
commit
0362427e80
2 changed files with 210 additions and 4 deletions
166
boot/uefi/main.c
166
boot/uefi/main.c
|
|
@ -252,6 +252,166 @@ load_elf(EFI_FILE_PROTOCOL *file)
|
|||
// Framebuffer
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
// Offer a resolution choice, briefly. The UEFI half of the same feature the
|
||||
// BIOS loader has -- see boot/bios/stage2.S.
|
||||
//
|
||||
// Different mechanism, same reasoning. GOP has no mode numbers worth
|
||||
// hardcoding: the firmware publishes however many modes the hardware can do,
|
||||
// and which index means what varies by machine. So this walks them, and the
|
||||
// menu is built from what is actually there rather than from a table.
|
||||
//
|
||||
// Four seconds, then it boots on its own. A loader that waits for a keypress
|
||||
// is a machine that does not come back up after a power cut.
|
||||
|
||||
static void
|
||||
print_dec(UINT32 v)
|
||||
{
|
||||
CHAR16 buf[12];
|
||||
int i = 11;
|
||||
buf[i--] = 0;
|
||||
if (v == 0)
|
||||
buf[i--] = L'0';
|
||||
while (v && i >= 0) {
|
||||
buf[i--] = (CHAR16)(L'0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
print(&buf[i + 1]);
|
||||
}
|
||||
|
||||
// A key, or 0 if nothing was typed within `ms` milliseconds. Stall is the
|
||||
// simplest clock available before boot services go away, and polling in 10 ms
|
||||
// steps is far below anything a person can notice.
|
||||
static CHAR16
|
||||
wait_key(UINTN ms)
|
||||
{
|
||||
EFI_INPUT_KEY key;
|
||||
for (UINTN waited = 0; waited < ms; waited += 10) {
|
||||
if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
return key.UnicodeChar;
|
||||
BS->Stall(10000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static CHAR16
|
||||
wait_key_forever(void)
|
||||
{
|
||||
EFI_INPUT_KEY key;
|
||||
for (;;) {
|
||||
if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
return key.UnicodeChar;
|
||||
BS->Stall(10000);
|
||||
}
|
||||
}
|
||||
|
||||
// Read a decimal number, echoing it. Ends on Enter (CR, or LF over a serial
|
||||
// console). Non-digits are ignored rather than aborting, so a stray keystroke
|
||||
// does not silently truncate the number.
|
||||
static UINT32
|
||||
read_dec(void)
|
||||
{
|
||||
UINT32 v = 0;
|
||||
for (;;) {
|
||||
CHAR16 c = wait_key_forever();
|
||||
if (c == L'\r' || c == L'\n')
|
||||
return v;
|
||||
if (c < L'0' || c > L'9')
|
||||
continue;
|
||||
CHAR16 echo[2]; echo[0] = c; echo[1] = 0;
|
||||
print(echo);
|
||||
v = v * 10 + (UINT32)(c - L'0');
|
||||
}
|
||||
}
|
||||
|
||||
// Pick a GOP mode. Returns the mode number to set, or the current one.
|
||||
static UINT32
|
||||
choose_mode(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
||||
{
|
||||
UINT32 current = gop->Mode->Mode;
|
||||
|
||||
print(L"Press R within 4s to choose a resolution... ");
|
||||
CHAR16 c = wait_key(4000);
|
||||
if (c != L'r' && c != L'R') {
|
||||
print(L"keeping ");
|
||||
print_dec(gop->Mode->Info->HorizontalResolution); print(L"x");
|
||||
print_dec(gop->Mode->Info->VerticalResolution); print(L"\r\n");
|
||||
return current;
|
||||
}
|
||||
|
||||
// List what the firmware actually offers, 32bpp only: the console, the
|
||||
// fbdev ioctls and Schcom all assume it, and PixelFormat 2 is a packed
|
||||
// format this loader does not describe to the kernel.
|
||||
print(L"\r\n");
|
||||
for (UINT32 i = 0; i < gop->Mode->MaxMode; i++) {
|
||||
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *dbg = 0;
|
||||
UINTN dsz = 0;
|
||||
EFI_STATUS dst = gop->QueryMode(gop, i, &dsz, &dbg);
|
||||
print(L" raw "); print_dec(i); print(L" st="); print_hex(dst);
|
||||
if (!EFI_ERROR(dst) && dbg) {
|
||||
print(L" "); print_dec(dbg->HorizontalResolution);
|
||||
print(L"x"); print_dec(dbg->VerticalResolution);
|
||||
print(L" fmt="); print_dec(dbg->PixelFormat);
|
||||
}
|
||||
print(L"\r\n");
|
||||
}
|
||||
for (UINT32 i = 0; i < gop->Mode->MaxMode; i++) {
|
||||
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info = 0;
|
||||
UINTN size = 0;
|
||||
if (EFI_ERROR(gop->QueryMode(gop, i, &size, &info)) || !info)
|
||||
continue;
|
||||
if (info->PixelFormat > 1)
|
||||
continue;
|
||||
if (info->HorizontalResolution < 800 || info->VerticalResolution < 600)
|
||||
continue;
|
||||
print(L" ");
|
||||
print_dec(i); print(L") ");
|
||||
print_dec(info->HorizontalResolution); print(L"x");
|
||||
print_dec(info->VerticalResolution); print(L"\r\n");
|
||||
}
|
||||
print(L" mode number, or Enter to type a size: ");
|
||||
|
||||
UINT32 want = read_dec();
|
||||
print(L"\r\n");
|
||||
|
||||
// Entering nothing means "let me type it instead". Zero is a valid mode
|
||||
// number, so the two are told apart by whether anything was typed at all;
|
||||
// read_dec returns 0 for an empty line, and mode 0 is reachable from the
|
||||
// typed path anyway.
|
||||
if (want != 0 || gop->Mode->MaxMode == 0) {
|
||||
if (want < gop->Mode->MaxMode)
|
||||
return want;
|
||||
print(L"schos: no such mode, keeping the current one\r\n");
|
||||
return current;
|
||||
}
|
||||
|
||||
print(L" width: ");
|
||||
UINT32 w = read_dec();
|
||||
print(L"\r\n height: ");
|
||||
UINT32 h = read_dec();
|
||||
print(L"\r\n");
|
||||
|
||||
// 800x600 is the floor the system is built around.
|
||||
if (w < 800 || h < 600) {
|
||||
print(L"schos: below the 800x600 minimum, keeping the current mode\r\n");
|
||||
return current;
|
||||
}
|
||||
|
||||
for (UINT32 i = 0; i < gop->Mode->MaxMode; i++) {
|
||||
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info = 0;
|
||||
UINTN size = 0;
|
||||
if (EFI_ERROR(gop->QueryMode(gop, i, &size, &info)) || !info)
|
||||
continue;
|
||||
if (info->PixelFormat > 1)
|
||||
continue;
|
||||
if (info->HorizontalResolution == w && info->VerticalResolution == h)
|
||||
return i;
|
||||
}
|
||||
|
||||
print(L"schos: no such 32bpp mode, keeping the current one\r\n");
|
||||
return current;
|
||||
}
|
||||
|
||||
static UINT32
|
||||
setup_framebuffer(void)
|
||||
{
|
||||
|
|
@ -263,6 +423,12 @@ setup_framebuffer(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
UINT32 pick = choose_mode(gop);
|
||||
if (pick != gop->Mode->Mode)
|
||||
gop->SetMode(gop, pick);
|
||||
|
||||
// Re-read after SetMode: Info and FrameBufferBase both describe the mode
|
||||
// that is current, and the framebuffer can move when the mode changes.
|
||||
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info = gop->Mode->Info;
|
||||
UINT32 fb = (UINT32)gop->Mode->FrameBufferBase;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue