at 22.05-pre 320 lines 14 kB view raw
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