loader: the F8 menu on the serial line
The three F8 screens painted into the framebuffer and nowhere else. That is
right on a machine with a screen and useless on one driven over a serial cable
-- which is how this loader is usually driven, and which f8_window() already
went out of its way to support: it accepts 'o' as well as F8 because, in its
own words, "a serial console sends no scan codes at all".
So the prompt invited a serial user in, drew them a menu they could not see,
and then blocked for ever on a key they had no way to choose. The whole visible
behaviour was:
Press F8 for boot options...
<nothing, for ever>
Each screen echoes itself to COM1 now -- the boot menu, the resolution list and
System Information. The whole thing once on entry, then one line per *move*;
not on every keypress, because a serial terminal has no cursor to paint over
and an unrecognised key would scroll the selection past again for nothing.
Straight to COM1 rather than through print(): print() goes to ConOut until the
splash is up, and ConOut on a machine with graphics draws its text across the
menu that was just painted there.
Two details found by actually driving it, neither cosmetic:
- **The item number is shown only where it is a key you can press.** The boot
menu takes 1-5. The resolution list takes only W/S and Enter and is longer
than nine entries, so numbering it printed ':', ';', '<' past the ninth --
a character nobody can type, offering to select something.
- **Q goes back as well as Esc.** A lone ESC byte on a serial line 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 --
the same shape of problem as F8, and the same answer.
Driven end to end over serial: open the menu, walk it with W/S, open System
Information and come back, open Resolution and come back with Q, then boot.
The graphical menu is unchanged apart from the hint now naming Q.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
bcd4d4e2bd
commit
3f2c0bdf05
2 changed files with 135 additions and 2 deletions
107
boot/uefi/main.c
107
boot/uefi/main.c
|
|
@ -112,6 +112,72 @@ serial_putc(char c)
|
|||
__asm__ volatile("outb %0, %1" : : "a"((UINT8)c), "d"((UINT16)0x3F8));
|
||||
}
|
||||
|
||||
/* The menus, on the serial line.
|
||||
*
|
||||
* The three screens below paint into the framebuffer, which is the right place
|
||||
* for them on a machine with a screen and nowhere at all on one driven over a
|
||||
* serial cable. That is how this loader is usually driven, and f8_window()
|
||||
* already went out of its way to support it -- it accepts 'o' as well as F8
|
||||
* precisely because "a serial console sends no scan codes at all". So the
|
||||
* prompt invited a serial user in, drew them a menu they could not see, and
|
||||
* then blocked for ever on a key they had no way to choose.
|
||||
*
|
||||
* Straight to COM1 rather than through print(): print() goes to ConOut until
|
||||
* the splash is up, and ConOut on a machine with graphics draws its text over
|
||||
* the menu that was just painted there.
|
||||
*
|
||||
* A machine with no COM1 reads 0xFF from the status register, serial_putc()
|
||||
* gives up after its bounded wait, and the write goes nowhere. So this costs
|
||||
* nothing where it is useless and is the whole interface where it is not. */
|
||||
static void
|
||||
serial_puts(const char *s)
|
||||
{
|
||||
for (; *s; s++) {
|
||||
if (*s == '\n')
|
||||
serial_putc('\r');
|
||||
serial_putc(*s);
|
||||
}
|
||||
}
|
||||
|
||||
/* One item, as the selected line or not. The marker is the same width either
|
||||
* way, so the list does not shift sideways as the selection moves.
|
||||
*
|
||||
* `numbered` is not decoration: the number is shown exactly where it is a key
|
||||
* you can press. The boot menu takes 1-5; the resolution list takes only W/S
|
||||
* and Enter, and is longer than nine entries -- numbering it printed ':', ';',
|
||||
* '<' past the ninth, which is a character nobody can type offering to select
|
||||
* something. */
|
||||
static void
|
||||
serial_item(const char *const *items, int i, int sel, int numbered)
|
||||
{
|
||||
serial_puts(i == sel ? " > " : " ");
|
||||
if (numbered && i < 9) {
|
||||
char n[4];
|
||||
n[0] = (char)('1' + i);
|
||||
n[1] = '.';
|
||||
n[2] = ' ';
|
||||
n[3] = 0;
|
||||
serial_puts(n);
|
||||
} else if (numbered) {
|
||||
serial_puts(" "); /* keep the column */
|
||||
}
|
||||
serial_puts(items[i]);
|
||||
serial_puts("\n");
|
||||
}
|
||||
|
||||
static void
|
||||
serial_menu(const char *title, const char *const *items, int n, int sel,
|
||||
const char *hint, int numbered)
|
||||
{
|
||||
serial_puts("\n-- ");
|
||||
serial_puts(title);
|
||||
serial_puts(" --\n");
|
||||
for (int i = 0; i < n; i++)
|
||||
serial_item(items, i, sel, numbered);
|
||||
serial_puts(hint);
|
||||
serial_puts("\n");
|
||||
}
|
||||
|
||||
/* Diagnostics: only on a verbose boot.
|
||||
*
|
||||
* These four lines say where the framebuffer is, where the kernel went and
|
||||
|
|
@ -561,6 +627,7 @@ static void
|
|||
res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
||||
{
|
||||
int sel = 0;
|
||||
int echoed = -1;
|
||||
|
||||
for (;;) {
|
||||
struct ui_fb uifb;
|
||||
|
|
@ -578,7 +645,15 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
|
||||
if (sel > RES_N) sel = RES_N;
|
||||
ui_menu("Resolution", items, RES_N + 1, sel,
|
||||
"W/S to choose, Enter to apply, Esc to go back");
|
||||
"W/S to choose, Enter to apply, Esc or Q to go back");
|
||||
|
||||
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);
|
||||
}
|
||||
echoed = sel;
|
||||
|
||||
EFI_INPUT_KEY key;
|
||||
for (;;) {
|
||||
|
|
@ -589,7 +664,12 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
}
|
||||
|
||||
CHAR16 c = key.UnicodeChar;
|
||||
if (key.ScanCode == 0x17 || c == 27) /* Esc */
|
||||
/* 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;
|
||||
|
|
@ -816,6 +896,16 @@ sys_info(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
|
||||
ui_panel("System Information", labels, values, 7, "Any key to go back");
|
||||
|
||||
serial_puts("\n-- System Information --\n");
|
||||
for (int i = 0; i < 7; i++) {
|
||||
serial_puts(" ");
|
||||
serial_puts(labels[i]);
|
||||
serial_puts(" ");
|
||||
serial_puts(values[i]);
|
||||
serial_puts("\n");
|
||||
}
|
||||
serial_puts("Any key to go back\n");
|
||||
|
||||
EFI_INPUT_KEY key;
|
||||
for (;;) {
|
||||
if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key)))
|
||||
|
|
@ -828,6 +918,7 @@ 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];
|
||||
|
||||
for (;;) {
|
||||
|
|
@ -853,6 +944,18 @@ boot_menu(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop)
|
|||
ui_menu("Boot Manager", items, 5, selected,
|
||||
"W/S or 1-5 to choose, Enter to select");
|
||||
|
||||
// The same menu on the serial line: the whole thing once, then one
|
||||
// line per *move*. Reprinting on every keypress would be correct and
|
||||
// unreadable -- a serial terminal has no cursor to paint over, so an
|
||||
// unrecognised key would scroll the selection past again for nothing.
|
||||
if (echoed < 0) {
|
||||
serial_menu("Boot Manager", items, 5, selected,
|
||||
"W/S or 1-5 to choose, Enter to select", 1);
|
||||
} else if (echoed != selected) {
|
||||
serial_item(items, selected, selected, 1);
|
||||
}
|
||||
echoed = selected;
|
||||
|
||||
EFI_INPUT_KEY key;
|
||||
CHAR16 c = 0;
|
||||
UINT16 scan = 0;
|
||||
|
|
|
|||
|
|
@ -79,6 +79,36 @@ Dropping back to protected mode to re-enter through the 32-bit multiboot path
|
|||
is a mode switch whose failure mode is a triple fault with no console. The
|
||||
32-bit entry is still present in `entry.S` and now has no caller.
|
||||
|
||||
### The menu is on the serial line too
|
||||
|
||||
The three F8 screens paint into the framebuffer, which is the right place for
|
||||
them on a machine with a screen and nowhere at all on one driven over a serial
|
||||
cable — which is how this loader is usually driven. `f8_window()` already went
|
||||
out of its way to support that, accepting `o` as well as F8 because *"a serial
|
||||
console sends no scan codes at all"*. So the prompt invited a serial user in,
|
||||
drew them a menu they could not see, and blocked for ever on a key they had no
|
||||
way to choose.
|
||||
|
||||
Each screen now echoes itself to COM1: the whole menu once, then one line per
|
||||
*move*. Not on every keypress — a serial terminal has no cursor to paint over,
|
||||
so an unrecognised key would scroll the selection past again for nothing.
|
||||
|
||||
Straight to COM1 rather than through `print()`, because `print()` goes to
|
||||
`ConOut` until the splash is up, and `ConOut` on a machine with graphics draws
|
||||
its text across the menu that was just painted there.
|
||||
|
||||
Two details that are not cosmetic:
|
||||
|
||||
- **The item number is shown only where it is a key you can press.** The boot
|
||||
menu takes 1–5. The resolution list takes only W/S and Enter and is longer
|
||||
than nine entries, so numbering it printed `:`, `;`, `<` past the ninth — a
|
||||
character nobody can type, offering to select something.
|
||||
- **Q goes back as well as Esc.** 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 — the
|
||||
same shape of problem, and the same answer, as `o` for F8.
|
||||
|
||||
## 3. `kmain()`
|
||||
|
||||
`kernel/core/main.c`. The ordering here is not arbitrary and several steps are
|
||||
|
|
|
|||
Loading…
Reference in a new issue