1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
9 #define SYSTEM_FONT_WIDTH 8
10 #define SYSTEM_FONT_HEIGHT 19
12 #define EFI_SIMPLE_TEXT_INPUT_EX_GUID &(EFI_GUID) EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID
14 static inline void EventClosep(EFI_EVENT
*event
) {
18 uefi_call_wrapper(BS
->CloseEvent
, 1, *event
);
22 * Reading input from the console sounds like an easy task to do, but thanks to broken
23 * firmware it is actually a nightmare.
25 * There is a ConIn and TextInputEx API for this. Ideally we want to use TextInputEx,
26 * because that gives us Ctrl/Alt/Shift key state information. Unfortunately, it is not
27 * always available and sometimes just non-functional.
29 * On the other hand we have ConIn, where some firmware likes to just freeze on us
30 * if we call ReadKeyStroke on it.
32 * Therefore, we use WaitForEvent on both ConIn and TextInputEx (if available) along
33 * with a timer event. The timer ensures there is no need to call into functions
34 * that might freeze on us, while still allowing us to show a timeout counter.
36 EFI_STATUS
console_key_read(UINT64
*key
, UINT64 timeout_usec
) {
37 static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
*TextInputEx
;
38 static BOOLEAN checked
;
42 _cleanup_(EventClosep
) EFI_EVENT timer
= NULL
;
43 EFI_EVENT events
[3] = { ST
->ConIn
->WaitForKey
};
49 err
= LibLocateProtocol(EFI_SIMPLE_TEXT_INPUT_EX_GUID
, (VOID
**)&TextInputEx
);
51 uefi_call_wrapper(BS
->CheckEvent
, 1, TextInputEx
->WaitForKeyEx
) == EFI_INVALID_PARAMETER
)
52 /* If WaitForKeyEx fails here, the firmware pretends it talks this
53 * protocol, but it really doesn't. */
56 events
[n_events
++] = TextInputEx
->WaitForKeyEx
;
61 if (timeout_usec
> 0) {
62 err
= uefi_call_wrapper(BS
->CreateEvent
, 5, EVT_TIMER
, 0, NULL
, NULL
, &timer
);
64 return log_error_status_stall(err
, L
"Error creating timer event: %r", err
);
66 /* SetTimer expects 100ns units for some reason. */
67 err
= uefi_call_wrapper(BS
->SetTimer
, 3, timer
, TimerRelative
, timeout_usec
* 10);
69 return log_error_status_stall(err
, L
"Error arming timer event: %r", err
);
71 events
[n_events
++] = timer
;
74 err
= uefi_call_wrapper(BS
->WaitForEvent
, 3, n_events
, events
, &index
);
76 return log_error_status_stall(err
, L
"Error waiting for events: %r", err
);
78 if (timeout_usec
> 0 && timer
== events
[index
])
81 /* TextInputEx might be ready too even if ConIn got to signal first. */
82 if (TextInputEx
&& !EFI_ERROR(uefi_call_wrapper(BS
->CheckEvent
, 1, TextInputEx
->WaitForKeyEx
))) {
87 err
= uefi_call_wrapper(TextInputEx
->ReadKeyStrokeEx
, 2, TextInputEx
, &keydata
);
91 /* do not distinguish between left and right keys */
92 if (keydata
.KeyState
.KeyShiftState
& EFI_SHIFT_STATE_VALID
) {
93 if (keydata
.KeyState
.KeyShiftState
& (EFI_RIGHT_CONTROL_PRESSED
|EFI_LEFT_CONTROL_PRESSED
))
94 shift
|= EFI_CONTROL_PRESSED
;
95 if (keydata
.KeyState
.KeyShiftState
& (EFI_RIGHT_ALT_PRESSED
|EFI_LEFT_ALT_PRESSED
))
96 shift
|= EFI_ALT_PRESSED
;
99 /* 32 bit modifier keys + 16 bit scan code + 16 bit unicode */
100 keypress
= KEYPRESS(shift
, keydata
.Key
.ScanCode
, keydata
.Key
.UnicodeChar
);
106 return EFI_NOT_READY
;
109 err
= uefi_call_wrapper(ST
->ConIn
->ReadKeyStroke
, 2, ST
->ConIn
, &k
);
113 *key
= KEYPRESS(0, k
.ScanCode
, k
.UnicodeChar
);
117 static EFI_STATUS
change_mode(UINTN mode
) {
120 err
= uefi_call_wrapper(ST
->ConOut
->SetMode
, 2, ST
->ConOut
, mode
);
122 /* Special case mode 1: when using OVMF and qemu, setting it returns error
123 * and breaks console output. */
124 if (EFI_ERROR(err
) && mode
== 1)
125 uefi_call_wrapper(ST
->ConOut
->SetMode
, 2, ST
->ConOut
, (UINTN
)0);
130 static UINT64
text_area_from_font_size(void) {
135 err
= uefi_call_wrapper(ST
->ConOut
->QueryMode
, 4, ST
->ConOut
, ST
->ConOut
->Mode
->Mode
, &columns
, &rows
);
136 if (EFI_ERROR(err
)) {
141 text_area
= SYSTEM_FONT_WIDTH
* SYSTEM_FONT_HEIGHT
* (UINT64
)rows
* (UINT64
)columns
;
146 static EFI_STATUS
mode_auto(UINTN
*mode
) {
147 const UINT32 HORIZONTAL_MAX_OK
= 1920;
148 const UINT32 VERTICAL_MAX_OK
= 1080;
149 const UINT64 VIEWPORT_RATIO
= 10;
150 UINT64 screen_area
, text_area
;
151 EFI_GUID GraphicsOutputProtocolGuid
= EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID
;
152 EFI_GRAPHICS_OUTPUT_PROTOCOL
*GraphicsOutput
;
153 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
*Info
;
155 BOOLEAN keep
= FALSE
;
159 err
= LibLocateProtocol(&GraphicsOutputProtocolGuid
, (VOID
**)&GraphicsOutput
);
160 if (!EFI_ERROR(err
) && GraphicsOutput
->Mode
&& GraphicsOutput
->Mode
->Info
) {
161 Info
= GraphicsOutput
->Mode
->Info
;
163 /* Start verifying if we are in a resolution larger than Full HD
164 * (1920x1080). If we're not, assume we're in a good mode and do not
165 * try to change it. */
166 if (Info
->HorizontalResolution
<= HORIZONTAL_MAX_OK
&& Info
->VerticalResolution
<= VERTICAL_MAX_OK
)
168 /* For larger resolutions, calculate the ratio of the total screen
169 * area to the text viewport area. If it's less than 10 times bigger,
170 * then assume the text is readable and keep the text mode. */
172 screen_area
= (UINT64
)Info
->HorizontalResolution
* (UINT64
)Info
->VerticalResolution
;
173 text_area
= text_area_from_font_size();
175 if (text_area
!= 0 && screen_area
/text_area
< VIEWPORT_RATIO
)
181 /* Just clear the screen instead of changing the mode and return. */
182 *mode
= ST
->ConOut
->Mode
->Mode
;
183 uefi_call_wrapper(ST
->ConOut
->ClearScreen
, 1, ST
->ConOut
);
187 /* If we reached here, then we have a high resolution screen and the text
188 * viewport is less than 10% the screen area, so the firmware developer
189 * screwed up. Try to switch to a better mode. Mode number 2 is first non
190 * standard mode, which is provided by the device manufacturer, so it should
192 * Note: MaxMode is the number of modes, not the last mode. */
193 if (ST
->ConOut
->Mode
->MaxMode
> 2)
195 /* Try again with mode different than zero (assume user requests
196 * auto mode due to some problem with mode zero). */
197 else if (ST
->ConOut
->Mode
->MaxMode
== 2)
199 /* Else force mode change to zero. */
203 return change_mode(*mode
);
206 EFI_STATUS
console_set_mode(UINTN
*mode
, enum console_mode_change_type how
) {
209 if (how
== CONSOLE_MODE_AUTO
)
210 return mode_auto(mode
);
212 if (how
== CONSOLE_MODE_MAX
) {
213 /* Note: MaxMode is the number of modes, not the last mode. */
214 if (ST
->ConOut
->Mode
->MaxMode
> 0)
215 *mode
= ST
->ConOut
->Mode
->MaxMode
-1;
220 return change_mode(*mode
);