1From 2d9fcfcfa38667ada306e095599944f941576e53 Mon Sep 17 00:00:00 2001
2From: Jan Janssen <medhefgo@web.de>
3Date: Wed, 11 Aug 2021 14:59:46 +0200
4Subject: [PATCH 21/21] sd-boot: Rework console input handling
5
6Fixes: #15847
7Probably fixes: #19191
8
9(cherry picked from commit e98d271e57f3d0356e444b6ea2d48836ee2769b0)
10---
11 src/boot/efi/boot.c | 55 +++++++---------------
12 src/boot/efi/console.c | 102 +++++++++++++++++++++++++++++------------
13 src/boot/efi/console.h | 2 +-
14 3 files changed, 91 insertions(+), 68 deletions(-)
15
16diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c
17index 54d704f0d1..b4f3b9605a 100644
18--- a/src/boot/efi/boot.c
19+++ b/src/boot/efi/boot.c
20@@ -134,7 +134,7 @@ static BOOLEAN line_edit(
21 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, print);
22 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
23
24- err = console_key_read(&key, TRUE);
25+ err = console_key_read(&key, 0);
26 if (EFI_ERROR(err))
27 continue;
28
29@@ -387,7 +387,7 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
30 Print(L"OsIndicationsSupported: %d\n", indvar);
31
32 Print(L"\n--- press key ---\n\n");
33- console_key_read(&key, TRUE);
34+ console_key_read(&key, 0);
35
36 Print(L"timeout: %u\n", config->timeout_sec);
37 if (config->timeout_sec_efivar >= 0)
38@@ -432,7 +432,7 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
39 Print(L"LoaderEntryDefault: %s\n", defaultstr);
40
41 Print(L"\n--- press key ---\n\n");
42- console_key_read(&key, TRUE);
43+ console_key_read(&key, 0);
44
45 for (UINTN i = 0; i < config->entry_count; i++) {
46 ConfigEntry *entry;
47@@ -482,7 +482,7 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
48 entry->path, entry->next_name);
49
50 Print(L"\n--- press key ---\n\n");
51- console_key_read(&key, TRUE);
52+ console_key_read(&key, 0);
53 }
54
55 uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
56@@ -509,11 +509,10 @@ static BOOLEAN menu_run(
57 UINTN y_max;
58 CHAR16 *status;
59 CHAR16 *clearline;
60- INTN timeout_remain;
61+ UINTN timeout_remain = config->timeout_sec;
62 INT16 idx;
63 BOOLEAN exit = FALSE;
64 BOOLEAN run = TRUE;
65- BOOLEAN wait = FALSE;
66
67 graphics_mode(FALSE);
68 uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE);
69@@ -538,12 +537,6 @@ static BOOLEAN menu_run(
70 y_max = 25;
71 }
72
73- /* we check 10 times per second for a keystroke */
74- if (config->timeout_sec > 0)
75- timeout_remain = config->timeout_sec * 10;
76- else
77- timeout_remain = -1;
78-
79 idx_highlight = config->idx_default;
80 idx_highlight_prev = 0;
81
82@@ -643,7 +636,7 @@ static BOOLEAN menu_run(
83
84 if (timeout_remain > 0) {
85 FreePool(status);
86- status = PoolPrint(L"Boot in %d sec.", (timeout_remain + 5) / 10);
87+ status = PoolPrint(L"Boot in %d s.", timeout_remain);
88 }
89
90 /* print status at last line of screen */
91@@ -664,27 +657,18 @@ static BOOLEAN menu_run(
92 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + x + len);
93 }
94
95- err = console_key_read(&key, wait);
96- if (EFI_ERROR(err)) {
97- /* timeout reached */
98+ err = console_key_read(&key, timeout_remain > 0 ? 1000 * 1000 : 0);
99+ if (err == EFI_TIMEOUT) {
100+ timeout_remain--;
101 if (timeout_remain == 0) {
102 exit = TRUE;
103 break;
104 }
105
106- /* sleep and update status */
107- if (timeout_remain > 0) {
108- uefi_call_wrapper(BS->Stall, 1, 100 * 1000);
109- timeout_remain--;
110- continue;
111- }
112-
113- /* timeout disabled, wait for next key */
114- wait = TRUE;
115+ /* update status */
116 continue;
117- }
118-
119- timeout_remain = -1;
120+ } else
121+ timeout_remain = 0;
122
123 /* clear status after keystroke */
124 if (status) {
125@@ -787,7 +771,7 @@ static BOOLEAN menu_run(
126 config->timeout_sec_efivar,
127 EFI_VARIABLE_NON_VOLATILE);
128 if (config->timeout_sec_efivar > 0)
129- status = PoolPrint(L"Menu timeout set to %d sec.", config->timeout_sec_efivar);
130+ status = PoolPrint(L"Menu timeout set to %d s.", config->timeout_sec_efivar);
131 else
132 status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
133 } else if (config->timeout_sec_efivar <= 0){
134@@ -795,7 +779,7 @@ static BOOLEAN menu_run(
135 efivar_set(
136 LOADER_GUID, L"LoaderConfigTimeout", NULL, EFI_VARIABLE_NON_VOLATILE);
137 if (config->timeout_sec_config > 0)
138- status = PoolPrint(L"Menu timeout of %d sec is defined by configuration file.",
139+ status = PoolPrint(L"Menu timeout of %d s is defined by configuration file.",
140 config->timeout_sec_config);
141 else
142 status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
143@@ -813,7 +797,7 @@ static BOOLEAN menu_run(
144 config->timeout_sec_efivar,
145 EFI_VARIABLE_NON_VOLATILE);
146 if (config->timeout_sec_efivar > 0)
147- status = PoolPrint(L"Menu timeout set to %d sec.",
148+ status = PoolPrint(L"Menu timeout set to %d s.",
149 config->timeout_sec_efivar);
150 else
151 status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
152@@ -2369,13 +2353,8 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
153 else {
154 UINT64 key;
155
156- err = console_key_read(&key, FALSE);
157-
158- if (err == EFI_NOT_READY) {
159- uefi_call_wrapper(BS->Stall, 1, 100 * 1000);
160- err = console_key_read(&key, FALSE);
161- }
162-
163+ /* Block up to 100ms to give firmware time to get input working. */
164+ err = console_key_read(&key, 100 * 1000);
165 if (!EFI_ERROR(err)) {
166 INT16 idx;
167
168diff --git a/src/boot/efi/console.c b/src/boot/efi/console.c
169index 83619d2147..369c549daf 100644
170--- a/src/boot/efi/console.c
171+++ b/src/boot/efi/console.c
172@@ -11,61 +11,105 @@
173
174 #define EFI_SIMPLE_TEXT_INPUT_EX_GUID &(EFI_GUID) EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID
175
176-EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait) {
177+static inline void EventClosep(EFI_EVENT *event) {
178+ if (!*event)
179+ return;
180+
181+ uefi_call_wrapper(BS->CloseEvent, 1, *event);
182+}
183+
184+/*
185+ * Reading input from the console sounds like an easy task to do, but thanks to broken
186+ * firmware it is actually a nightmare.
187+ *
188+ * There is a ConIn and TextInputEx API for this. Ideally we want to use TextInputEx,
189+ * because that gives us Ctrl/Alt/Shift key state information. Unfortunately, it is not
190+ * always available and sometimes just non-functional.
191+ *
192+ * On the other hand we have ConIn, where some firmware likes to just freeze on us
193+ * if we call ReadKeyStroke on it.
194+ *
195+ * Therefore, we use WaitForEvent on both ConIn and TextInputEx (if available) along
196+ * with a timer event. The timer ensures there is no need to call into functions
197+ * that might freeze on us, while still allowing us to show a timeout counter.
198+ */
199+EFI_STATUS console_key_read(UINT64 *key, UINT64 timeout_usec) {
200 static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *TextInputEx;
201 static BOOLEAN checked;
202 UINTN index;
203 EFI_INPUT_KEY k;
204 EFI_STATUS err;
205+ _cleanup_(EventClosep) EFI_EVENT timer = NULL;
206+ EFI_EVENT events[3] = { ST->ConIn->WaitForKey };
207+ UINTN n_events = 1;
208
209 if (!checked) {
210 err = LibLocateProtocol(EFI_SIMPLE_TEXT_INPUT_EX_GUID, (VOID **)&TextInputEx);
211- if (EFI_ERROR(err))
212+ if (EFI_ERROR(err) ||
213+ uefi_call_wrapper(BS->CheckEvent, 1, TextInputEx->WaitForKeyEx) == EFI_INVALID_PARAMETER)
214+ /* If WaitForKeyEx fails here, the firmware pretends it talks this
215+ * protocol, but it really doesn't. */
216 TextInputEx = NULL;
217+ else
218+ events[n_events++] = TextInputEx->WaitForKeyEx;
219
220 checked = TRUE;
221 }
222
223- /* wait until key is pressed */
224- if (wait)
225- uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
226+ if (timeout_usec > 0) {
227+ err = uefi_call_wrapper(BS->CreateEvent, 5, EVT_TIMER, 0, NULL, NULL, &timer);
228+ if (EFI_ERROR(err))
229+ return log_error_status_stall(err, L"Error creating timer event: %r", err);
230+
231+ /* SetTimer expects 100ns units for some reason. */
232+ err = uefi_call_wrapper(BS->SetTimer, 3, timer, TimerRelative, timeout_usec * 10);
233+ if (EFI_ERROR(err))
234+ return log_error_status_stall(err, L"Error arming timer event: %r", err);
235
236- if (TextInputEx) {
237+ events[n_events++] = timer;
238+ }
239+
240+ err = uefi_call_wrapper(BS->WaitForEvent, 3, n_events, events, &index);
241+ if (EFI_ERROR(err))
242+ return log_error_status_stall(err, L"Error waiting for events: %r", err);
243+
244+ if (timeout_usec > 0 && timer == events[index])
245+ return EFI_TIMEOUT;
246+
247+ /* TextInputEx might be ready too even if ConIn got to signal first. */
248+ if (TextInputEx && !EFI_ERROR(uefi_call_wrapper(BS->CheckEvent, 1, TextInputEx->WaitForKeyEx))) {
249 EFI_KEY_DATA keydata;
250 UINT64 keypress;
251+ UINT32 shift = 0;
252
253 err = uefi_call_wrapper(TextInputEx->ReadKeyStrokeEx, 2, TextInputEx, &keydata);
254- if (!EFI_ERROR(err)) {
255- UINT32 shift = 0;
256-
257- /* do not distinguish between left and right keys */
258- if (keydata.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) {
259- if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED))
260- shift |= EFI_CONTROL_PRESSED;
261- if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED))
262- shift |= EFI_ALT_PRESSED;
263- };
264-
265- /* 32 bit modifier keys + 16 bit scan code + 16 bit unicode */
266- keypress = KEYPRESS(shift, keydata.Key.ScanCode, keydata.Key.UnicodeChar);
267- if (keypress > 0) {
268- *key = keypress;
269- return 0;
270- }
271+ if (EFI_ERROR(err))
272+ return err;
273+
274+ /* do not distinguish between left and right keys */
275+ if (keydata.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) {
276+ if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED))
277+ shift |= EFI_CONTROL_PRESSED;
278+ if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED))
279+ shift |= EFI_ALT_PRESSED;
280+ };
281+
282+ /* 32 bit modifier keys + 16 bit scan code + 16 bit unicode */
283+ keypress = KEYPRESS(shift, keydata.Key.ScanCode, keydata.Key.UnicodeChar);
284+ if (keypress > 0) {
285+ *key = keypress;
286+ return EFI_SUCCESS;
287 }
288+
289+ return EFI_NOT_READY;
290 }
291
292- /* fallback for firmware which does not support SimpleTextInputExProtocol
293- *
294- * This is also called in case ReadKeyStrokeEx did not return a key, because
295- * some broken firmwares offer SimpleTextInputExProtocol, but never actually
296- * handle any key. */
297 err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &k);
298 if (EFI_ERROR(err))
299 return err;
300
301 *key = KEYPRESS(0, k.ScanCode, k.UnicodeChar);
302- return 0;
303+ return EFI_SUCCESS;
304 }
305
306 static EFI_STATUS change_mode(UINTN mode) {
307diff --git a/src/boot/efi/console.h b/src/boot/efi/console.h
308index 2c69af552a..23848a9c58 100644
309--- a/src/boot/efi/console.h
310+++ b/src/boot/efi/console.h
311@@ -16,5 +16,5 @@ enum console_mode_change_type {
312 CONSOLE_MODE_MAX,
313 };
314
315-EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait);
316+EFI_STATUS console_key_read(UINT64 *key, UINT64 timeout_usec);
317 EFI_STATUS console_set_mode(UINTN *mode, enum console_mode_change_type how);
318--
3192.33.0
320