1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU Lesser General Public License as published by
5 * the Free Software Foundation; either version 2.1 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
13 * Copyright (C) 2012-2013 Kay Sievers <kay@vrfy.org>
14 * Copyright (C) 2012 Harald Hoyer <harald@redhat.com>
23 #define SYSTEM_FONT_WIDTH 8
24 #define SYSTEM_FONT_HEIGHT 19
26 #define EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID \
27 { 0xdd9e7534, 0x7762, 0x4698, { 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa } }
29 struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
;
31 typedef EFI_STATUS (EFIAPI
*EFI_INPUT_RESET_EX
)(
32 struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
*This
,
33 BOOLEAN ExtendedVerification
36 typedef UINT8 EFI_KEY_TOGGLE_STATE
;
40 EFI_KEY_TOGGLE_STATE KeyToggleState
;
45 EFI_KEY_STATE KeyState
;
48 typedef EFI_STATUS (EFIAPI
*EFI_INPUT_READ_KEY_EX
)(
49 struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
*This
,
53 typedef EFI_STATUS (EFIAPI
*EFI_SET_STATE
)(
54 struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
*This
,
55 EFI_KEY_TOGGLE_STATE
*KeyToggleState
58 typedef EFI_STATUS (EFIAPI
*EFI_KEY_NOTIFY_FUNCTION
)(
62 typedef EFI_STATUS (EFIAPI
*EFI_REGISTER_KEYSTROKE_NOTIFY
)(
63 struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
*This
,
65 EFI_KEY_NOTIFY_FUNCTION KeyNotificationFunction
,
69 typedef EFI_STATUS (EFIAPI
*EFI_UNREGISTER_KEYSTROKE_NOTIFY
)(
70 struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
*This
,
71 VOID
*NotificationHandle
74 typedef struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
{
75 EFI_INPUT_RESET_EX Reset
;
76 EFI_INPUT_READ_KEY_EX ReadKeyStrokeEx
;
77 EFI_EVENT WaitForKeyEx
;
78 EFI_SET_STATE SetState
;
79 EFI_REGISTER_KEYSTROKE_NOTIFY RegisterKeyNotify
;
80 EFI_UNREGISTER_KEYSTROKE_NOTIFY UnregisterKeyNotify
;
81 } EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
;
83 EFI_STATUS
console_key_read(UINT64
*key
, BOOLEAN wait
) {
84 EFI_GUID EfiSimpleTextInputExProtocolGuid
= EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID
;
85 static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
*TextInputEx
;
86 static BOOLEAN checked
;
92 err
= LibLocateProtocol(&EfiSimpleTextInputExProtocolGuid
, (VOID
**)&TextInputEx
);
99 /* wait until key is pressed */
101 uefi_call_wrapper(BS
->WaitForEvent
, 3, 1, &ST
->ConIn
->WaitForKey
, &index
);
104 EFI_KEY_DATA keydata
;
107 err
= uefi_call_wrapper(TextInputEx
->ReadKeyStrokeEx
, 2, TextInputEx
, &keydata
);
108 if (!EFI_ERROR(err
)) {
111 /* do not distinguish between left and right keys */
112 if (keydata
.KeyState
.KeyShiftState
& EFI_SHIFT_STATE_VALID
) {
113 if (keydata
.KeyState
.KeyShiftState
& (EFI_RIGHT_CONTROL_PRESSED
|EFI_LEFT_CONTROL_PRESSED
))
114 shift
|= EFI_CONTROL_PRESSED
;
115 if (keydata
.KeyState
.KeyShiftState
& (EFI_RIGHT_ALT_PRESSED
|EFI_LEFT_ALT_PRESSED
))
116 shift
|= EFI_ALT_PRESSED
;
119 /* 32 bit modifier keys + 16 bit scan code + 16 bit unicode */
120 keypress
= KEYPRESS(shift
, keydata
.Key
.ScanCode
, keydata
.Key
.UnicodeChar
);
128 /* fallback for firmware which does not support SimpleTextInputExProtocol
130 * This is also called in case ReadKeyStrokeEx did not return a key, because
131 * some broken firmwares offer SimpleTextInputExProtocol, but never acually
133 err
= uefi_call_wrapper(ST
->ConIn
->ReadKeyStroke
, 2, ST
->ConIn
, &k
);
137 *key
= KEYPRESS(0, k
.ScanCode
, k
.UnicodeChar
);
141 static EFI_STATUS
change_mode(UINTN mode
) {
144 err
= uefi_call_wrapper(ST
->ConOut
->SetMode
, 2, ST
->ConOut
, mode
);
146 /* Special case mode 1: when using OVMF and qemu, setting it returns error
147 * and breaks console output. */
148 if (EFI_ERROR(err
) && mode
== 1)
149 uefi_call_wrapper(ST
->ConOut
->SetMode
, 2, ST
->ConOut
, (UINTN
)0);
154 static UINT64
text_area_from_font_size(void) {
159 err
= uefi_call_wrapper(ST
->ConOut
->QueryMode
, 4, ST
->ConOut
, ST
->ConOut
->Mode
->Mode
, &columns
, &rows
);
160 if (EFI_ERROR(err
)) {
165 text_area
= SYSTEM_FONT_WIDTH
* SYSTEM_FONT_HEIGHT
* (UINT64
)rows
* (UINT64
)columns
;
170 static EFI_STATUS
mode_auto(UINTN
*mode
) {
171 const UINT32 HORIZONTAL_MAX_OK
= 1920;
172 const UINT32 VERTICAL_MAX_OK
= 1080;
173 const UINT64 VIEWPORT_RATIO
= 10;
174 UINT64 screen_area
, text_area
;
175 EFI_GUID GraphicsOutputProtocolGuid
= EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID
;
176 EFI_GRAPHICS_OUTPUT_PROTOCOL
*GraphicsOutput
;
177 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
*Info
;
179 BOOLEAN keep
= FALSE
;
181 err
= LibLocateProtocol(&GraphicsOutputProtocolGuid
, (VOID
**)&GraphicsOutput
);
182 if (!EFI_ERROR(err
) && GraphicsOutput
->Mode
&& GraphicsOutput
->Mode
->Info
) {
183 Info
= GraphicsOutput
->Mode
->Info
;
185 /* Start verifying if we are in a resolution larger than Full HD
186 * (1920x1080). If we're not, assume we're in a good mode and do not
187 * try to change it. */
188 if (Info
->HorizontalResolution
<= HORIZONTAL_MAX_OK
&& Info
->VerticalResolution
<= VERTICAL_MAX_OK
)
190 /* For larger resolutions, calculate the ratio of the total screen
191 * area to the text viewport area. If it's less than 10 times bigger,
192 * then assume the text is readable and keep the text mode. */
194 screen_area
= (UINT64
)Info
->HorizontalResolution
* (UINT64
)Info
->VerticalResolution
;
195 text_area
= text_area_from_font_size();
197 if (text_area
!= 0 && screen_area
/text_area
< VIEWPORT_RATIO
)
203 /* Just clear the screen instead of changing the mode and return. */
204 *mode
= ST
->ConOut
->Mode
->Mode
;
205 uefi_call_wrapper(ST
->ConOut
->ClearScreen
, 1, ST
->ConOut
);
209 /* If we reached here, then we have a high resolution screen and the text
210 * viewport is less than 10% the screen area, so the firmware developer
211 * screwed up. Try to switch to a better mode. Mode number 2 is first non
212 * standard mode, which is provided by the device manufacturer, so it should
214 * Note: MaxMode is the number of modes, not the last mode. */
215 if (ST
->ConOut
->Mode
->MaxMode
> 2)
217 /* Try again with mode different than zero (assume user requests
218 * auto mode due to some problem with mode zero). */
219 else if (ST
->ConOut
->Mode
->MaxMode
== 2)
221 /* Else force mode change to zero. */
225 return change_mode(*mode
);
228 EFI_STATUS
console_set_mode(UINTN
*mode
, enum console_mode_change_type how
) {
229 if (how
== CONSOLE_MODE_AUTO
)
230 return mode_auto(mode
);
232 if (how
== CONSOLE_MODE_MAX
) {
233 /* Note: MaxMode is the number of modes, not the last mode. */
234 if (ST
->ConOut
->Mode
->MaxMode
> 0)
235 *mode
= ST
->ConOut
->Mode
->MaxMode
-1;
240 return change_mode(*mode
);