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 @@ -151,6 +151,16 @@ Decrease the timeout + + r + Change screen resolution, skipping any unsupported modes. + + + + R + Reset screen resolution to firmware or configuration file default. + + p Print status diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 50a17e0a77..af02cfb7e9 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -69,8 +69,8 @@ typedef struct { BOOLEAN auto_entries; BOOLEAN auto_firmware; BOOLEAN force_menu; - UINTN console_mode; - enum console_mode_change_type console_mode_change; + INT64 console_mode; + INT64 console_mode_efivar; RandomSeedMode random_seed_mode; } Config; @@ -370,13 +370,13 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) { UINTN timeout; BOOLEAN modevar; _cleanup_freepool_ CHAR16 *partstr = NULL, *defaultstr = NULL; - UINTN x, y; + UINTN x_max, y_max; 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"); Print(L"architecture: " EFI_MACHINE_TYPE_NAME "\n"); @@ -384,10 +384,8 @@ 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); - - if (uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x, &y) == EFI_SUCCESS) - Print(L"console size: %d x %d\n", x, y); - + 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())); if (efivar_get_boolean_u8(EFI_GLOBAL_GUID, L"SetupMode", &modevar) == EFI_SUCCESS) @@ -499,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( @@ -513,107 +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" "); - if (config->console_mode_change != CONSOLE_MODE_KEEP) { - err = console_set_mode(&config->console_mode, config->console_mode_change); - if (EFI_ERROR(err)) { - uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); - log_error_stall(L"Error switching console mode to %lu: %r", (UINT64)config->console_mode, err); - } - } else - uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); - - err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x_max, &y_max); + err = console_set_mode(config->console_mode_efivar != CONSOLE_MODE_KEEP ? + config->console_mode_efivar : config->console_mode); if (EFI_ERROR(err)) { - x_max = 80; - y_max = 25; + clear_screen(COLOR_NORMAL); + log_error_stall(L"Error switching console mode: %r", err); } - visible_max = y_max - 2; - - /* 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; - - /* menu entries title lines */ - lines = AllocatePool(sizeof(CHAR16 *) * config->entry_count); - for (UINTN i = 0; i < config->entry_count; i++) { - UINTN j; - - lines[i] = AllocatePool(((line_width + 1) * sizeof(CHAR16))); - UINTN padding = (line_width - MIN(StrLen(config->entries[i]->title_show), line_width)) / 2; - - for (j = 0; j < padding; j++) - lines[i][j] = ' '; - - 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 < line_width; j++) - lines[i][j] = ' '; - lines[i][line_width] = '\0'; - } - - clearline = AllocatePool((x_max+1) * sizeof(CHAR16)); - for (UINTN i = 0; i < x_max; i++) - clearline[i] = ' '; - clearline[x_max] = 0; - while (!exit) { UINT64 key; - if (refresh) { + if (new_mode) { + UINTN line_width = 0, entry_padding = 3; + + console_query_mode(&x_max, &y_max); + + /* account for padding+status */ + visible_max = y_max - 2; + + /* 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); + } + + /* menu entries title lines */ + lines = AllocatePool(sizeof(CHAR16 *) * config->entry_count); for (UINTN i = 0; i < config->entry_count; i++) { - if (i < idx_first || i > idx_last) - continue; + UINTN j, padding; + + lines[i] = AllocatePool(((line_width + 1) * sizeof(CHAR16))); + padding = (line_width - MIN(StrLen(config->entries[i]->title_show), line_width)) / 2; + + for (j = 0; j < padding; j++) + lines[i][j] = ' '; + + 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 < line_width; j++) + lines[i][j] = ' '; + lines[i][line_width] = '\0'; + } + + 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 = 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]); @@ -649,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); } @@ -671,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; @@ -733,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'): @@ -813,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'): @@ -827,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: @@ -860,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; } @@ -1027,17 +1064,16 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) { if (strcmpa((CHAR8 *)"console-mode", key) == 0) { if (strcmpa((CHAR8 *)"auto", value) == 0) - config->console_mode_change = CONSOLE_MODE_AUTO; + config->console_mode = CONSOLE_MODE_AUTO; else if (strcmpa((CHAR8 *)"max", value) == 0) - config->console_mode_change = CONSOLE_MODE_MAX; + config->console_mode = CONSOLE_MODE_FIRMWARE_MAX; else if (strcmpa((CHAR8 *)"keep", value) == 0) - config->console_mode_change = CONSOLE_MODE_KEEP; + config->console_mode = CONSOLE_MODE_KEEP; else { _cleanup_freepool_ CHAR16 *s = NULL; s = stra_to_str(value); - config->console_mode = Atoi(s); - config->console_mode_change = CONSOLE_MODE_SET; + config->console_mode = MIN(Atoi(s), (UINTN)CONSOLE_MODE_RANGE_MAX); } continue; @@ -1410,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); @@ -1421,27 +1457,33 @@ static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) { .auto_firmware = TRUE, .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 12d3047ac5..b581c21bda 100644 --- a/src/boot/efi/console.c +++ b/src/boot/efi/console.c @@ -8,6 +8,9 @@ #define SYSTEM_FONT_WIDTH 8 #define SYSTEM_FONT_HEIGHT 19 +#define HORIZONTAL_MAX_OK 1920 +#define VERTICAL_MAX_OK 1080 +#define VIEWPORT_RATIO 10 #define EFI_SIMPLE_TEXT_INPUT_EX_GUID \ &(const EFI_GUID) EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID @@ -115,51 +118,36 @@ EFI_STATUS console_key_read(UINT64 *key, UINT64 timeout_usec) { return EFI_SUCCESS; } -static EFI_STATUS change_mode(UINTN mode) { +static EFI_STATUS change_mode(INT64 mode) { EFI_STATUS err; + INT32 old_mode; + + /* SetMode expects a UINTN, so make sure these values are sane. */ + mode = CLAMP(mode, CONSOLE_MODE_RANGE_MIN, CONSOLE_MODE_RANGE_MAX); + old_mode = MAX(CONSOLE_MODE_RANGE_MIN, ST->ConOut->Mode->Mode); err = uefi_call_wrapper(ST->ConOut->SetMode, 2, ST->ConOut, mode); + if (!EFI_ERROR(err)) + return EFI_SUCCESS; - /* Special case mode 1: when using OVMF and qemu, setting it returns error - * and breaks console output. */ - if (EFI_ERROR(err) && mode == 1) - uefi_call_wrapper(ST->ConOut->SetMode, 2, ST->ConOut, (UINTN)0); + /* Something went wrong. Output is probably borked, so try to revert to previous mode. */ + if (!EFI_ERROR(uefi_call_wrapper(ST->ConOut->SetMode, 2, ST->ConOut, old_mode))) + return err; + /* Maybe the device is on fire? */ + uefi_call_wrapper(ST->ConOut->Reset, 2, ST->ConOut, TRUE); + uefi_call_wrapper(ST->ConOut->SetMode, 2, ST->ConOut, CONSOLE_MODE_RANGE_MIN); return err; } -static UINT64 text_area_from_font_size(void) { - EFI_STATUS err; - UINT64 text_area; - UINTN rows, columns; - - err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &columns, &rows); - if (EFI_ERROR(err)) { - columns = 80; - rows = 25; - } - - text_area = SYSTEM_FONT_WIDTH * SYSTEM_FONT_HEIGHT * (UINT64)rows * (UINT64)columns; - - return text_area; -} - -static EFI_STATUS mode_auto(UINTN *mode) { - const UINT32 HORIZONTAL_MAX_OK = 1920; - const UINT32 VERTICAL_MAX_OK = 1080; - const UINT64 VIEWPORT_RATIO = 10; - UINT64 screen_area, text_area; - static const EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; +static INT64 get_auto_mode(void) { EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput; - EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; EFI_STATUS err; - BOOLEAN keep = FALSE; - assert(mode); - - err = LibLocateProtocol((EFI_GUID*) &GraphicsOutputProtocolGuid, (VOID **)&GraphicsOutput); + err = LibLocateProtocol(&GraphicsOutputProtocol, (VOID **)&GraphicsOutput); if (!EFI_ERROR(err) && GraphicsOutput->Mode && GraphicsOutput->Mode->Info) { - Info = GraphicsOutput->Mode->Info; + EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info = GraphicsOutput->Mode->Info; + BOOLEAN keep = FALSE; /* Start verifying if we are in a resolution larger than Full HD * (1920x1080). If we're not, assume we're in a good mode and do not @@ -170,19 +158,19 @@ static EFI_STATUS mode_auto(UINTN *mode) { * area to the text viewport area. If it's less than 10 times bigger, * then assume the text is readable and keep the text mode. */ else { - screen_area = (UINT64)Info->HorizontalResolution * (UINT64)Info->VerticalResolution; - text_area = text_area_from_font_size(); + UINT64 text_area; + UINTN x_max, y_max; + UINT64 screen_area = (UINT64)Info->HorizontalResolution * (UINT64)Info->VerticalResolution; + + console_query_mode(&x_max, &y_max); + text_area = SYSTEM_FONT_WIDTH * SYSTEM_FONT_HEIGHT * (UINT64)x_max * (UINT64)y_max; if (text_area != 0 && screen_area/text_area < VIEWPORT_RATIO) keep = TRUE; } - } - if (keep) { - /* Just clear the screen instead of changing the mode and return. */ - *mode = ST->ConOut->Mode->Mode; - uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); - return EFI_SUCCESS; + if (keep) + return ST->ConOut->Mode->Mode; } /* If we reached here, then we have a high resolution screen and the text @@ -191,32 +179,72 @@ static EFI_STATUS mode_auto(UINTN *mode) { * standard mode, which is provided by the device manufacturer, so it should * be a good mode. * Note: MaxMode is the number of modes, not the last mode. */ - if (ST->ConOut->Mode->MaxMode > 2) - *mode = 2; + if (ST->ConOut->Mode->MaxMode > CONSOLE_MODE_FIRMWARE_FIRST) + return CONSOLE_MODE_FIRMWARE_FIRST; + /* Try again with mode different than zero (assume user requests * auto mode due to some problem with mode zero). */ - else if (ST->ConOut->Mode->MaxMode == 2) - *mode = 1; - /* Else force mode change to zero. */ - else - *mode = 0; + if (ST->ConOut->Mode->MaxMode > CONSOLE_MODE_80_50) + return CONSOLE_MODE_80_50; - return change_mode(*mode); + return CONSOLE_MODE_80_25; } -EFI_STATUS console_set_mode(UINTN *mode, enum console_mode_change_type how) { - assert(mode); +EFI_STATUS console_set_mode(INT64 mode) { + switch (mode) { + case CONSOLE_MODE_KEEP: + /* If the firmware indicates the current mode is invalid, change it anyway. */ + if (ST->ConOut->Mode->Mode < CONSOLE_MODE_RANGE_MIN) + return change_mode(CONSOLE_MODE_RANGE_MIN); + return EFI_SUCCESS; - if (how == CONSOLE_MODE_AUTO) - return mode_auto(mode); + case CONSOLE_MODE_NEXT: + if (ST->ConOut->Mode->MaxMode <= CONSOLE_MODE_RANGE_MIN) + return EFI_UNSUPPORTED; - if (how == CONSOLE_MODE_MAX) { + 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()); + + case CONSOLE_MODE_FIRMWARE_MAX: /* Note: MaxMode is the number of modes, not the last mode. */ - if (ST->ConOut->Mode->MaxMode > 0) - *mode = ST->ConOut->Mode->MaxMode-1; - else - *mode = 0; + return change_mode(ST->ConOut->Mode->MaxMode - 1LL); + + default: + return change_mode(mode); + } +} + +EFI_STATUS console_query_mode(UINTN *x_max, UINTN *y_max) { + EFI_STATUS err; + + assert(x_max); + assert(y_max); + + err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, x_max, y_max); + if (EFI_ERROR(err)) { + /* Fallback values mandated by UEFI spec. */ + switch (ST->ConOut->Mode->Mode) { + case CONSOLE_MODE_80_50: + *x_max = 80; + *y_max = 50; + break; + case CONSOLE_MODE_80_25: + default: + *x_max = 80; + *y_max = 25; + } } - return change_mode(*mode); + return err; } diff --git a/src/boot/efi/console.h b/src/boot/efi/console.h index 23848a9c58..90086028c0 100644 --- a/src/boot/efi/console.h +++ b/src/boot/efi/console.h @@ -9,12 +9,23 @@ #define KEYCHAR(k) ((k) & 0xffff) #define CHAR_CTRL(c) ((c) - 'a' + 1) -enum console_mode_change_type { - CONSOLE_MODE_KEEP = 0, - CONSOLE_MODE_SET, +enum { + /* Console mode is a INT32 in EFI. We use INT64 to make room for our special values. */ + CONSOLE_MODE_RANGE_MIN = 0, + CONSOLE_MODE_RANGE_MAX = INT32_MAX, /* This is just the theoretical limit. */ + CONSOLE_MODE_INVALID = -1, /* UEFI uses -1 if the device is not in a valid text mode. */ + + CONSOLE_MODE_80_25 = 0, /* 80x25 is required by UEFI spec. */ + CONSOLE_MODE_80_50 = 1, /* 80x50 may be supported. */ + CONSOLE_MODE_FIRMWARE_FIRST = 2, /* First custom mode, if supported. */ + + /* 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_MAX, + CONSOLE_MODE_FIRMWARE_MAX, /* 'max' in config. */ }; EFI_STATUS console_key_read(UINT64 *key, UINT64 timeout_usec); -EFI_STATUS console_set_mode(UINTN *mode, enum console_mode_change_type how); +EFI_STATUS console_set_mode(INT64 mode); +EFI_STATUS console_query_mode(UINTN *x_max, UINTN *y_max); 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);