| /* SPDX-License-Identifier: LGPL-2.1+ */ |
| /* |
| * Copyright © 2012 Harald Hoyer <harald@redhat.com> |
| */ |
| |
| #include <efi.h> |
| #include <efilib.h> |
| |
| #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 acually |
| * 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); |
| } |