/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #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 } } struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; typedef EFI_STATUS (EFIAPI *EFI_INPUT_RESET_EX)( struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, BOOLEAN ExtendedVerification ); typedef UINT8 EFI_KEY_TOGGLE_STATE; typedef struct { UINT32 KeyShiftState; EFI_KEY_TOGGLE_STATE KeyToggleState; } EFI_KEY_STATE; typedef struct { EFI_INPUT_KEY Key; EFI_KEY_STATE KeyState; } EFI_KEY_DATA; typedef EFI_STATUS (EFIAPI *EFI_INPUT_READ_KEY_EX)( struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, EFI_KEY_DATA *KeyData ); typedef EFI_STATUS (EFIAPI *EFI_SET_STATE)( struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, EFI_KEY_TOGGLE_STATE *KeyToggleState ); typedef EFI_STATUS (EFIAPI *EFI_KEY_NOTIFY_FUNCTION)( EFI_KEY_DATA *KeyData ); typedef EFI_STATUS (EFIAPI *EFI_REGISTER_KEYSTROKE_NOTIFY)( struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, EFI_KEY_DATA KeyData, EFI_KEY_NOTIFY_FUNCTION KeyNotificationFunction, VOID **NotifyHandle ); typedef EFI_STATUS (EFIAPI *EFI_UNREGISTER_KEYSTROKE_NOTIFY)( struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, VOID *NotificationHandle ); typedef struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL { EFI_INPUT_RESET_EX Reset; EFI_INPUT_READ_KEY_EX ReadKeyStrokeEx; EFI_EVENT WaitForKeyEx; EFI_SET_STATE SetState; EFI_REGISTER_KEYSTROKE_NOTIFY RegisterKeyNotify; EFI_UNREGISTER_KEYSTROKE_NOTIFY UnregisterKeyNotify; } EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait) { EFI_GUID EfiSimpleTextInputExProtocolGuid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID; static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *TextInputEx; static BOOLEAN checked; UINTN index; EFI_INPUT_KEY k; EFI_STATUS err; if (!checked) { err = LibLocateProtocol(&EfiSimpleTextInputExProtocolGuid, (VOID **)&TextInputEx); if (EFI_ERROR(err)) TextInputEx = NULL; checked = TRUE; } /* wait until key is pressed */ if (wait) uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index); if (TextInputEx) { EFI_KEY_DATA keydata; UINT64 keypress; err = uefi_call_wrapper(TextInputEx->ReadKeyStrokeEx, 2, TextInputEx, &keydata); if (!EFI_ERROR(err)) { UINT32 shift = 0; /* do not distinguish between left and right keys */ if (keydata.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) { if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED)) shift |= EFI_CONTROL_PRESSED; if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED)) shift |= EFI_ALT_PRESSED; }; /* 32 bit modifier keys + 16 bit scan code + 16 bit unicode */ keypress = KEYPRESS(shift, keydata.Key.ScanCode, keydata.Key.UnicodeChar); if (keypress > 0) { *key = keypress; return 0; } } } /* fallback for firmware which does not support SimpleTextInputExProtocol * * This is also called in case ReadKeyStrokeEx did not return a key, because * some broken firmwares offer SimpleTextInputExProtocol, but never actually * handle any key. */ err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &k); if (EFI_ERROR(err)) return err; *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 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; 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; /* 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); }