diff options
author | Jan Janssen <medhefgo@web.de> | 2021-08-15 13:44:47 +0200 |
---|---|---|
committer | Jan Janssen <medhefgo@web.de> | 2021-09-15 16:32:18 +0200 |
commit | 1b965abc66712fcc0a6b8ba60a1e0781c7b0e364 (patch) | |
tree | 5332b56644f60c893faece7f416fd1808e409ae5 | |
parent | 134144abc80285413691017b37b5dd47a9aeed7a (diff) | |
download | systemd-1b965abc66712fcc0a6b8ba60a1e0781c7b0e364.tar.gz |
sd-boot: Add support for changing console mode at runtime
-rw-r--r-- | man/systemd-boot.xml | 10 | ||||
-rw-r--r-- | src/boot/efi/boot.c | 227 | ||||
-rw-r--r-- | src/boot/efi/console.c | 15 | ||||
-rw-r--r-- | src/boot/efi/console.h | 1 | ||||
-rw-r--r-- | src/boot/efi/util.c | 5 | ||||
-rw-r--r-- | src/boot/efi/util.h | 1 |
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); |