summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Janssen <medhefgo@web.de>2021-08-15 13:44:47 +0200
committerJan Janssen <medhefgo@web.de>2021-09-15 16:32:18 +0200
commit1b965abc66712fcc0a6b8ba60a1e0781c7b0e364 (patch)
tree5332b56644f60c893faece7f416fd1808e409ae5
parent134144abc80285413691017b37b5dd47a9aeed7a (diff)
downloadsystemd-1b965abc66712fcc0a6b8ba60a1e0781c7b0e364.tar.gz
sd-boot: Add support for changing console mode at runtime
-rw-r--r--man/systemd-boot.xml10
-rw-r--r--src/boot/efi/boot.c227
-rw-r--r--src/boot/efi/console.c15
-rw-r--r--src/boot/efi/console.h1
-rw-r--r--src/boot/efi/util.c5
-rw-r--r--src/boot/efi/util.h1
6 files changed, 172 insertions, 87 deletions
diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml
index d3306593e4..8685ed50ff 100644
--- a/man/systemd-boot.xml
+++ b/man/systemd-boot.xml
@@ -152,6 +152,16 @@
</varlistentry>
<varlistentry>
+ <term><keycap>r</keycap></term>
+ <listitem><para>Change screen resolution, skipping any unsupported modes.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><keycap>R</keycap></term>
+ <listitem><para>Reset screen resolution to firmware or configuration file default.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><keycap>p</keycap></term>
<listitem><para>Print status</para></listitem>
</varlistentry>
diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c
index df369b426b..af02cfb7e9 100644
--- a/src/boot/efi/boot.c
+++ b/src/boot/efi/boot.c
@@ -70,6 +70,7 @@ typedef struct {
BOOLEAN auto_firmware;
BOOLEAN force_menu;
INT64 console_mode;
+ INT64 console_mode_efivar;
RandomSeedMode random_seed_mode;
} Config;
@@ -374,9 +375,7 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
assert(config);
assert(loaded_image_path);
- uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, COLOR_NORMAL);
- uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
-
+ clear_screen(COLOR_NORMAL);
console_query_mode(&x_max, &y_max);
Print(L"systemd-boot version: " GIT_VERSION "\n");
@@ -385,6 +384,7 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
Print(L"UEFI specification: %d.%02d\n", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
Print(L"firmware vendor: %s\n", ST->FirmwareVendor);
Print(L"firmware version: %d.%02d\n", ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
+ Print(L"console mode: %d/%ld\n", ST->ConOut->Mode->Mode, ST->ConOut->Mode->MaxMode - 1LL);
Print(L"console size: %d x %d\n", x_max, y_max);
Print(L"SecureBoot: %s\n", yes_no(secure_boot_enabled()));
@@ -497,8 +497,6 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
Print(L"\n--- press key ---\n\n");
console_key_read(&key, 0);
}
-
- uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
}
static BOOLEAN menu_run(
@@ -511,98 +509,115 @@ static BOOLEAN menu_run(
assert(loaded_image_path);
EFI_STATUS err;
- UINTN visible_max;
+ UINTN visible_max = 0;
UINTN idx_highlight = config->idx_default;
UINTN idx_highlight_prev = 0;
- UINTN idx_first;
- UINTN idx_last;
- BOOLEAN refresh = TRUE;
- BOOLEAN highlight = FALSE;
- UINTN line_width = 0;
- UINTN entry_padding = 3;
- CHAR16 **lines;
- UINTN x_start;
- UINTN y_start;
- UINTN x_max;
- UINTN y_max;
- CHAR16 *status = NULL;
- CHAR16 *clearline;
+ UINTN idx_first = 0, idx_last = 0;
+ BOOLEAN new_mode = TRUE, clear = TRUE;
+ BOOLEAN refresh = TRUE, highlight = FALSE;
+ UINTN x_start = 0, y_start = 0, y_status = 0;
+ UINTN x_max, y_max;
+ CHAR16 **lines = NULL, *status = NULL, *clearline = NULL;
UINTN timeout_remain = config->timeout_sec;
INT16 idx;
- BOOLEAN exit = FALSE;
- BOOLEAN run = TRUE;
+ BOOLEAN exit = FALSE, run = TRUE;
+ INT64 console_mode_initial = ST->ConOut->Mode->Mode, console_mode_efivar_saved = config->console_mode_efivar;
graphics_mode(FALSE);
uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE);
uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE);
- uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, COLOR_NORMAL);
/* draw a single character to make ClearScreen work on some firmware */
Print(L" ");
- err = console_set_mode(config->console_mode);
- uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
- if (EFI_ERROR(err))
+ err = console_set_mode(config->console_mode_efivar != CONSOLE_MODE_KEEP ?
+ config->console_mode_efivar : config->console_mode);
+ if (EFI_ERROR(err)) {
+ clear_screen(COLOR_NORMAL);
log_error_stall(L"Error switching console mode: %r", err);
- console_query_mode(&x_max, &y_max);
+ }
- visible_max = y_max - 2;
+ while (!exit) {
+ UINT64 key;
- /* Drawing entries starts at idx_first until idx_last. We want to make
- * sure that idx_highlight is centered, but not if we are close to the
- * beginning/end of the entry list. Otherwise we would have a half-empty
- * screen. */
- if (config->entry_count <= visible_max || idx_highlight <= visible_max / 2)
- idx_first = 0;
- else if (idx_highlight >= config->entry_count - (visible_max / 2))
- idx_first = config->entry_count - visible_max;
- else
- idx_first = idx_highlight - (visible_max / 2);
- idx_last = idx_first + visible_max - 1;
+ if (new_mode) {
+ UINTN line_width = 0, entry_padding = 3;
- /* length of the longest entry */
- for (UINTN i = 0; i < config->entry_count; i++)
- line_width = MAX(line_width, StrLen(config->entries[i]->title_show));
- line_width = MIN(line_width + 2 * entry_padding, x_max);
+ console_query_mode(&x_max, &y_max);
- /* offsets to center the entries on the screen */
- x_start = (x_max - (line_width)) / 2;
- if (config->entry_count < visible_max)
- y_start = ((visible_max - config->entry_count) / 2) + 1;
- else
- y_start = 0;
+ /* account for padding+status */
+ visible_max = y_max - 2;
- /* menu entries title lines */
- lines = AllocatePool(sizeof(CHAR16 *) * config->entry_count);
- for (UINTN i = 0; i < config->entry_count; i++) {
- UINTN j;
+ /* Drawing entries starts at idx_first until idx_last. We want to make
+ * sure that idx_highlight is centered, but not if we are close to the
+ * beginning/end of the entry list. Otherwise we would have a half-empty
+ * screen. */
+ if (config->entry_count <= visible_max || idx_highlight <= visible_max / 2)
+ idx_first = 0;
+ else if (idx_highlight >= config->entry_count - (visible_max / 2))
+ idx_first = config->entry_count - visible_max;
+ else
+ idx_first = idx_highlight - (visible_max / 2);
+ idx_last = idx_first + visible_max - 1;
+
+ /* length of the longest entry */
+ for (UINTN i = 0; i < config->entry_count; i++)
+ line_width = MAX(line_width, StrLen(config->entries[i]->title_show));
+ line_width = MIN(line_width + 2 * entry_padding, x_max);
+
+ /* offsets to center the entries on the screen */
+ x_start = (x_max - (line_width)) / 2;
+ if (config->entry_count < visible_max)
+ y_start = ((visible_max - config->entry_count) / 2) + 1;
+ else
+ y_start = 0;
+
+ /* Put status line after the entry list, but give it some breathing room. */
+ y_status = MIN(y_start + MIN(visible_max, config->entry_count) + 4, y_max - 1);
+
+ if (lines) {
+ for (UINTN i = 0; i < config->entry_count; i++)
+ FreePool(lines[i]);
+ FreePool(lines);
+ FreePool(clearline);
+ }
- lines[i] = AllocatePool(((line_width + 1) * sizeof(CHAR16)));
- UINTN padding = (line_width - MIN(StrLen(config->entries[i]->title_show), line_width)) / 2;
+ /* menu entries title lines */
+ lines = AllocatePool(sizeof(CHAR16 *) * config->entry_count);
+ for (UINTN i = 0; i < config->entry_count; i++) {
+ UINTN j, padding;
- for (j = 0; j < padding; j++)
- lines[i][j] = ' ';
+ lines[i] = AllocatePool(((line_width + 1) * sizeof(CHAR16)));
+ padding = (line_width - MIN(StrLen(config->entries[i]->title_show), line_width)) / 2;
- for (UINTN k = 0; config->entries[i]->title_show[k] != '\0' && j < line_width; j++, k++)
- lines[i][j] = config->entries[i]->title_show[k];
+ for (j = 0; j < padding; j++)
+ lines[i][j] = ' ';
- for (; j < line_width; j++)
- lines[i][j] = ' ';
- lines[i][line_width] = '\0';
- }
+ for (UINTN k = 0; config->entries[i]->title_show[k] != '\0' && j < line_width; j++, k++)
+ lines[i][j] = config->entries[i]->title_show[k];
- clearline = AllocatePool((x_max+1) * sizeof(CHAR16));
- for (UINTN i = 0; i < x_max; i++)
- clearline[i] = ' ';
- clearline[x_max] = 0;
+ for (; j < line_width; j++)
+ lines[i][j] = ' ';
+ lines[i][line_width] = '\0';
+ }
- while (!exit) {
- UINT64 key;
+ clearline = AllocatePool((x_max+1) * sizeof(CHAR16));
+ for (UINTN i = 0; i < x_max; i++)
+ clearline[i] = ' ';
+ clearline[x_max] = 0;
+
+ new_mode = FALSE;
+ clear = TRUE;
+ }
+
+ if (clear) {
+ clear_screen(COLOR_NORMAL);
+ clear = FALSE;
+ refresh = TRUE;
+ }
if (refresh) {
- for (UINTN i = 0; i < config->entry_count; i++) {
- if (i < idx_first || i > idx_last)
- continue;
+ for (UINTN i = idx_first; i <= idx_last && i < config->entry_count; i++) {
print_at(x_start, y_start + i - idx_first,
(i == idx_highlight) ? COLOR_HIGHLIGHT : COLOR_ENTRY,
lines[i]);
@@ -638,7 +653,7 @@ static BOOLEAN menu_run(
x = (x_max - len) / 2;
else
x = 0;
- print_at(0, y_max - 1, COLOR_NORMAL, clearline + (x_max - x));
+ print_at(0, y_status, COLOR_NORMAL, clearline + (x_max - x));
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, status);
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + x + len);
}
@@ -660,7 +675,7 @@ static BOOLEAN menu_run(
if (status) {
FreePool(status);
status = NULL;
- print_at(0, y_max - 1, COLOR_NORMAL, clearline + 1);
+ print_at(0, y_status, COLOR_NORMAL, clearline + 1);
}
idx_highlight_prev = idx_highlight;
@@ -722,7 +737,7 @@ static BOOLEAN menu_run(
case KEYPRESS(0, 0, 'H'):
case KEYPRESS(0, 0, '?'):
/* This must stay below 80 characters! Q/v/Ctrl+l deliberately not advertised. */
- status = StrDuplicate(L"(d)efault (t/T)timeout (e)dit (p)rint (h)elp");
+ status = StrDuplicate(L"(d)efault (t/T)timeout (e)dit (r/R)resolution (p)rint (h)elp");
break;
case KEYPRESS(0, 0, 'Q'):
@@ -802,9 +817,9 @@ static BOOLEAN menu_run(
* causing a scroll to happen that screws with our beautiful boot loader output.
* Since we cannot paint the last character of the edit line, we simply start
* at x-offset 1 for symmetry. */
- print_at(1, y_max - 1, COLOR_EDIT, clearline + 2);
- exit = line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max-2, y_max-1);
- print_at(1, y_max - 1, COLOR_NORMAL, clearline + 2);
+ print_at(1, y_status, COLOR_EDIT, clearline + 2);
+ exit = line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max - 2, y_status);
+ print_at(1, y_status, COLOR_NORMAL, clearline + 2);
break;
case KEYPRESS(0, 0, 'v'):
@@ -816,12 +831,35 @@ static BOOLEAN menu_run(
case KEYPRESS(0, 0, 'p'):
case KEYPRESS(0, 0, 'P'):
print_status(config, loaded_image_path);
- refresh = TRUE;
+ clear = TRUE;
break;
case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'l'):
case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('l')):
- refresh = TRUE;
+ clear = TRUE;
+ break;
+
+ case KEYPRESS(0, 0, 'r'):
+ err = console_set_mode(CONSOLE_MODE_NEXT);
+ if (EFI_ERROR(err))
+ status = PoolPrint(L"Error changing console mode: %r", err);
+ else {
+ config->console_mode_efivar = ST->ConOut->Mode->Mode;
+ status = PoolPrint(L"Console mode changed to %ld.", config->console_mode_efivar);
+ }
+ new_mode = TRUE;
+ break;
+
+ case KEYPRESS(0, 0, 'R'):
+ config->console_mode_efivar = CONSOLE_MODE_KEEP;
+ err = console_set_mode(config->console_mode == CONSOLE_MODE_KEEP ?
+ console_mode_initial : config->console_mode);
+ if (EFI_ERROR(err))
+ status = PoolPrint(L"Error resetting console mode: %r", err);
+ else
+ status = PoolPrint(L"Console mode reset to %s default.",
+ config->console_mode == CONSOLE_MODE_KEEP ? L"firmware" : L"configuration file");
+ new_mode = TRUE;
break;
default:
@@ -849,13 +887,23 @@ static BOOLEAN menu_run(
*chosen_entry = config->entries[idx_highlight];
+ /* The user is likely to cycle through several modes before
+ * deciding to keep one. Therefore, we update the EFI var after
+ * we left the menu to reduce nvram writes. */
+ if (console_mode_efivar_saved != config->console_mode_efivar) {
+ if (config->console_mode_efivar == CONSOLE_MODE_KEEP)
+ efivar_set(LOADER_GUID, L"LoaderConfigConsoleMode", NULL, EFI_VARIABLE_NON_VOLATILE);
+ else
+ efivar_set_uint_string(LOADER_GUID, L"LoaderConfigConsoleMode",
+ config->console_mode_efivar, EFI_VARIABLE_NON_VOLATILE);
+ }
+
for (UINTN i = 0; i < config->entry_count; i++)
FreePool(lines[i]);
FreePool(lines);
FreePool(clearline);
- uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, COLOR_NORMAL);
- uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
+ clear_screen(COLOR_NORMAL);
return run;
}
@@ -1398,7 +1446,7 @@ static VOID config_entry_add_from_file(
static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) {
_cleanup_freepool_ CHAR8 *content = NULL;
- UINTN sec;
+ UINTN value;
EFI_STATUS err;
assert(root_dir);
@@ -1410,27 +1458,32 @@ static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) {
.random_seed_mode = RANDOM_SEED_WITH_SYSTEM_TOKEN,
.idx_default_efivar = -1,
.console_mode = CONSOLE_MODE_KEEP,
+ .console_mode_efivar = CONSOLE_MODE_KEEP,
};
err = file_read(root_dir, L"\\loader\\loader.conf", 0, 0, &content, NULL);
if (!EFI_ERROR(err))
config_defaults_load_from_file(config, content);
- err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigTimeout", &sec);
+ err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigTimeout", &value);
if (!EFI_ERROR(err)) {
- config->timeout_sec_efivar = sec > INTN_MAX ? INTN_MAX : sec;
- config->timeout_sec = sec;
+ config->timeout_sec_efivar = value > INTN_MAX ? INTN_MAX : value;
+ config->timeout_sec = value;
} else
config->timeout_sec_efivar = -1;
- err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigTimeoutOneShot", &sec);
+ err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigTimeoutOneShot", &value);
if (!EFI_ERROR(err)) {
/* Unset variable now, after all it's "one shot". */
(void) efivar_set(LOADER_GUID, L"LoaderConfigTimeoutOneShot", NULL, EFI_VARIABLE_NON_VOLATILE);
- config->timeout_sec = sec;
+ config->timeout_sec = value;
config->force_menu = TRUE; /* force the menu when this is set */
}
+
+ err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigConsoleMode", &value);
+ if (!EFI_ERROR(err))
+ config->console_mode_efivar = value;
}
static VOID config_load_entries(
diff --git a/src/boot/efi/console.c b/src/boot/efi/console.c
index 5f1b3500db..b581c21bda 100644
--- a/src/boot/efi/console.c
+++ b/src/boot/efi/console.c
@@ -198,6 +198,21 @@ EFI_STATUS console_set_mode(INT64 mode) {
return change_mode(CONSOLE_MODE_RANGE_MIN);
return EFI_SUCCESS;
+ case CONSOLE_MODE_NEXT:
+ if (ST->ConOut->Mode->MaxMode <= CONSOLE_MODE_RANGE_MIN)
+ return EFI_UNSUPPORTED;
+
+ mode = MAX(CONSOLE_MODE_RANGE_MIN, ST->ConOut->Mode->Mode);
+ do {
+ mode = (mode + 1) % ST->ConOut->Mode->MaxMode;
+ if (!EFI_ERROR(change_mode(mode)))
+ break;
+ /* If this mode is broken/unsupported, try the next.
+ * If mode is 0, we wrapped around and should stop. */
+ } while (mode > CONSOLE_MODE_RANGE_MIN);
+
+ return EFI_SUCCESS;
+
case CONSOLE_MODE_AUTO:
return change_mode(get_auto_mode());
diff --git a/src/boot/efi/console.h b/src/boot/efi/console.h
index ec1a96871a..90086028c0 100644
--- a/src/boot/efi/console.h
+++ b/src/boot/efi/console.h
@@ -21,6 +21,7 @@ enum {
/* These are our own mode values that map to concrete values at runtime. */
CONSOLE_MODE_KEEP = CONSOLE_MODE_RANGE_MAX + 1LL,
+ CONSOLE_MODE_NEXT,
CONSOLE_MODE_AUTO,
CONSOLE_MODE_FIRMWARE_MAX, /* 'max' in config. */
};
diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c
index b78408e1ab..065e1ea396 100644
--- a/src/boot/efi/util.c
+++ b/src/boot/efi/util.c
@@ -522,3 +522,8 @@ VOID print_at(UINTN x, UINTN y, UINTN attr, const CHAR16 *str) {
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, attr);
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, (CHAR16*)str);
}
+
+VOID clear_screen(UINTN attr) {
+ uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, attr);
+ uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
+}
diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h
index 9eef51cc7e..a5b6435f1b 100644
--- a/src/boot/efi/util.h
+++ b/src/boot/efi/util.h
@@ -93,3 +93,4 @@ static inline VOID *mempmem_safe(const VOID *haystack, UINTN haystack_len, const
}
VOID print_at(UINTN x, UINTN y, UINTN attr, const CHAR16 *str);
+VOID clear_screen(UINTN attr);