Boot manager: Niveus window, keys and wells, Instrument Sans, pointer, other systems, working power entries
The F8 menu is redrawn in the Niveus language: a window with three lights, bevelled keys with a lit edge, wells for panels, anti-aliased corners and a soft shadow, set in Instrument Sans rasterised into include/gfx/nvfont.h (tools/python/mknvfont.py, make nvfont). Scale doubles on screens 1400 px tall or more. Lists taller than the screen scroll. The keyboard still drives everything, and a mouse, touchpad or tablet does too: both UEFI pointer protocols are polled, hover moves the highlight, a click activates. The cursor stays hidden until the pointer has really done something, since the firmware installs a pointer protocol with or without a mouse. The 'keep this resolution?' countdown is one ten-second budget however many times the pointer moves. Loaders found at well-known paths on any volume (Windows, Ubuntu, Debian, Fedora, Arch, openSUSE, systemd-boot, rEFInd, other BOOTX64.EFI) are listed and chain-loaded with LoadImage/StartImage. 'Reboot to firmware' used to halt; it now sets OsIndications and restarts, and Restart and Shut Down call ResetSystem. Checked under QEMU+OVMF: menu, resolution list (incl. 2560x1600 at double scale), system information, shut down, firmware setup, chain-loading a copy of the loader from a second disk, and a full boot of the ISO to the login prompt. Pointer hover and click were checked only with synthetic events: in this setup a USB mouse or tablet delivered no event to the loader (one USB I/O handle appeared but no pointer protocol came from it), and why was not established. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
parent
020aa6113c
commit
dee3affa3b
11 changed files with 9304 additions and 208 deletions
659
boot/uefi/main.c
659
boot/uefi/main.c
|
|
@ -81,6 +81,8 @@ struct elf64_phdr {
|
|||
#define PT_LOAD 1
|
||||
|
||||
static EFI_SYSTEM_TABLE *ST;
|
||||
static EFI_HANDLE SELF; /* this image */
|
||||
static EFI_HANDLE BOOT_VOL; /* the volume it was loaded from */
|
||||
static EFI_BOOT_SERVICES *BS;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -183,7 +185,20 @@ serial_item(const char *const *items, int i, int sel, int numbered)
|
|||
} else if (numbered) {
|
||||
serial_puts(" "); /* keep the column */
|
||||
}
|
||||
serial_puts(items[i]);
|
||||
/* The label, and its detail in brackets. Items carry both, with a newline
|
||||
* between and a leading \001 where a group begins: the screen uses those
|
||||
* for layout and a serial line has no use for either. */
|
||||
const char *t = items[i];
|
||||
if (t[0] == 1)
|
||||
t++;
|
||||
for (; *t && *t != '\n'; t++)
|
||||
serial_putc(*t);
|
||||
if (*t == '\n') {
|
||||
serial_puts(" (");
|
||||
for (t++; *t; t++)
|
||||
serial_putc(*t);
|
||||
serial_putc(')');
|
||||
}
|
||||
serial_puts("\n");
|
||||
}
|
||||
|
||||
|
|
@ -360,6 +375,7 @@ open_boot_volume(EFI_HANDLE image)
|
|||
// is not a thing to do by accident.
|
||||
st = BS->HandleProtocol(image, &li_guid, (void **)&li);
|
||||
if (EFI_ERROR(st)) die(L"cannot get LOADED_IMAGE", st);
|
||||
BOOT_VOL = li->DeviceHandle;
|
||||
self_base = (UINT64)(UINTN)li->ImageBase;
|
||||
self_size = li->ImageSize;
|
||||
|
||||
|
|
@ -904,6 +920,231 @@ u64_to_dec(UINT64 v, char *out, int cap)
|
|||
out[i] = 0;
|
||||
}
|
||||
|
||||
/* --- pointers, and one place to wait for input ------------------------------
|
||||
*
|
||||
* The boot manager is operated by keyboard, mouse, touchpad or touchscreen, and
|
||||
* every screen waits through here so that all of them behave the same way.
|
||||
*
|
||||
* Both pointer protocols are polled rather than waited on: the loop is already
|
||||
* polling the keyboard, and an event per device would mean a wait-for-any call
|
||||
* that most firmware implements badly. A machine with no pointer simply never
|
||||
* shows a cursor.
|
||||
*/
|
||||
#define MAX_PTR 4
|
||||
static EFI_SIMPLE_POINTER_PROTOCOL *PTR_REL[MAX_PTR];
|
||||
static EFI_ABSOLUTE_POINTER_PROTOCOL *PTR_ABS[MAX_PTR];
|
||||
static int PTR_NREL, PTR_NABS;
|
||||
static int PTR_X, PTR_Y, PTR_DOWN;
|
||||
static int PTR_RESCANS, PTR_TICKS;
|
||||
static int PTR_ACTIVE; /* a real event has arrived; until then there is no cursor */
|
||||
static void u64_to_dec(UINT64 v, char *out, int cap);
|
||||
|
||||
#define EV_NONE 0
|
||||
#define EV_KEY 1
|
||||
#define EV_HOVER 2 /* the pointer moved onto a key: *item */
|
||||
#define EV_CLICK 3 /* a key was clicked: *item */
|
||||
|
||||
#define PE_MOVED 1
|
||||
#define PE_DOWN 2
|
||||
#define PE_UP 4 /* wheel */
|
||||
#define PE_DOWNW 8
|
||||
|
||||
/* Bind every driver to every controller.
|
||||
*
|
||||
* The firmware connects only what it needs to boot: the devices that carry a
|
||||
* console, and the disks. A USB mouse or tablet is neither, so on most
|
||||
* machines its driver has not been started and no pointer protocol exists yet.
|
||||
* Doing it here, when the menu opens, costs a second or so on a real machine
|
||||
* and nothing on a boot that never shows a menu. */
|
||||
static void
|
||||
connect_all(void)
|
||||
{
|
||||
EFI_HANDLE *h = 0;
|
||||
UINTN n = 0;
|
||||
|
||||
if (EFI_ERROR(BS->LocateHandleBuffer(0 /* AllHandles */, 0, 0, &n, &h)) || !h)
|
||||
return;
|
||||
for (UINTN i = 0; i < n; i++)
|
||||
BS->ConnectController(h[i], 0, 0, 1);
|
||||
BS->FreePool(h);
|
||||
}
|
||||
|
||||
static void
|
||||
pointers_find(void)
|
||||
{
|
||||
EFI_GUID g1 = EFI_SIMPLE_POINTER_PROTOCOL_GUID;
|
||||
EFI_GUID g2 = EFI_ABSOLUTE_POINTER_PROTOCOL_GUID;
|
||||
EFI_HANDLE *h = 0;
|
||||
UINTN n = 0;
|
||||
|
||||
if (!EFI_ERROR(BS->LocateHandleBuffer(ByProtocol, &g1, 0, &n, &h)) && h) {
|
||||
for (UINTN i = 0; i < n && PTR_NREL < MAX_PTR; i++) {
|
||||
void *p = 0;
|
||||
if (!EFI_ERROR(BS->HandleProtocol(h[i], &g1, &p)) && p) {
|
||||
EFI_SIMPLE_POINTER_PROTOCOL *sp = p;
|
||||
int seen = 0;
|
||||
for (int k = 0; k < PTR_NREL; k++)
|
||||
if (PTR_REL[k] == sp) seen = 1;
|
||||
if (!seen) {
|
||||
sp->Reset(sp, 0);
|
||||
PTR_REL[PTR_NREL++] = sp;
|
||||
}
|
||||
}
|
||||
}
|
||||
BS->FreePool(h);
|
||||
}
|
||||
h = 0; n = 0;
|
||||
if (!EFI_ERROR(BS->LocateHandleBuffer(ByProtocol, &g2, 0, &n, &h)) && h) {
|
||||
for (UINTN i = 0; i < n && PTR_NABS < MAX_PTR; i++) {
|
||||
void *p = 0;
|
||||
if (!EFI_ERROR(BS->HandleProtocol(h[i], &g2, &p)) && p) {
|
||||
EFI_ABSOLUTE_POINTER_PROTOCOL *ap = p;
|
||||
int seen = 0;
|
||||
for (int k = 0; k < PTR_NABS; k++)
|
||||
if (PTR_ABS[k] == ap) seen = 1;
|
||||
if (!seen) {
|
||||
ap->Reset(ap, 0);
|
||||
PTR_ABS[PTR_NABS++] = ap;
|
||||
}
|
||||
}
|
||||
}
|
||||
BS->FreePool(h);
|
||||
}
|
||||
if (PTR_X == 0 && PTR_Y == 0)
|
||||
PTR_X = PTR_Y = -1; /* centred on first use, when the size is known */
|
||||
}
|
||||
|
||||
static int
|
||||
pointer_poll(void)
|
||||
{
|
||||
int ev = 0, w = (int)ui_width(), h = (int)ui_height();
|
||||
int down = PTR_DOWN;
|
||||
|
||||
for (int i = 0; i < PTR_NREL; i++) {
|
||||
EFI_SIMPLE_POINTER_STATE st;
|
||||
if (EFI_ERROR(PTR_REL[i]->GetState(PTR_REL[i], &st)))
|
||||
continue;
|
||||
int dx = st.RelativeMovementX, dy = st.RelativeMovementY;
|
||||
/* A little acceleration: the counts are the device's, not pixels, and
|
||||
* a fast flick has to be able to cross a 4K panel. */
|
||||
if (dx > 6 || dx < -6) dx *= 2;
|
||||
if (dy > 6 || dy < -6) dy *= 2;
|
||||
if (dx || dy) {
|
||||
PTR_X += dx; PTR_Y += dy;
|
||||
ev |= PE_MOVED;
|
||||
}
|
||||
if (st.RelativeMovementZ)
|
||||
ev |= st.RelativeMovementZ < 0 ? PE_UP : PE_DOWNW;
|
||||
down = st.LeftButton;
|
||||
}
|
||||
for (int i = 0; i < PTR_NABS; i++) {
|
||||
EFI_ABSOLUTE_POINTER_STATE st;
|
||||
EFI_ABSOLUTE_POINTER_MODE *m = PTR_ABS[i]->Mode;
|
||||
if (EFI_ERROR(PTR_ABS[i]->GetState(PTR_ABS[i], &st)) || !m)
|
||||
continue;
|
||||
UINT64 rx = m->AbsoluteMaxX - m->AbsoluteMinX;
|
||||
UINT64 ry = m->AbsoluteMaxY - m->AbsoluteMinY;
|
||||
if (rx && ry) {
|
||||
PTR_X = (int)(((st.CurrentX - m->AbsoluteMinX) * (UINT64)(w - 1)) / rx);
|
||||
PTR_Y = (int)(((st.CurrentY - m->AbsoluteMinY) * (UINT64)(h - 1)) / ry);
|
||||
ev |= PE_MOVED;
|
||||
}
|
||||
down = (int)(st.ActiveButtons & 1);
|
||||
}
|
||||
|
||||
if (PTR_X < 0) PTR_X = 0;
|
||||
if (PTR_Y < 0) PTR_Y = 0;
|
||||
if (PTR_X >= w) PTR_X = w - 1;
|
||||
if (PTR_Y >= h) PTR_Y = h - 1;
|
||||
if (down && !PTR_DOWN)
|
||||
ev |= PE_DOWN;
|
||||
PTR_DOWN = down;
|
||||
return ev;
|
||||
}
|
||||
|
||||
/* Wait for a key, a click on a key, or the pointer moving onto one.
|
||||
*
|
||||
* `budget` is a countdown in milliseconds that survives across calls: a
|
||||
* caller that returns here after a hover continues the same ten seconds
|
||||
* instead of being given a fresh ten. NULL waits for ever.
|
||||
*
|
||||
* The cursor stays hidden until the pointer has actually done something. The
|
||||
* firmware's console layer installs a pointer protocol whether or not a mouse
|
||||
* is attached, so "a protocol exists" proves nothing -- and a cursor parked in
|
||||
* the middle of the menu of a laptop with no mouse is worse than none. */
|
||||
static int
|
||||
wait_event(EFI_INPUT_KEY *key, int *item, UINTN *budget)
|
||||
{
|
||||
int have = PTR_NREL + PTR_NABS;
|
||||
int last = -2;
|
||||
|
||||
if (PTR_X < 0) {
|
||||
PTR_X = (int)ui_width() / 2;
|
||||
PTR_Y = (int)ui_height() / 2;
|
||||
}
|
||||
ui_cursor(PTR_X, PTR_Y, PTR_ACTIVE);
|
||||
for (;;) {
|
||||
if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, key)))
|
||||
return EV_KEY;
|
||||
/* USB devices appear some time after their controller is connected,
|
||||
* and the menu is already on screen by then: look again, twice a
|
||||
* second for the first ten. */
|
||||
if (PTR_RESCANS < 20 && ++PTR_TICKS >= 250) {
|
||||
PTR_TICKS = 0;
|
||||
PTR_RESCANS++;
|
||||
pointers_find();
|
||||
have = PTR_NREL + PTR_NABS;
|
||||
}
|
||||
if (have) {
|
||||
int e = pointer_poll();
|
||||
if ((e & (PE_MOVED | PE_DOWN)) && !PTR_ACTIVE)
|
||||
PTR_ACTIVE = 1;
|
||||
if (e & PE_MOVED || ((e & PE_DOWN) && PTR_ACTIVE))
|
||||
ui_cursor(PTR_X, PTR_Y, PTR_ACTIVE);
|
||||
if (e & (PE_UP | PE_DOWNW)) {
|
||||
key->UnicodeChar = 0;
|
||||
key->ScanCode = (e & PE_UP) ? 0x01 : 0x02;
|
||||
return EV_KEY;
|
||||
}
|
||||
int hit = ui_menu_hit(PTR_X, PTR_Y);
|
||||
if ((e & PE_DOWN) && hit >= 0) {
|
||||
*item = hit;
|
||||
return EV_CLICK;
|
||||
}
|
||||
if ((e & PE_MOVED) && hit >= 0 && hit != last) {
|
||||
last = hit;
|
||||
*item = hit;
|
||||
return EV_HOVER;
|
||||
}
|
||||
if (hit < 0)
|
||||
last = -2;
|
||||
}
|
||||
if (budget) {
|
||||
if (*budget < 2)
|
||||
return EV_NONE;
|
||||
*budget -= 2;
|
||||
}
|
||||
BS->Stall(2000);
|
||||
}
|
||||
}
|
||||
|
||||
/* A message and a button. Any key or a click dismisses it. */
|
||||
static void
|
||||
notice(const char *title, const char *const *lines, int n, const char *button,
|
||||
const char *hint)
|
||||
{
|
||||
EFI_INPUT_KEY key;
|
||||
int item = 0;
|
||||
|
||||
ui_notice(title, lines, n, button, hint);
|
||||
mirrors_show();
|
||||
for (;;) {
|
||||
int e = wait_event(&key, &item, 0);
|
||||
if (e == EV_KEY || e == EV_CLICK)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
res_row(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, char *out, int cap)
|
||||
{
|
||||
|
|
@ -916,6 +1157,8 @@ res_row(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, char *out, int cap)
|
|||
for (char *p = wa; *p && i < cap - 1; p++) out[i++] = *p;
|
||||
if (i < cap - 1) out[i++] = 'x';
|
||||
for (char *p = ha; *p && i < cap - 1; p++) out[i++] = *p;
|
||||
for (const char *p = "\nChange the screen size"; *p && i < cap - 1; p++)
|
||||
out[i++] = *p;
|
||||
out[i] = 0;
|
||||
}
|
||||
|
||||
|
|
@ -1038,16 +1281,16 @@ res_read_number(const char *prompt)
|
|||
shown[i] = 0;
|
||||
|
||||
const char *rows[1] = { shown };
|
||||
ui_menu("Custom resolution", rows, 1, 0, "digits, Enter to accept");
|
||||
ui_notice("Custom resolution", rows, 1, "Accept", "digits, Enter to accept");
|
||||
mirrors_show();
|
||||
|
||||
EFI_INPUT_KEY key;
|
||||
for (;;) {
|
||||
if (ST->ConIn &&
|
||||
!EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
break;
|
||||
BS->Stall(10000);
|
||||
}
|
||||
int item = 0, e;
|
||||
do
|
||||
e = wait_event(&key, &item, 0);
|
||||
while (e == EV_HOVER);
|
||||
if (e == EV_CLICK)
|
||||
return v;
|
||||
if (key.UnicodeChar == L'\r' || key.UnicodeChar == L'\n')
|
||||
return v;
|
||||
if (key.UnicodeChar >= L'0' && key.UnicodeChar <= L'9')
|
||||
|
|
@ -1091,15 +1334,21 @@ mode_confirmed(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
uifb.height = gop->Mode->Info->VerticalResolution;
|
||||
uifb.stride = gop->Mode->Info->PixelsPerScanLine * 4;
|
||||
ui_init(&uifb);
|
||||
ui_menu("Resolution", msg, 2, 0, "going back in 10 seconds");
|
||||
ui_notice("Resolution", msg, 2, "Keep", "going back in 10 seconds");
|
||||
|
||||
if (ST->ConIn)
|
||||
while (!EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
; /* the key that chose the mode is not an answer */
|
||||
for (UINTN waited = 0; waited < 10000; waited += 10) {
|
||||
if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
UINTN left = 10000; /* one countdown, however many hovers */
|
||||
for (;;) {
|
||||
int item = 0;
|
||||
int e = wait_event(&key, &item, &left);
|
||||
if (e == EV_NONE)
|
||||
break;
|
||||
if (e == EV_CLICK)
|
||||
return 1;
|
||||
if (e == EV_KEY)
|
||||
return key.UnicodeChar == L'\r' || key.UnicodeChar == L'\n';
|
||||
BS->Stall(10000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1136,7 +1385,6 @@ static void
|
|||
res_refused(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
||||
{
|
||||
struct ui_fb uifb;
|
||||
EFI_INPUT_KEY key;
|
||||
const char *msg[1] = { "Not kept -- back to the size before" };
|
||||
|
||||
uifb.base = (ui_u8 *)(UINTN)gop->Mode->FrameBufferBase;
|
||||
|
|
@ -1144,13 +1392,7 @@ res_refused(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
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);
|
||||
}
|
||||
notice("Resolution", msg, 1, "OK", "any key to go back");
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1158,6 +1400,8 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
{
|
||||
int sel = 0;
|
||||
int echoed = -1;
|
||||
static const char hint[] =
|
||||
"W/S or arrows to choose, Enter to apply, Esc or Q to go back";
|
||||
|
||||
for (;;) {
|
||||
struct ui_fb uifb;
|
||||
|
|
@ -1174,41 +1418,53 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
items[i] = RES_TEXT[i];
|
||||
|
||||
if (sel > RES_N) sel = RES_N;
|
||||
ui_menu("Resolution", items, RES_N + 1, sel,
|
||||
"W/S to choose, Enter to apply, Esc or Q to go back");
|
||||
ui_menu("Resolution", items, RES_N + 1, sel, hint);
|
||||
|
||||
if (echoed < 0) {
|
||||
serial_menu("Resolution", items, RES_N + 1, sel,
|
||||
"W/S to choose, Enter to apply, Esc or Q to go back", 0);
|
||||
} else if (echoed != sel) {
|
||||
serial_item(items, sel, sel, 0);
|
||||
}
|
||||
if (echoed < 0)
|
||||
serial_menu("Resolution", items, RES_N + 1, sel, hint, 0);
|
||||
echoed = sel;
|
||||
mirrors_show();
|
||||
|
||||
EFI_INPUT_KEY key;
|
||||
for (;;) {
|
||||
if (ST->ConIn &&
|
||||
!EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
break;
|
||||
BS->Stall(10000);
|
||||
}
|
||||
EFI_INPUT_KEY key;
|
||||
int item = -1, act = 0;
|
||||
int e = wait_event(&key, &item, 0);
|
||||
|
||||
if (e == EV_HOVER || e == EV_CLICK) {
|
||||
if (item != sel) {
|
||||
sel = item;
|
||||
ui_menu_select(sel);
|
||||
serial_item(items, sel, sel, 0);
|
||||
mirrors_show();
|
||||
}
|
||||
act = e == EV_CLICK;
|
||||
} else {
|
||||
CHAR16 c = key.UnicodeChar;
|
||||
/* Q as well as Esc, for the same reason f8_window() accepts
|
||||
* 'o' as well as F8: on a serial line a lone ESC byte is
|
||||
* indistinguishable from the start of an escape sequence, so
|
||||
* the terminal driver holds it back waiting for the rest and
|
||||
* it never arrives as a key. Esc is the obvious thing to
|
||||
* press on a screen and unreachable on a cable. */
|
||||
if (key.ScanCode == 0x17 || c == 27 || c == L'q' || c == L'Q')
|
||||
return;
|
||||
int to = sel;
|
||||
if (key.ScanCode == 0x01 || c == L'w' || c == L'W')
|
||||
to = (sel + RES_N) % (RES_N + 1);
|
||||
else if (key.ScanCode == 0x02 || c == L's' || c == L'S')
|
||||
to = (sel + 1) % (RES_N + 1);
|
||||
else if (c == L'\r' || c == L'\n')
|
||||
act = 1;
|
||||
if (to != sel) {
|
||||
sel = to;
|
||||
ui_menu_select(sel);
|
||||
serial_item(items, sel, sel, 0);
|
||||
mirrors_show();
|
||||
}
|
||||
}
|
||||
if (!act)
|
||||
continue;
|
||||
|
||||
CHAR16 c = key.UnicodeChar;
|
||||
/* Q as well as Esc, for the same reason f8_window() accepts 'o' as
|
||||
* well as F8: on a serial line a lone ESC byte is indistinguishable
|
||||
* from the start of an escape sequence, so the terminal driver holds
|
||||
* it back waiting for the rest and it never arrives as a key. Esc is
|
||||
* the obvious thing to press on a screen and unreachable on a cable. */
|
||||
if (key.ScanCode == 0x17 || c == 27 || c == L'q' || c == L'Q')
|
||||
return;
|
||||
if (key.ScanCode == 0x01 || c == L'w' || c == L'W') {
|
||||
sel = (sel + RES_N) % (RES_N + 1); continue;
|
||||
}
|
||||
if (key.ScanCode == 0x02 || c == L's' || c == L'S') {
|
||||
sel = (sel + 1) % (RES_N + 1); continue;
|
||||
}
|
||||
if (c == L'\r' || c == L'\n') {
|
||||
if (sel < RES_N) {
|
||||
if (!mode_set_checked(gop, RES_MODE[sel])) {
|
||||
res_refused(gop);
|
||||
|
|
@ -1242,14 +1498,7 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
/* Nothing matched. Saying so beats silently keeping the old mode
|
||||
* and letting the user wonder whether the key registered. */
|
||||
const char *msg[1] = { "No such mode -- keeping this one" };
|
||||
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);
|
||||
}
|
||||
notice("Resolution", msg, 1, "OK", "any key to go back");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1505,19 +1754,205 @@ sys_info(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
mirrors_show();
|
||||
|
||||
EFI_INPUT_KEY key;
|
||||
int item = 0;
|
||||
for (;;) {
|
||||
if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
int e = wait_event(&key, &item, 0);
|
||||
if (e == EV_KEY || e == EV_CLICK)
|
||||
return;
|
||||
BS->Stall(10000);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- other systems ------------------------------------------------------------
|
||||
*
|
||||
* A boot manager that can only boot itself is a menu with one item. Other
|
||||
* operating systems on this machine leave their loader at a known path on an
|
||||
* EFI system partition, and chain-loading one is the firmware's own
|
||||
* LoadImage and StartImage: no ExitBootServices of ours is involved, and if the
|
||||
* other loader gives control back the menu is still here.
|
||||
*
|
||||
* Only paths that are well known are probed. Walking every directory of every
|
||||
* volume would find more and would also find things that are not an operating
|
||||
* system, and a boot menu that lists a firmware updater is not an improvement.
|
||||
*/
|
||||
#define MAX_OTHERS 6
|
||||
|
||||
static const struct { const CHAR16 *path; const char *name; } KNOWN_LOADERS[] = {
|
||||
{ L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi", "Windows Boot Manager" },
|
||||
{ L"\\EFI\\ubuntu\\shimx64.efi", "Ubuntu" },
|
||||
{ L"\\EFI\\debian\\shimx64.efi", "Debian" },
|
||||
{ L"\\EFI\\fedora\\shimx64.efi", "Fedora" },
|
||||
{ L"\\EFI\\arch\\grubx64.efi", "Arch Linux" },
|
||||
{ L"\\EFI\\opensuse\\grubx64.efi", "openSUSE" },
|
||||
{ L"\\EFI\\systemd\\systemd-bootx64.efi", "systemd-boot" },
|
||||
{ L"\\EFI\\refind\\refind_x64.efi", "rEFInd" },
|
||||
{ L"\\EFI\\BOOT\\BOOTX64.EFI", "Another EFI loader" },
|
||||
};
|
||||
|
||||
static struct {
|
||||
EFI_HANDLE vol;
|
||||
const CHAR16 *path;
|
||||
char text[96]; /* "name\ndetail", as the menu wants it */
|
||||
} OTHERS[MAX_OTHERS];
|
||||
static int OTHER_N;
|
||||
|
||||
static void
|
||||
others_find(void)
|
||||
{
|
||||
EFI_GUID fs_guid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
|
||||
EFI_HANDLE *h = 0;
|
||||
UINTN n = 0;
|
||||
|
||||
OTHER_N = 0;
|
||||
if (EFI_ERROR(BS->LocateHandleBuffer(ByProtocol, &fs_guid, 0, &n, &h)) || !h)
|
||||
return;
|
||||
|
||||
for (UINTN i = 0; i < n && OTHER_N < MAX_OTHERS; i++) {
|
||||
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs = 0;
|
||||
EFI_FILE_PROTOCOL *root = 0;
|
||||
if (EFI_ERROR(BS->HandleProtocol(h[i], &fs_guid, (void **)&fs)) || !fs)
|
||||
continue;
|
||||
if (EFI_ERROR(fs->OpenVolume(fs, &root)) || !root)
|
||||
continue;
|
||||
|
||||
for (UINTN k = 0; k < sizeof KNOWN_LOADERS / sizeof KNOWN_LOADERS[0] &&
|
||||
OTHER_N < MAX_OTHERS; k++) {
|
||||
/* Our own volume is probed too -- SchOS shares the ESP with
|
||||
* Windows on a laptop -- but the fallback path there is this
|
||||
* loader, and offering to start ourselves again is no help. */
|
||||
if (h[i] == BOOT_VOL && k == sizeof KNOWN_LOADERS / sizeof KNOWN_LOADERS[0] - 1)
|
||||
continue;
|
||||
EFI_FILE_PROTOCOL *f = 0;
|
||||
if (EFI_ERROR(root->Open(root, &f, (CHAR16 *)KNOWN_LOADERS[k].path,
|
||||
EFI_FILE_MODE_READ, 0)) || !f)
|
||||
continue;
|
||||
f->Close(f);
|
||||
|
||||
int o = OTHER_N++;
|
||||
OTHERS[o].vol = h[i];
|
||||
OTHERS[o].path = KNOWN_LOADERS[k].path;
|
||||
int c = 0;
|
||||
for (const char *q = KNOWN_LOADERS[k].name; *q && c < 40; q++)
|
||||
OTHERS[o].text[c++] = *q;
|
||||
OTHERS[o].text[c++] = '\n';
|
||||
for (const char *q = "Start it from "; *q; q++)
|
||||
OTHERS[o].text[c++] = *q;
|
||||
char pn[48];
|
||||
narrow(KNOWN_LOADERS[k].path, pn, sizeof pn);
|
||||
for (const char *q = pn; *q && c < (int)sizeof OTHERS[o].text - 1; q++)
|
||||
OTHERS[o].text[c++] = *q;
|
||||
OTHERS[o].text[c] = 0;
|
||||
}
|
||||
root->Close(root);
|
||||
}
|
||||
BS->FreePool(h);
|
||||
}
|
||||
|
||||
/* Start another loader. The device path for LoadImage is the volume's own path
|
||||
* with one more node on the end naming the file: copy the volume's nodes up to
|
||||
* its terminator, append a file-path node, and terminate again. */
|
||||
static void
|
||||
chain_load(int o)
|
||||
{
|
||||
EFI_GUID dp_guid = EFI_DEVICE_PATH_PROTOCOL_GUID;
|
||||
static UINT8 dp[512];
|
||||
UINT8 *vol = 0;
|
||||
UINTN len = 0;
|
||||
|
||||
if (EFI_ERROR(BS->HandleProtocol(OTHERS[o].vol, &dp_guid, (void **)&vol)) || !vol)
|
||||
return;
|
||||
|
||||
for (;;) {
|
||||
UINTN nl = (UINTN)vol[len + 2] | ((UINTN)vol[len + 3] << 8);
|
||||
if (nl < 4 || len + nl > sizeof dp - 64)
|
||||
return;
|
||||
if (vol[len] == 0x7F && vol[len + 1] == 0xFF) /* End of Entire Path */
|
||||
break;
|
||||
len += nl;
|
||||
}
|
||||
copy(dp, vol, len);
|
||||
|
||||
UINTN chars = 0;
|
||||
while (OTHERS[o].path[chars])
|
||||
chars++;
|
||||
UINTN nl = 4 + (chars + 1) * 2;
|
||||
if (len + nl + 4 > sizeof dp)
|
||||
return;
|
||||
dp[len] = 4; dp[len + 1] = 4; /* media, file path */
|
||||
dp[len + 2] = (UINT8)nl; dp[len + 3] = (UINT8)(nl >> 8);
|
||||
copy(dp + len + 4, OTHERS[o].path, (chars + 1) * 2);
|
||||
len += nl;
|
||||
dp[len] = 0x7F; dp[len + 1] = 0xFF; dp[len + 2] = 4; dp[len + 3] = 0;
|
||||
|
||||
EFI_HANDLE img = 0;
|
||||
if (EFI_ERROR(BS->LoadImage(0, SELF, dp, 0, 0, &img)) || !img) {
|
||||
const char *msg[1] = { "The firmware would not load it." };
|
||||
notice("Start another system", msg, 1, "OK", "any key to go back");
|
||||
return;
|
||||
}
|
||||
BS->StartImage(img, 0, 0);
|
||||
BS->UnloadImage(img);
|
||||
}
|
||||
|
||||
/* Ask the firmware to open its own setup on the next start, then restart.
|
||||
*
|
||||
* This used to halt with "reset to reach firmware setup", which is the screen
|
||||
* of a loader that never learned the protocol. The firmware publishes a
|
||||
* variable for exactly this: OsIndicationsSupported says what it can do and
|
||||
* OsIndications is where the request goes. Not every firmware has it, and the
|
||||
* ones that do not are told so rather than restarted for nothing. */
|
||||
static void
|
||||
firmware_setup(void)
|
||||
{
|
||||
EFI_RUNTIME_SERVICES *rt = ST->RuntimeServices;
|
||||
EFI_GUID gv = EFI_GLOBAL_VARIABLE_GUID;
|
||||
UINT64 supported = 0, cur = 0;
|
||||
UINTN sz = sizeof supported;
|
||||
UINT32 attr = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
||||
EFI_VARIABLE_RUNTIME_ACCESS;
|
||||
|
||||
if (EFI_ERROR(rt->GetVariable(L"OsIndicationsSupported", &gv, 0, &sz,
|
||||
&supported)) ||
|
||||
!(supported & EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) {
|
||||
const char *msg[2] = { "This firmware cannot be asked to open its",
|
||||
"setup. Use its own key when powering on." };
|
||||
notice("Firmware Setup", msg, 2, "OK", "any key to go back");
|
||||
return;
|
||||
}
|
||||
|
||||
UINT32 a = 0;
|
||||
sz = sizeof cur;
|
||||
if (EFI_ERROR(rt->GetVariable(L"OsIndications", &gv, &a, &sz, &cur)))
|
||||
cur = 0;
|
||||
cur |= EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
|
||||
if (EFI_ERROR(rt->SetVariable(L"OsIndications", &gv, attr, sizeof cur, &cur))) {
|
||||
const char *msg[1] = { "The firmware refused the request." };
|
||||
notice("Firmware Setup", msg, 1, "OK", "any key to go back");
|
||||
return;
|
||||
}
|
||||
rt->ResetSystem(EfiResetCold, 0, 0, 0);
|
||||
for (;;) __asm__ volatile("hlt");
|
||||
}
|
||||
|
||||
static void
|
||||
power(UINTN how)
|
||||
{
|
||||
ST->RuntimeServices->ResetSystem(how, 0, 0, 0);
|
||||
for (;;) __asm__ volatile("hlt");
|
||||
}
|
||||
|
||||
enum { A_BOOT, A_VERBOSE, A_OTHER, A_DISPLAY, A_INFO, A_FIRMWARE, A_RESTART,
|
||||
A_SHUTDOWN, A_DEBUG };
|
||||
|
||||
static int
|
||||
boot_menu(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
||||
{
|
||||
int selected = 0;
|
||||
int echoed = -1; // the serial copy: header once, then moves
|
||||
char resbuf[48];
|
||||
char resbuf[96];
|
||||
|
||||
connect_all();
|
||||
pointers_find();
|
||||
others_find();
|
||||
|
||||
for (;;) {
|
||||
/* Re-read the framebuffer every pass: choosing a resolution moves it,
|
||||
|
|
@ -1532,29 +1967,38 @@ boot_menu(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
|
||||
res_row(gop, resbuf, sizeof resbuf);
|
||||
|
||||
// Six entries in a debug build, five in a release one.
|
||||
//
|
||||
// The debugger entry is compiled out rather than hidden, because a
|
||||
// menu item that is present and refuses is an invitation to work out
|
||||
// how to make it not refuse. An ISO built for anybody else has no way
|
||||
// to turn a network debugger on, because the code that would set the
|
||||
// flag is not in it.
|
||||
const char *items[6];
|
||||
int nitems = 5;
|
||||
items[0] = "Boot SchOS";
|
||||
items[1] = "Boot SchOS (verbose)";
|
||||
items[2] = resbuf;
|
||||
items[3] = "System Information";
|
||||
items[4] = "Reboot to firmware";
|
||||
//
|
||||
// An item is a label, then a newline and a line of detail; a leading
|
||||
// \001 starts a new group, drawn with a gap above it. `what` says what
|
||||
// each row does, because the number of rows is not fixed: other
|
||||
// systems found on the disks are listed after the two ways of
|
||||
// starting this one.
|
||||
const char *items[8 + MAX_OTHERS];
|
||||
int what[8 + MAX_OTHERS], arg[8 + MAX_OTHERS];
|
||||
int nitems = 0;
|
||||
#define ITEM(text, a, x) do { items[nitems] = (text); what[nitems] = (a); arg[nitems] = (x); nitems++; } while (0)
|
||||
ITEM("Boot SchOS\nStart the system", A_BOOT, 0);
|
||||
ITEM("Boot SchOS (verbose)\nShow kernel messages while starting", A_VERBOSE, 0);
|
||||
for (int o = 0; o < OTHER_N; o++)
|
||||
ITEM(OTHERS[o].text, A_OTHER, o);
|
||||
ITEM(resbuf, A_DISPLAY, 0);
|
||||
ITEM("System Information\nFirmware, processor and memory", A_INFO, 0);
|
||||
ITEM("\001Firmware Setup\nRestart into the UEFI settings", A_FIRMWARE, 0);
|
||||
ITEM("Restart\nReboot the machine", A_RESTART, 0);
|
||||
ITEM("Shut Down\nPower the machine off", A_SHUTDOWN, 0);
|
||||
#ifdef SCHOS_DEBUG
|
||||
items[5] = DEBUG_BOOT ? "Remote debugging: on (schdbgd, port 4444)"
|
||||
: "Remote debugging: off";
|
||||
nitems = 6;
|
||||
ITEM(DEBUG_BOOT ? "Remote debugging: on\nschdbgd, port 4444"
|
||||
: "Remote debugging: off\nschdbgd, port 4444", A_DEBUG, 0);
|
||||
#endif
|
||||
const char *hint = nitems == 6 ? "W/S or 1-6 to choose, Enter to select"
|
||||
: "W/S or 1-5 to choose, Enter to select";
|
||||
const char *hint = "W/S or arrows to choose, Enter to select - number keys jump";
|
||||
if (selected >= nitems) selected = 0;
|
||||
|
||||
ui_menu("Boot Manager", items, nitems, selected, hint);
|
||||
ui_menu("SchOS Boot Manager", items, nitems, selected, hint);
|
||||
|
||||
// The same menu on the serial line: the whole thing once, then one
|
||||
// line per *move*. Reprinting on every keypress would be correct and
|
||||
|
|
@ -1562,46 +2006,58 @@ boot_menu(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
// unrecognised key would scroll the selection past again for nothing.
|
||||
if (echoed < 0) {
|
||||
serial_menu("Boot Manager", items, nitems, selected, hint, 1);
|
||||
} else if (echoed != selected) {
|
||||
serial_item(items, selected, selected, 1);
|
||||
}
|
||||
echoed = selected;
|
||||
mirrors_show();
|
||||
|
||||
EFI_INPUT_KEY key;
|
||||
CHAR16 c = 0;
|
||||
UINT16 scan = 0;
|
||||
for (;;) {
|
||||
if (ST->ConIn &&
|
||||
!EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key))) {
|
||||
c = key.UnicodeChar; scan = key.ScanCode; break;
|
||||
EFI_INPUT_KEY key;
|
||||
int item = -1, act = 0;
|
||||
int e = wait_event(&key, &item, 0);
|
||||
int to = selected;
|
||||
|
||||
if (e == EV_HOVER || e == EV_CLICK) {
|
||||
to = item;
|
||||
act = e == EV_CLICK;
|
||||
} else {
|
||||
CHAR16 c = key.UnicodeChar;
|
||||
if (key.ScanCode == 0x01 || c == L'w' || c == L'W')
|
||||
to = (selected + nitems - 1) % nitems;
|
||||
else if (key.ScanCode == 0x02 || c == L's' || c == L'S')
|
||||
to = (selected + 1) % nitems;
|
||||
else if (c >= L'1' && c <= L'9' && (int)(c - L'1') < nitems)
|
||||
to = (int)(c - L'1');
|
||||
else if (c == L'\r' || c == L'\n')
|
||||
act = 1;
|
||||
}
|
||||
BS->Stall(10000);
|
||||
}
|
||||
|
||||
if (scan == 0x01) { selected = (selected + nitems - 1) % nitems; continue; }
|
||||
if (scan == 0x02) { selected = (selected + 1) % nitems; continue; }
|
||||
if (c == L'w' || c == L'W') { selected = (selected + nitems - 1) % nitems; continue; }
|
||||
if (c == L's' || c == L'S') { selected = (selected + 1) % nitems; continue; }
|
||||
if (c >= L'1' && c <= L'0' + nitems) { selected = (int)(c - L'1'); continue; }
|
||||
if (to != selected) {
|
||||
selected = to;
|
||||
ui_menu_select(selected);
|
||||
serial_item(items, selected, selected, 1);
|
||||
echoed = selected;
|
||||
mirrors_show();
|
||||
}
|
||||
if (!act)
|
||||
continue;
|
||||
|
||||
if (c == L'\r' || c == L'\n') {
|
||||
if (selected == 0) return 0;
|
||||
if (selected == 1) return 1;
|
||||
if (selected == 2) { res_choose(gop); continue; }
|
||||
if (selected == 3) { sys_info(gop); continue; }
|
||||
switch (what[selected]) {
|
||||
case A_BOOT: return 0;
|
||||
case A_VERBOSE: return 1;
|
||||
case A_OTHER: chain_load(arg[selected]); break;
|
||||
case A_DISPLAY: res_choose(gop); break;
|
||||
case A_INFO: sys_info(gop); break;
|
||||
case A_FIRMWARE: firmware_setup(); break;
|
||||
case A_RESTART: power(EfiResetCold); break;
|
||||
case A_SHUTDOWN: power(EfiResetShutdown); break;
|
||||
#ifdef SCHOS_DEBUG
|
||||
// A toggle rather than an action: picking it changes what the
|
||||
// entry says and leaves you in the menu, so the state you booted
|
||||
// with is the state you can see before you boot.
|
||||
if (selected == 5) { DEBUG_BOOT = !DEBUG_BOOT; echoed = -1; continue; }
|
||||
case A_DEBUG: DEBUG_BOOT = !DEBUG_BOOT; echoed = -1; break;
|
||||
#endif
|
||||
if (selected == 4) {
|
||||
ui_clear();
|
||||
ui_text_centered((int)ui_height() / 2, "Halted -- reset to reach firmware setup",
|
||||
0xB9BEC7u);
|
||||
for (;;) __asm__ volatile("hlt");
|
||||
}
|
||||
break; // repaint: the thing that ran may have changed the screen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2180,6 +2636,7 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
|
|||
{
|
||||
ST = systab;
|
||||
BS = systab->BootServices;
|
||||
SELF = image;
|
||||
|
||||
print(L"SchOS UEFI loader\r\n");
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue