From 68d4b8ac9b9673637fa198b735f6e64b78b35d3b Mon Sep 17 00:00:00 2001 From: Henrique Dante de Almeida Date: Sat, 16 Dec 2017 16:23:06 -0200 Subject: [PATCH 1/3] boot/efi: add console-mode boot option to change resolution The new boot option allows changing the display console mode to some new display resolution. Usage: in systemd-boot configuration file loader/loader.conf add one of: console-mode console-mode auto console-mode max console-mode keep Where number can be: 1) 0: UEFI standard 80x25 mode 2) 1: 80x50 mode, if supported by device 3) 2 or greater: some non-standard device mode, if supported When using the auto mode, systemd-boot will try to find a reasonable mode automatically using some heuristic. Current implementation tries to switch console mode to mode 2 if it exists, mode 1 if it exists, else mode 0. Mode 2 is the first non standard mode provided by the firmware developer and it's assumed to be a reasonable mode. The max mode adds support for switching to the highest numbered console mode, whatever mode that would be in the system. The maximum mode being the last one implemented by the firmware provider should be a reasonable mode. The keep mode is the default and is only included for completeness. With this parameter, the user explicitly states and can be assured that systemd-boot will not change console mode. Note: patch includes a workaround for mode 1, that may not exist in EDK2 OVFM and results in a buggy text mode when requested. --- src/boot/efi/boot.c | 36 ++++++++++++++++++++++++++++++++- src/boot/efi/console.c | 45 ++++++++++++++++++++++++++++++++++++++++++ src/boot/efi/console.h | 8 ++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 06331da2d4..107e2451fc 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -69,6 +69,8 @@ typedef struct { CHAR16 *entry_oneshot; CHAR16 *options_edit; BOOLEAN no_editor; + UINTN console_mode; + enum console_mode_change_type console_mode_change; } Config; static VOID cursor_left(UINTN *cursor, UINTN *first) { @@ -491,6 +493,7 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load BOOLEAN exit = FALSE; BOOLEAN run = TRUE; BOOLEAN wait = FALSE; + BOOLEAN cleared_screen = FALSE; graphics_mode(FALSE); uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE); @@ -499,7 +502,18 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load /* draw a single character to make ClearScreen work on some firmware */ uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L" "); - uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + if (config->console_mode_change != CONSOLE_MODE_KEEP) { + err = console_set_mode(&config->console_mode, config->console_mode_change); + if (!EFI_ERROR(err)) + cleared_screen = TRUE; + } + + if (!cleared_screen) + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + if (config->console_mode_change != CONSOLE_MODE_KEEP && EFI_ERROR(err)) + Print(L"Error switching console mode to %ld: %r.\r", (UINT64)config->console_mode, err); err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x_max, &y_max); if (EFI_ERROR(err)) { @@ -1008,6 +1022,26 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) { continue; config->no_editor = !on; } + + if (strcmpa((CHAR8 *)"console-mode", key) == 0) { + CHAR16 *s; + + if (strcmpa((CHAR8 *)"auto", value) == 0) + config->console_mode_change = CONSOLE_MODE_AUTO; + else if (strcmpa((CHAR8 *)"max", value) == 0) + config->console_mode_change = CONSOLE_MODE_MAX; + else if (strcmpa((CHAR8 *)"keep", value) == 0) + config->console_mode_change = CONSOLE_MODE_KEEP; + else { + s = stra_to_str(value); + config->console_mode = Atoi(s); + config->console_mode_change = CONSOLE_MODE_SET; + FreePool(s); + } + + continue; + } + } } diff --git a/src/boot/efi/console.c b/src/boot/efi/console.c index 4487ed099f..241167b9f8 100644 --- a/src/boot/efi/console.c +++ b/src/boot/efi/console.c @@ -134,3 +134,48 @@ EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait) { *key = KEYPRESS(0, k.ScanCode, k.UnicodeChar); return 0; } + +static EFI_STATUS change_mode(UINTN mode) { + EFI_STATUS err; + + err = uefi_call_wrapper(ST->ConOut->SetMode, 2, ST->ConOut, mode); + + /* 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); + + return err; +} + +static EFI_STATUS mode_auto(UINTN *mode) { + /* Mode number 2 is first non 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; + /* 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; + + return change_mode(*mode); +} + +EFI_STATUS console_set_mode(UINTN *mode, enum console_mode_change_type how) { + if (how == CONSOLE_MODE_AUTO) + return mode_auto(mode); + + if (how == CONSOLE_MODE_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(*mode); +} diff --git a/src/boot/efi/console.h b/src/boot/efi/console.h index 5664a4e62f..6296d467c4 100644 --- a/src/boot/efi/console.h +++ b/src/boot/efi/console.h @@ -29,5 +29,13 @@ #define KEYCHAR(k) ((k) & 0xffff) #define CHAR_CTRL(c) ((c) - 'a' + 1) +enum console_mode_change_type { + CONSOLE_MODE_KEEP = 0, + CONSOLE_MODE_SET, + CONSOLE_MODE_AUTO, + CONSOLE_MODE_MAX, +}; + EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait); +EFI_STATUS console_set_mode(UINTN *mode, enum console_mode_change_type how); #endif From 26e51d87f7b9c50cc215dc9ccc78fa99f5225a82 Mon Sep 17 00:00:00 2001 From: Henrique Dante de Almeida Date: Sat, 16 Dec 2017 21:12:53 -0200 Subject: [PATCH 2/3] boot/efi: improve automatic mode handling This patch improves the automatic console mode switching by avoiding changing the mode if it can verify that the text is readable in the default mode. The implementation avoids changing mode if we're using a low resolution device (fixed to be smaller than 1920x1080) because we assume the text is readable, so the initial mode should be a good one. Also, the mode is not changed when in high resolution mode if the text viewport area is larger than 10% of the screen. The conclusion is that only if the text viewport is calculated to occupy less than 10% of the screen area it is assumed to be hard to read and a new mode is selected. With this patch the auto mode will typically avoid changing mode hopefully in most of the cases, only changing it if completely necessary. --- src/boot/efi/console.c | 64 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/src/boot/efi/console.c b/src/boot/efi/console.c index 241167b9f8..94551a89e1 100644 --- a/src/boot/efi/console.c +++ b/src/boot/efi/console.c @@ -20,6 +20,9 @@ #include "console.h" #include "util.h" +#define SYSTEM_FONT_WIDTH 8 +#define SYSTEM_FONT_HEIGHT 19 + #define EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID \ { 0xdd9e7534, 0x7762, 0x4698, { 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa } } @@ -148,9 +151,66 @@ static EFI_STATUS change_mode(UINTN mode) { 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) { - /* Mode number 2 is first non standard mode, which is provided by - * the device manufacturer, so it should be a good mode. + const UINT32 HORIZONTAL_MAX_OK = 1920; + const UINT32 VERTICAL_MAX_OK = 1080; + const UINT64 VIEWPORT_RATIO = 10; + UINT64 screen_area, text_area; + EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput; + EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; + EFI_STATUS err; + BOOLEAN keep = FALSE; + + err = LibLocateProtocol(&GraphicsOutputProtocolGuid, (VOID **)&GraphicsOutput); + if (!EFI_ERROR(err) && GraphicsOutput->Mode && GraphicsOutput->Mode->Info) { + Info = GraphicsOutput->Mode->Info; + + /* 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 + * try to change it. */ + if (Info->HorizontalResolution <= HORIZONTAL_MAX_OK && Info->VerticalResolution <= VERTICAL_MAX_OK) + keep = TRUE; + /* For larger resolutions, calculate the ratio of the total screen + * 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(); + + 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 we reached here, then we have a high resolution screen and the text + * viewport is less than 10% the screen area, so the firmware developer + * screwed up. Try to switch to a better mode. Mode number 2 is first non + * 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; From d37b0737e14ca3539e6962c48953df135533cea6 Mon Sep 17 00:00:00 2001 From: Henrique Dante de Almeida Date: Sat, 16 Dec 2017 22:52:05 -0200 Subject: [PATCH 3/3] bootctl: synchronize bootctl code with sd-boot code This patch adds a line with "#console-mode keep" as a documentation for the console mode feature and duplicates console-mode parsing to bootctl. --- src/boot/bootctl.c | 1 + src/shared/bootspec.c | 2 ++ src/shared/bootspec.h | 1 + 3 files changed, 4 insertions(+) diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index ae034f5cdb..a0453e077d 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -826,6 +826,7 @@ static int install_loader_config(const char *esp_path) { } fprintf(f, "#timeout 3\n"); + fprintf(f, "#console-mode keep\n"); fprintf(f, "default %s-*\n", sd_id128_to_string(machine_id, machine_string)); r = fflush_sync_and_check(f); diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 9c3bdd47de..c0214b333e 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -194,6 +194,8 @@ int boot_loader_read_conf(const char *path, BootConfig *config) { r = free_and_strdup(&config->timeout, p); else if (streq(buf, "editor")) r = free_and_strdup(&config->editor, p); + else if (streq(buf, "console-mode")) + r = free_and_strdup(&config->console_mode, p); else { log_notice("%s:%u: Unknown line \"%s\"", path, line, buf); continue; diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index d9c641bf08..820688289a 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -40,6 +40,7 @@ typedef struct BootConfig { char *default_pattern; char *timeout; char *editor; + char *console_mode; char *entry_oneshot; char *entry_default;