228 lines
8.2 KiB
C
228 lines
8.2 KiB
C
/* SPDX-License-Identifier: LGPL-2.1+ */
|
|
|
|
#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);
|
|
}
|