2 * Simple UEFI boot loader which executes configured EFI images, where the
3 * default entry is selected by a configured pattern (glob) or an on-screen
6 * All gummiboot code is LGPL not GPL, to stay out of politics and to give
7 * the freedom of copying code from programs to possible future libraries.
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * Copyright (C) 2012-2013 Kay Sievers <kay@vrfy.org>
20 * Copyright (C) 2012 Harald Hoyer <harald@redhat.com>
22 * "Any intelligent fool can make things bigger, more complex, and more violent."
29 #ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI
30 #define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL
33 #ifndef EFI_SECURITY_VIOLATION
34 #define EFI_SECURITY_VIOLATION EFIERR(26)
37 /* magic string to find in the binary image */
38 static const char __attribute__((used)) magic[] = "#### LoaderInfo: gummiboot " VERSION " ####";
41 * Allocated random UUID, intended to be shared across tools that implement
42 * the (ESP)\loader\entries\<vendor>-<revision>.conf convention and the
43 * associated EFI variables.
45 static const EFI_GUID loader_guid = { 0x4a67b082, 0x0a4c, 0x41cf, {0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f} };
47 static const EFI_GUID global_guid = EFI_GLOBAL_VARIABLE;
62 enum loader_type type;
66 EFI_STATUS (*call)(void);
67 BOOLEAN no_autoselect;
72 ConfigEntry **entries;
75 INTN idx_default_efivar;
77 UINTN timeout_sec_config;
78 INTN timeout_sec_efivar;
79 CHAR16 *entry_default_pattern;
80 CHAR16 *entry_oneshot;
85 static CHAR16 *stra_to_str(CHAR8 *stra);
88 static UINT64 ticks_read(void) {
90 __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
94 static UINT64 ticks_read(void) {
96 __asm__ volatile ("rdtsc" : "=A" (val));
101 static void cpuid_read(UINT32 info, UINT32 *eax, UINT32 *ebx, UINT32 *ecx, UINT32 *edx) {
108 :"+a" (*eax), "=S" (*ebx), "=c" (*ecx), "=d" (*edx)
113 static UINT64 cpufreq_read(void) {
114 UINT32 eax, ebx, ecx, edx;
117 CHAR8 s[4 * 4 * 3 + 1];
128 for (i = 0; i < 3; i++) {
129 cpuid_read(0x80000002 + i, &eax, &ebx, &ecx, &edx);
135 brand.s[4 * 4 * 3] = '\0';
139 * “x.xxyHz” or “xxxxyHz”, where y=M,G,T
140 * from CPUID brand string:
141 * Intel(R) Core(TM) i5-2540M CPU @ 2.60GHz
143 * http://www.intel.com/content/dam/www/public/us/en/documents/
144 * application-notes/processor-identification-cpuid-instruction-note.pdf
147 for (i = 4; i < (4 * 4 * 3) - 2; i++) {
148 if (brand.s[i+1] == 'H' && brand.s[i+2] == 'z') {
177 str = stra_to_str(s);
178 usec = Atoi(str) * scale;
183 static UINT64 time_usec(void) {
187 ticks = ticks_read();
191 cpufreq = cpufreq_read();
195 return 1000 * 1000 * ticks / cpufreq;
198 static EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 *buf, UINTN size, BOOLEAN persistent) {
201 flags = EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS;
203 flags |= EFI_VARIABLE_NON_VOLATILE;
205 return uefi_call_wrapper(RT->SetVariable, 5, name, (EFI_GUID *)vendor, flags, size, buf);
208 static EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent) {
209 return efivar_set_raw(&loader_guid, name, (CHAR8 *)value, value ? (StrLen(value)+1) * sizeof(CHAR16) : 0, persistent);
212 static EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, UINTN *size) {
217 l = sizeof(CHAR16 *) * EFI_MAXIMUM_VARIABLE_SIZE;
218 buf = AllocatePool(l);
220 return EFI_OUT_OF_RESOURCES;
222 err = uefi_call_wrapper(RT->GetVariable, 5, name, (EFI_GUID *)vendor, NULL, &l, buf);
223 if (!EFI_ERROR(err)) {
233 static EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value) {
239 err = efivar_get_raw(&loader_guid, name, &buf, &size);
243 val = StrDuplicate((CHAR16 *)buf);
246 return EFI_OUT_OF_RESOURCES;
253 static EFI_STATUS efivar_set_int(CHAR16 *name, UINTN i, BOOLEAN persistent) {
256 SPrint(str, 32, L"%d", i);
257 return efivar_set(name, str, persistent);
260 static EFI_STATUS efivar_get_int(CHAR16 *name, UINTN *i) {
264 err = efivar_get(name, &val);
265 if (!EFI_ERROR(err)) {
272 static VOID efivar_set_time_usec(CHAR16 *name, UINT64 usec) {
280 SPrint(str, 32, L"%ld", usec);
281 efivar_set(name, str, FALSE);
284 #define EFI_SHIFT_STATE_VALID 0x80000000
285 #define EFI_RIGHT_CONTROL_PRESSED 0x00000004
286 #define EFI_LEFT_CONTROL_PRESSED 0x00000008
287 #define EFI_RIGHT_ALT_PRESSED 0x00000010
288 #define EFI_LEFT_ALT_PRESSED 0x00000020
289 #define EFI_CONTROL_PRESSED (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED)
290 #define EFI_ALT_PRESSED (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED)
291 #define KEYPRESS(keys, scan, uni) ((((UINT64)keys) << 32) | ((scan) << 16) | (uni))
292 #define KEYCHAR(k) ((k) & 0xffff)
293 #define CHAR_CTRL(c) ((c) - 'a' + 1)
295 static EFI_STATUS key_read(UINT64 *key, BOOLEAN wait) {
296 #define EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID \
297 { 0xdd9e7534, 0x7762, 0x4698, { 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa } }
299 struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL;
301 typedef EFI_STATUS (EFIAPI *EFI_INPUT_RESET_EX)(
302 struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This;
303 BOOLEAN ExtendedVerification;
306 typedef UINT8 EFI_KEY_TOGGLE_STATE;
309 UINT32 KeyShiftState;
310 EFI_KEY_TOGGLE_STATE KeyToggleState;
315 EFI_KEY_STATE KeyState;
318 typedef EFI_STATUS (EFIAPI *EFI_INPUT_READ_KEY_EX)(
319 struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This;
320 EFI_KEY_DATA *KeyData;
323 typedef EFI_STATUS (EFIAPI *EFI_SET_STATE)(
324 struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This;
325 EFI_KEY_TOGGLE_STATE *KeyToggleState;
328 typedef EFI_STATUS (EFIAPI *EFI_KEY_NOTIFY_FUNCTION)(
329 EFI_KEY_DATA *KeyData;
332 typedef EFI_STATUS (EFIAPI *EFI_REGISTER_KEYSTROKE_NOTIFY)(
333 struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This;
334 EFI_KEY_DATA KeyData;
335 EFI_KEY_NOTIFY_FUNCTION KeyNotificationFunction;
339 typedef EFI_STATUS (EFIAPI *EFI_UNREGISTER_KEYSTROKE_NOTIFY)(
340 struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This;
341 VOID *NotificationHandle;
344 typedef struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL {
345 EFI_INPUT_RESET_EX Reset;
346 EFI_INPUT_READ_KEY_EX ReadKeyStrokeEx;
347 EFI_EVENT WaitForKeyEx;
348 EFI_SET_STATE SetState;
349 EFI_REGISTER_KEYSTROKE_NOTIFY RegisterKeyNotify;
350 EFI_UNREGISTER_KEYSTROKE_NOTIFY UnregisterKeyNotify;
351 } EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL;
353 EFI_GUID EfiSimpleTextInputExProtocolGuid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID;
354 static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *TextInputEx;
355 static BOOLEAN checked;
356 EFI_KEY_DATA keydata;
362 err = LibLocateProtocol(&EfiSimpleTextInputExProtocolGuid, (VOID **)&TextInputEx);
372 /* fallback for firmware which does not support SIMPLE_TEXT_INPUT_EX_PROTOCOL */
374 uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
375 err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &k);
379 *key = KEYPRESS(0, k.ScanCode, k.UnicodeChar);
384 uefi_call_wrapper(BS->WaitForEvent, 3, 1, &TextInputEx->WaitForKeyEx, &index);
385 err = uefi_call_wrapper(TextInputEx->ReadKeyStrokeEx, 2, TextInputEx, &keydata);
386 if (EFI_ERROR(err)) {
387 /* hmm, we waited but we could read a key; some firmwares seem
388 * to provide SimpleTextInputExProtocol but it does not do the
389 * right thing; just fall back to SimpleTextInputProtocol. */
395 /* do not distinguish between left and right keys */
396 if (keydata.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) {
397 if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED))
398 shift |= EFI_CONTROL_PRESSED;
399 if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED))
400 shift |= EFI_ALT_PRESSED;
403 /* 32 bit modifier keys + 16 bit scan code + 16 bit unicode */
404 *key = KEYPRESS(shift, keydata.Key.ScanCode, keydata.Key.UnicodeChar);
408 static void cursor_left(UINTN *cursor, UINTN *first)
412 else if ((*first) > 0)
416 static void cursor_right(UINTN *cursor, UINTN *first, UINTN x_max, UINTN len)
418 if ((*cursor)+1 < x_max)
420 else if ((*first) + (*cursor) < len)
424 static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN y_pos) {
437 size = StrLen(line_in) + 1024;
438 line = AllocatePool(size * sizeof(CHAR16));
439 StrCpy(line, line_in);
441 print = AllocatePool((x_max+1) * sizeof(CHAR16));
443 uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE);
458 CopyMem(print, line + first, i * sizeof(CHAR16));
459 while (clear > 0 && i < x_max-1) {
465 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_pos);
466 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, print);
467 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
469 err = key_read(&key, TRUE);
474 case KEYPRESS(0, SCAN_ESC, 0):
475 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'):
476 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'):
477 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')):
478 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')):
482 case KEYPRESS(0, SCAN_HOME, 0):
483 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'):
484 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')):
485 /* beginning-of-line */
490 case KEYPRESS(0, SCAN_END, 0):
491 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'):
492 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')):
494 cursor = len - first;
495 if (cursor+1 >= x_max) {
497 first = len - (x_max-1);
501 case KEYPRESS(0, SCAN_DOWN, 0):
502 case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'):
503 case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0):
505 while (line[first + cursor] && line[first + cursor] == ' ')
506 cursor_right(&cursor, &first, x_max, len);
507 while (line[first + cursor] && line[first + cursor] != ' ')
508 cursor_right(&cursor, &first, x_max, len);
509 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
512 case KEYPRESS(0, SCAN_UP, 0):
513 case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'):
514 case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0):
516 if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
517 cursor_left(&cursor, &first);
518 while ((first + cursor) > 0 && line[first + cursor] == ' ')
519 cursor_left(&cursor, &first);
521 while ((first + cursor) > 0 && line[first + cursor-1] != ' ')
522 cursor_left(&cursor, &first);
523 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
526 case KEYPRESS(0, SCAN_RIGHT, 0):
527 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'):
528 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')):
530 if (first + cursor == len)
532 cursor_right(&cursor, &first, x_max, len);
533 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
536 case KEYPRESS(0, SCAN_LEFT, 0):
537 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'):
538 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')):
540 cursor_left(&cursor, &first);
541 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
544 case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'):
547 for (i = first + cursor; i < len && line[i] == ' '; i++)
549 for (; i < len && line[i] != ' '; i++)
552 for (i = first + cursor; i + clear < len; i++)
553 line[i] = line[i + clear];
558 case KEYPRESS(EFI_ALT_PRESSED, 0, CHAR_BACKSPACE):
559 /* backward-kill-word */
561 if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
562 cursor_left(&cursor, &first);
564 while ((first + cursor) > 0 && line[first + cursor] == ' ') {
565 cursor_left(&cursor, &first);
569 while ((first + cursor) > 0 && line[first + cursor-1] != ' ') {
570 cursor_left(&cursor, &first);
573 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
575 for (i = first + cursor; i + clear < len; i++)
576 line[i] = line[i + clear];
581 case KEYPRESS(0, SCAN_DELETE, 0):
582 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'):
583 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')):
586 if (first + cursor == len)
588 for (i = first + cursor; i < len; i++)
594 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'):
595 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')):
597 line[first + cursor] = '\0';
598 clear = len - (first + cursor);
599 len = first + cursor;
602 case KEYPRESS(0, 0, CHAR_LINEFEED):
603 case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN):
604 if (StrCmp(line, line_in) != 0) {
612 case KEYPRESS(0, 0, CHAR_BACKSPACE):
615 if (first == 0 && cursor == 0)
617 for (i = first + cursor-1; i < len; i++)
623 if (cursor > 0 || first == 0)
625 /* show full line if it fits */
631 /* jump left to see what we delete */
641 case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'):
642 case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff):
645 for (i = len; i > first + cursor; i--)
647 line[first + cursor] = KEYCHAR(key);
650 if (cursor+1 < x_max)
652 else if (first + cursor < len)
658 uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE);
664 static UINTN entry_lookup_key(Config *config, UINTN start, CHAR16 key) {
670 /* select entry by number key */
671 if (key >= '1' && key <= '9') {
673 if (i > config->entry_count)
674 i = config->entry_count;
678 /* find matching key in config entries */
679 for (i = start; i < config->entry_count; i++)
680 if (config->entries[i]->key == key)
683 for (i = 0; i < start; i++)
684 if (config->entries[i]->key == key)
690 static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
697 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
698 uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
700 Print(L"gummiboot version: " VERSION "\n");
701 Print(L"loaded image: %s\n", loaded_image_path);
702 Print(L"UEFI version: %d.%02d\n", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
703 Print(L"firmware vendor: %s\n", ST->FirmwareVendor);
704 Print(L"firmware version: %d.%02d\n", ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
705 if (efivar_get_raw(&global_guid, L"SecureBoot", &b, &size) == EFI_SUCCESS) {
706 Print(L"SecureBoot: %s\n", *b > 0 ? L"enabled" : L"disabled");
710 if (efivar_get_raw(&global_guid, L"SetupMode", &b, &size) == EFI_SUCCESS) {
711 Print(L"SetupMode: %s\n", *b > 0 ? L"setup" : L"user");
715 if (efivar_get_raw(&global_guid, L"OsIndicationsSupported", &b, &size) == EFI_SUCCESS) {
716 Print(L"OsIndicationsSupported: %d\n", (UINT64)*b);
721 Print(L"timeout: %d\n", config->timeout_sec);
722 if (config->timeout_sec_efivar >= 0)
723 Print(L"timeout (EFI var): %d\n", config->timeout_sec_efivar);
724 Print(L"timeout (config): %d\n", config->timeout_sec_config);
725 if (config->entry_default_pattern)
726 Print(L"default pattern: '%s'\n", config->entry_default_pattern);
729 Print(L"config entry count: %d\n", config->entry_count);
730 Print(L"entry selected idx: %d\n", config->idx_default);
731 if (config->idx_default_efivar >= 0)
732 Print(L"entry EFI var idx: %d\n", config->idx_default_efivar);
735 if (efivar_get_int(L"LoaderConfigTimeout", &i) == EFI_SUCCESS)
736 Print(L"LoaderConfigTimeout: %d\n", i);
737 if (config->entry_oneshot)
738 Print(L"LoaderEntryOneShot: %s\n", config->entry_oneshot);
739 if (efivar_get(L"LoaderDeviceIdentifier", &s) == EFI_SUCCESS) {
740 Print(L"LoaderDeviceIdentifier: %s\n", s);
743 if (efivar_get(L"LoaderDevicePartUUID", &s) == EFI_SUCCESS) {
744 Print(L"LoaderDevicePartUUID: %s\n", s);
747 if (efivar_get(L"LoaderEntryDefault", &s) == EFI_SUCCESS) {
748 Print(L"LoaderEntryDefault: %s\n", s);
752 Print(L"\n--- press key ---\n\n");
753 key_read(&key, TRUE);
755 for (i = 0; i < config->entry_count; i++) {
758 if (key == KEYPRESS(0, SCAN_ESC, 0) || key == KEYPRESS(0, 0, 'q'))
761 entry = config->entries[i];
762 Print(L"config entry: %d/%d\n", i+1, config->entry_count);
764 Print(L"file '%s'\n", entry->file);
765 Print(L"title show '%s'\n", entry->title_show);
767 Print(L"title '%s'\n", entry->title);
769 Print(L"version '%s'\n", entry->version);
770 if (entry->machine_id)
771 Print(L"machine-id '%s'\n", entry->machine_id);
773 EFI_DEVICE_PATH *device_path;
776 device_path = DevicePathFromHandle(entry->device);
778 str = DevicePathToStr(device_path);
779 Print(L"device handle '%s'\n", str);
784 Print(L"loader '%s'\n", entry->loader);
786 Print(L"options '%s'\n", entry->options);
787 Print(L"auto-select %s\n", entry->no_autoselect ? L"no" : L"yes");
789 Print(L"internal call yes\n");
791 Print(L"\n--- press key ---\n\n");
792 key_read(&key, TRUE);
795 uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
798 static EFI_STATUS console_text_mode(VOID) {
799 #define EFI_CONSOLE_CONTROL_PROTOCOL_GUID \
800 { 0xf42f7782, 0x12e, 0x4c12, { 0x99, 0x56, 0x49, 0xf9, 0x43, 0x4, 0xf7, 0x21 } };
802 struct _EFI_CONSOLE_CONTROL_PROTOCOL;
805 EfiConsoleControlScreenText,
806 EfiConsoleControlScreenGraphics,
807 EfiConsoleControlScreenMaxValue,
808 } EFI_CONSOLE_CONTROL_SCREEN_MODE;
810 typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE)(
811 struct _EFI_CONSOLE_CONTROL_PROTOCOL *This,
812 EFI_CONSOLE_CONTROL_SCREEN_MODE *Mode,
817 typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE)(
818 struct _EFI_CONSOLE_CONTROL_PROTOCOL *This,
819 EFI_CONSOLE_CONTROL_SCREEN_MODE Mode
822 typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN)(
823 struct _EFI_CONSOLE_CONTROL_PROTOCOL *This,
827 typedef struct _EFI_CONSOLE_CONTROL_PROTOCOL {
828 EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE GetMode;
829 EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE SetMode;
830 EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN LockStdIn;
831 } EFI_CONSOLE_CONTROL_PROTOCOL;
833 EFI_GUID ConsoleControlProtocolGuid = EFI_CONSOLE_CONTROL_PROTOCOL_GUID;
834 EFI_CONSOLE_CONTROL_PROTOCOL *ConsoleControl = NULL;
837 err = LibLocateProtocol(&ConsoleControlProtocolGuid, (VOID **)&ConsoleControl);
840 return uefi_call_wrapper(ConsoleControl->SetMode, 2, ConsoleControl, EfiConsoleControlScreenText);
843 static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *loaded_image_path) {
847 UINTN idx_highlight_prev;
863 BOOLEAN exit = FALSE;
865 BOOLEAN wait = FALSE;
868 uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE);
869 uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE);
870 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
871 uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
873 err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x_max, &y_max);
874 if (EFI_ERROR(err)) {
879 /* we check 10 times per second for a keystroke */
880 if (config->timeout_sec > 0)
881 timeout_remain = config->timeout_sec * 10;
885 idx_highlight = config->idx_default;
886 idx_highlight_prev = 0;
888 visible_max = y_max - 2;
890 if ((UINTN)config->idx_default >= visible_max)
891 idx_first = config->idx_default-1;
895 idx_last = idx_first + visible_max-1;
900 /* length of the longest entry */
902 for (i = 0; i < config->entry_count; i++) {
905 entry_len = StrLen(config->entries[i]->title_show);
906 if (line_width < entry_len)
907 line_width = entry_len;
909 if (line_width > x_max-6)
910 line_width = x_max-6;
912 /* offsets to center the entries on the screen */
913 x_start = (x_max - (line_width)) / 2;
914 if (config->entry_count < visible_max)
915 y_start = ((visible_max - config->entry_count) / 2) + 1;
919 /* menu entries title lines */
920 lines = AllocatePool(sizeof(CHAR16 *) * config->entry_count);
921 for (i = 0; i < config->entry_count; i++) {
924 lines[i] = AllocatePool(((x_max+1) * sizeof(CHAR16)));
925 for (j = 0; j < x_start; j++)
928 for (k = 0; config->entries[i]->title_show[k] != '\0' && j < x_max; j++, k++)
929 lines[i][j] = config->entries[i]->title_show[k];
931 for (; j < x_max; j++)
933 lines[i][x_max] = '\0';
937 clearline = AllocatePool((x_max+1) * sizeof(CHAR16));
938 for (i = 0; i < x_max; i++)
946 for (i = 0; i < config->entry_count; i++) {
947 if (i < idx_first || i > idx_last)
949 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + i - idx_first);
950 if (i == idx_highlight)
951 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut,
952 EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY);
954 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut,
955 EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
956 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[i]);
957 if ((INTN)i == config->idx_default_efivar) {
958 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + i - idx_first);
959 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"=>");
963 } else if (highlight) {
964 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + idx_highlight_prev - idx_first);
965 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
966 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight_prev]);
967 if ((INTN)idx_highlight_prev == config->idx_default_efivar) {
968 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + idx_highlight_prev - idx_first);
969 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"=>");
972 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + idx_highlight - idx_first);
973 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY);
974 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight]);
975 if ((INTN)idx_highlight == config->idx_default_efivar) {
976 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + idx_highlight - idx_first);
977 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"=>");
982 if (timeout_remain > 0) {
984 status = PoolPrint(L"Boot in %d sec.", (timeout_remain + 5) / 10);
987 /* print status at last line of screen */
993 len = StrLen(status);
995 x = (x_max - len) / 2;
998 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
999 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
1000 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline + (x_max - x));
1001 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, status);
1002 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + x + len);
1005 err = key_read(&key, wait);
1006 if (EFI_ERROR(err)) {
1007 /* timeout reached */
1008 if (timeout_remain == 0) {
1013 /* sleep and update status */
1014 if (timeout_remain > 0) {
1015 uefi_call_wrapper(BS->Stall, 1, 100 * 1000);
1020 /* timeout disabled, wait for next key */
1025 timeout_remain = -1;
1027 /* clear status after keystroke */
1031 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
1032 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
1033 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
1036 idx_highlight_prev = idx_highlight;
1039 case KEYPRESS(0, SCAN_UP, 0):
1040 case KEYPRESS(0, 0, 'k'):
1041 if (idx_highlight > 0)
1045 case KEYPRESS(0, SCAN_DOWN, 0):
1046 case KEYPRESS(0, 0, 'j'):
1047 if (idx_highlight < config->entry_count-1)
1051 case KEYPRESS(0, SCAN_HOME, 0):
1052 case KEYPRESS(EFI_ALT_PRESSED, 0, '<'):
1053 if (idx_highlight > 0) {
1059 case KEYPRESS(0, SCAN_END, 0):
1060 case KEYPRESS(EFI_ALT_PRESSED, 0, '>'):
1061 if (idx_highlight < config->entry_count-1) {
1063 idx_highlight = config->entry_count-1;
1067 case KEYPRESS(0, SCAN_PAGE_UP, 0):
1068 if (idx_highlight > visible_max)
1069 idx_highlight -= visible_max;
1074 case KEYPRESS(0, SCAN_PAGE_DOWN, 0):
1075 idx_highlight += visible_max;
1076 if (idx_highlight > config->entry_count-1)
1077 idx_highlight = config->entry_count-1;
1080 case KEYPRESS(0, 0, CHAR_LINEFEED):
1081 case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN):
1085 case KEYPRESS(0, SCAN_F1, 0):
1086 case KEYPRESS(0, 0, 'h'):
1087 case KEYPRESS(0, 0, '?'):
1088 status = StrDuplicate(L"(d)efault, (t/T)timeout, (e)dit, (v)ersion (Q)uit (P)rint (h)elp");
1091 case KEYPRESS(0, 0, 'Q'):
1096 case KEYPRESS(0, 0, 'd'):
1097 if (config->idx_default_efivar != (INTN)idx_highlight) {
1098 /* store the selected entry in a persistent EFI variable */
1099 efivar_set(L"LoaderEntryDefault", config->entries[idx_highlight]->file, TRUE);
1100 config->idx_default_efivar = idx_highlight;
1101 status = StrDuplicate(L"Default boot entry selected.");
1103 /* clear the default entry EFI variable */
1104 efivar_set(L"LoaderEntryDefault", NULL, TRUE);
1105 config->idx_default_efivar = -1;
1106 status = StrDuplicate(L"Default boot entry cleared.");
1111 case KEYPRESS(0, 0, '-'):
1112 case KEYPRESS(0, 0, 'T'):
1113 if (config->timeout_sec_efivar > 0) {
1114 config->timeout_sec_efivar--;
1115 efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE);
1116 if (config->timeout_sec_efivar > 0)
1117 status = PoolPrint(L"Menu timeout set to %d sec.", config->timeout_sec_efivar);
1119 status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
1120 } else if (config->timeout_sec_efivar <= 0){
1121 config->timeout_sec_efivar = -1;
1122 efivar_set(L"LoaderConfigTimeout", NULL, TRUE);
1123 if (config->timeout_sec_config > 0)
1124 status = PoolPrint(L"Menu timeout of %d sec is defined by configuration file.",
1125 config->timeout_sec_config);
1127 status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
1131 case KEYPRESS(0, 0, '+'):
1132 case KEYPRESS(0, 0, 't'):
1133 if (config->timeout_sec_efivar == -1 && config->timeout_sec_config == 0)
1134 config->timeout_sec_efivar++;
1135 config->timeout_sec_efivar++;
1136 efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE);
1137 if (config->timeout_sec_efivar > 0)
1138 status = PoolPrint(L"Menu timeout set to %d sec.",
1139 config->timeout_sec_efivar);
1141 status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
1144 case KEYPRESS(0, 0, 'e'):
1145 /* only the options of configured entries can be edited */
1146 if (config->entries[idx_highlight]->type == LOADER_UNDEFINED)
1148 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
1149 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
1150 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
1151 if (line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max-1, y_max-1))
1153 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
1154 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
1157 case KEYPRESS(0, 0, 'v'):
1158 status = PoolPrint(L"gummiboot " VERSION ", UEFI %d.%02d, %s %d.%02d",
1159 ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff,
1160 ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
1163 case KEYPRESS(0, 0, 'P'):
1164 print_status(config, loaded_image_path);
1168 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'l'):
1169 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('l')):
1174 /* jump with a hotkey directly to a matching entry */
1175 idx = entry_lookup_key(config, idx_highlight+1, KEYCHAR(key));
1178 idx_highlight = idx;
1182 if (idx_highlight > idx_last) {
1183 idx_last = idx_highlight;
1184 idx_first = 1 + idx_highlight - visible_max;
1187 if (idx_highlight < idx_first) {
1188 idx_first = idx_highlight;
1189 idx_last = idx_highlight + visible_max-1;
1193 idx_last = idx_first + visible_max-1;
1195 if (!refresh && idx_highlight != idx_highlight_prev)
1199 *chosen_entry = config->entries[idx_highlight];
1201 for (i = 0; i < config->entry_count; i++)
1204 FreePool(clearline);
1206 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_WHITE|EFI_BACKGROUND_BLACK);
1207 uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
1211 static VOID config_add_entry(Config *config, ConfigEntry *entry) {
1212 if ((config->entry_count & 15) == 0) {
1215 i = config->entry_count + 16;
1216 if (config->entry_count == 0)
1217 config->entries = AllocatePool(sizeof(VOID *) * i);
1219 config->entries = ReallocatePool(config->entries,
1220 sizeof(VOID *) * config->entry_count, sizeof(VOID *) * i);
1222 config->entries[config->entry_count++] = entry;
1225 static VOID config_entry_free(ConfigEntry *entry) {
1226 FreePool(entry->title_show);
1227 FreePool(entry->title);
1228 FreePool(entry->machine_id);
1229 FreePool(entry->loader);
1230 FreePool(entry->options);
1233 static BOOLEAN is_digit(CHAR16 c)
1235 return (c >= '0') && (c <= '9');
1238 static UINTN c_order(CHAR16 c)
1244 else if ((c >= 'a') && (c <= 'z'))
1250 static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2)
1255 while (*s1 || *s2) {
1258 while ((*s1 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) {
1261 order = c_order(*s1) - c_order(*s2);
1274 while (is_digit(*s1) && is_digit(*s2)) {
1290 return StrCmp(os1, os2);
1293 static INTN utf8_to_16(CHAR8 *stra, CHAR16 *c) {
1300 else if ((stra[0] & 0xe0) == 0xc0)
1302 else if ((stra[0] & 0xf0) == 0xe0)
1304 else if ((stra[0] & 0xf8) == 0xf0)
1306 else if ((stra[0] & 0xfc) == 0xf8)
1308 else if ((stra[0] & 0xfe) == 0xfc)
1318 unichar = stra[0] & 0x1f;
1321 unichar = stra[0] & 0x0f;
1324 unichar = stra[0] & 0x07;
1327 unichar = stra[0] & 0x03;
1330 unichar = stra[0] & 0x01;
1334 for (i = 1; i < len; i++) {
1335 if ((stra[i] & 0xc0) != 0x80)
1338 unichar |= stra[i] & 0x3f;
1345 static CHAR16 *stra_to_str(CHAR8 *stra) {
1351 len = strlena(stra);
1352 str = AllocatePool((len + 1) * sizeof(CHAR16));
1359 utf8len = utf8_to_16(stra + i, str + strlen);
1361 /* invalid utf8 sequence, skip the garbage */
1373 static CHAR16 *stra_to_path(CHAR8 *stra) {
1379 len = strlena(stra);
1380 str = AllocatePool((len + 2) * sizeof(CHAR16));
1388 utf8len = utf8_to_16(stra + i, str + strlen);
1390 /* invalid utf8 sequence, skip the garbage */
1395 if (str[strlen] == '/')
1397 if (str[strlen] == '\\' && str[strlen-1] == '\\') {
1398 /* skip double slashes */
1410 static CHAR8 *strchra(CHAR8 *s, CHAR8 c) {
1418 static CHAR8 *line_get_key_value(CHAR8 *content, UINTN *pos, CHAR8 **key_ret, CHAR8 **value_ret) {
1424 line = content + *pos;
1429 while (line[linelen] && !strchra((CHAR8 *)"\n\r", line[linelen]))
1432 /* move pos to next line */
1441 /* terminate line */
1442 line[linelen] = '\0';
1444 /* remove leading whitespace */
1445 while (strchra((CHAR8 *)" \t", *line)) {
1450 /* remove trailing whitespace */
1451 while (linelen > 0 && strchra((CHAR8 *)" \t", line[linelen-1]))
1453 line[linelen] = '\0';
1458 /* split key/value */
1460 while (*value && !strchra((CHAR8 *)" \t", *value))
1466 while (*value && strchra((CHAR8 *)" \t", *value))
1474 static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) {
1480 while ((line = line_get_key_value(content, &pos, &key, &value))) {
1481 if (strcmpa((CHAR8 *)"timeout", key) == 0) {
1484 s = stra_to_str(value);
1485 config->timeout_sec_config = Atoi(s);
1486 config->timeout_sec = config->timeout_sec_config;
1490 if (strcmpa((CHAR8 *)"default", key) == 0) {
1491 config->entry_default_pattern = stra_to_str(value);
1492 StrLwr(config->entry_default_pattern);
1498 static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR16 *file, CHAR8 *content, CHAR16 *loaded_image_path) {
1504 CHAR16 *initrd = NULL;
1506 entry = AllocateZeroPool(sizeof(ConfigEntry));
1509 while ((line = line_get_key_value(content, &pos, &key, &value))) {
1510 if (strcmpa((CHAR8 *)"title", key) == 0) {
1511 FreePool(entry->title);
1512 entry->title = stra_to_str(value);
1516 if (strcmpa((CHAR8 *)"version", key) == 0) {
1517 FreePool(entry->version);
1518 entry->version = stra_to_str(value);
1522 if (strcmpa((CHAR8 *)"machine-id", key) == 0) {
1523 FreePool(entry->machine_id);
1524 entry->machine_id = stra_to_str(value);
1528 if (strcmpa((CHAR8 *)"linux", key) == 0) {
1529 FreePool(entry->loader);
1530 entry->type = LOADER_LINUX;
1531 entry->loader = stra_to_path(value);
1536 if (strcmpa((CHAR8 *)"efi", key) == 0) {
1537 entry->type = LOADER_EFI;
1538 FreePool(entry->loader);
1539 entry->loader = stra_to_path(value);
1541 /* do not add an entry for ourselves */
1542 if (StriCmp(entry->loader, loaded_image_path) == 0) {
1543 entry->type = LOADER_UNDEFINED;
1549 if (strcmpa((CHAR8 *)"initrd", key) == 0) {
1552 new = stra_to_path(value);
1556 s = PoolPrint(L"%s initrd=%s", initrd, new);
1560 initrd = PoolPrint(L"initrd=%s", new);
1565 if (strcmpa((CHAR8 *)"options", key) == 0) {
1568 new = stra_to_str(value);
1569 if (entry->options) {
1572 s = PoolPrint(L"%s %s", entry->options, new);
1573 FreePool(entry->options);
1576 entry->options = new;
1584 if (entry->type == LOADER_UNDEFINED) {
1585 config_entry_free(entry);
1591 /* add initrd= to options */
1592 if (entry->type == LOADER_LINUX && initrd) {
1593 if (entry->options) {
1596 s = PoolPrint(L"%s %s", initrd, entry->options);
1597 FreePool(entry->options);
1600 entry->options = initrd;
1606 if (entry->machine_id) {
1609 /* append additional options from EFI variables for this machine-id */
1610 var = PoolPrint(L"LoaderEntryOptions-%s", entry->machine_id);
1614 if (efivar_get(var, &s) == EFI_SUCCESS) {
1615 if (entry->options) {
1618 s2 = PoolPrint(L"%s %s", entry->options, s);
1619 FreePool(entry->options);
1620 entry->options = s2;
1627 var = PoolPrint(L"LoaderEntryOptionsOneShot-%s", entry->machine_id);
1631 if (efivar_get(var, &s) == EFI_SUCCESS) {
1632 if (entry->options) {
1635 s2 = PoolPrint(L"%s %s", entry->options, s);
1636 FreePool(entry->options);
1637 entry->options = s2;
1640 efivar_set(var, NULL, TRUE);
1646 entry->device = device;
1647 entry->file = StrDuplicate(file);
1648 len = StrLen(entry->file);
1649 /* remove ".conf" */
1651 entry->file[len - 5] = '\0';
1652 StrLwr(entry->file);
1654 config_add_entry(config, entry);
1657 static UINTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, CHAR8 **content) {
1658 EFI_FILE_HANDLE handle;
1659 EFI_FILE_INFO *info;
1665 err = uefi_call_wrapper(dir->Open, 5, dir, &handle, name, EFI_FILE_MODE_READ, 0ULL);
1669 info = LibFileInfo(handle);
1670 buflen = info->FileSize+1;
1671 buf = AllocatePool(buflen);
1673 err = uefi_call_wrapper(handle->Read, 3, handle, &buflen, buf);
1674 if (!EFI_ERROR(err)) {
1682 uefi_call_wrapper(handle->Close, 1, handle);
1687 static VOID config_load(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path) {
1688 EFI_FILE_HANDLE entries_dir;
1690 CHAR8 *content = NULL;
1695 len = file_read(root_dir, L"\\loader\\loader.conf", &content);
1697 config_defaults_load_from_file(config, content);
1700 err = efivar_get_int(L"LoaderConfigTimeout", &sec);
1701 if (!EFI_ERROR(err)) {
1702 config->timeout_sec_efivar = sec;
1703 config->timeout_sec = sec;
1705 config->timeout_sec_efivar = -1;
1707 err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &entries_dir, L"\\loader\\entries", EFI_FILE_MODE_READ, 0ULL);
1708 if (!EFI_ERROR(err)) {
1713 CHAR8 *content = NULL;
1716 bufsize = sizeof(buf);
1717 err = uefi_call_wrapper(entries_dir->Read, 3, entries_dir, &bufsize, buf);
1718 if (bufsize == 0 || EFI_ERROR(err))
1721 f = (EFI_FILE_INFO *) buf;
1722 if (f->FileName[0] == '.')
1724 if (f->Attribute & EFI_FILE_DIRECTORY)
1726 len = StrLen(f->FileName);
1729 if (StriCmp(f->FileName + len - 5, L".conf") != 0)
1732 len = file_read(entries_dir, f->FileName, &content);
1734 config_entry_add_from_file(config, device, f->FileName, content, loaded_image_path);
1737 uefi_call_wrapper(entries_dir->Close, 1, entries_dir);
1740 /* sort entries after version number */
1741 for (i = 1; i < config->entry_count; i++) {
1746 for (k = 0; k < config->entry_count - i; k++) {
1749 if (str_verscmp(config->entries[k]->file, config->entries[k+1]->file) <= 0)
1751 entry = config->entries[k];
1752 config->entries[k] = config->entries[k+1];
1753 config->entries[k+1] = entry;
1761 static VOID config_default_entry_select(Config *config) {
1767 * The EFI variable to specify a boot entry for the next, and only the
1768 * next reboot. The variable is always cleared directly after it is read.
1770 err = efivar_get(L"LoaderEntryOneShot", &var);
1771 if (!EFI_ERROR(err)) {
1772 BOOLEAN found = FALSE;
1774 for (i = 0; i < config->entry_count; i++) {
1775 if (StrCmp(config->entries[i]->file, var) == 0) {
1776 config->idx_default = i;
1782 config->entry_oneshot = StrDuplicate(var);
1783 efivar_set(L"LoaderEntryOneShot", NULL, TRUE);
1790 * The EFI variable to select the default boot entry overrides the
1791 * configured pattern. The variable can be set and cleared by pressing
1792 * the 'd' key in the loader selection menu, the entry is marked with
1795 err = efivar_get(L"LoaderEntryDefault", &var);
1796 if (!EFI_ERROR(err)) {
1797 BOOLEAN found = FALSE;
1799 for (i = 0; i < config->entry_count; i++) {
1800 if (StrCmp(config->entries[i]->file, var) == 0) {
1801 config->idx_default = i;
1802 config->idx_default_efivar = i;
1811 config->idx_default_efivar = -1;
1813 if (config->entry_count == 0)
1817 * Match the pattern from the end of the list to the start, find last
1818 * entry (largest number) matching the given pattern.
1820 if (config->entry_default_pattern) {
1821 i = config->entry_count;
1823 if (config->entries[i]->no_autoselect)
1825 if (MetaiMatch(config->entries[i]->file, config->entry_default_pattern)) {
1826 config->idx_default = i;
1832 /* select the last suitable entry */
1833 i = config->entry_count;
1835 if (config->entries[i]->no_autoselect)
1837 config->idx_default = i;
1841 /* no entry found */
1842 config->idx_default = -1;
1845 /* generate a unique title, avoiding non-distinguishable menu entries */
1846 static VOID config_title_generate(Config *config) {
1851 for (i = 0; i < config->entry_count; i++) {
1854 FreePool(config->entries[i]->title_show);
1855 title = config->entries[i]->title;
1857 title = config->entries[i]->file;
1858 config->entries[i]->title_show = StrDuplicate(title);
1862 for (i = 0; i < config->entry_count; i++) {
1863 for (k = 0; k < config->entry_count; k++) {
1866 if (StrCmp(config->entries[i]->title_show, config->entries[k]->title_show) != 0)
1870 config->entries[i]->non_unique = TRUE;
1871 config->entries[k]->non_unique = TRUE;
1877 /* add version to non-unique titles */
1878 for (i = 0; i < config->entry_count; i++) {
1881 if (!config->entries[i]->non_unique)
1883 if (!config->entries[i]->version)
1886 s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, config->entries[i]->version);
1887 FreePool(config->entries[i]->title_show);
1888 config->entries[i]->title_show = s;
1889 config->entries[i]->non_unique = FALSE;
1893 for (i = 0; i < config->entry_count; i++) {
1894 for (k = 0; k < config->entry_count; k++) {
1897 if (StrCmp(config->entries[i]->title_show, config->entries[k]->title_show) != 0)
1901 config->entries[i]->non_unique = TRUE;
1902 config->entries[k]->non_unique = TRUE;
1908 /* add machine-id to non-unique titles */
1909 for (i = 0; i < config->entry_count; i++) {
1913 if (!config->entries[i]->non_unique)
1915 if (!config->entries[i]->machine_id)
1918 m = StrDuplicate(config->entries[i]->machine_id);
1920 s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, m);
1921 FreePool(config->entries[i]->title_show);
1922 config->entries[i]->title_show = s;
1923 config->entries[i]->non_unique = FALSE;
1928 for (i = 0; i < config->entry_count; i++) {
1929 for (k = 0; k < config->entry_count; k++) {
1932 if (StrCmp(config->entries[i]->title_show, config->entries[k]->title_show) != 0)
1936 config->entries[i]->non_unique = TRUE;
1937 config->entries[k]->non_unique = TRUE;
1943 /* add file name to non-unique titles */
1944 for (i = 0; i < config->entry_count; i++) {
1947 if (!config->entries[i]->non_unique)
1949 s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, config->entries[i]->file);
1950 FreePool(config->entries[i]->title_show);
1951 config->entries[i]->title_show = s;
1952 config->entries[i]->non_unique = FALSE;
1956 static BOOLEAN config_entry_add_call(Config *config, CHAR16 *title, EFI_STATUS (*call)(void)) {
1959 entry = AllocateZeroPool(sizeof(ConfigEntry));
1960 entry->title = StrDuplicate(title);
1962 entry->no_autoselect = TRUE;
1963 config_add_entry(config, entry);
1967 static BOOLEAN config_entry_add_loader(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path,
1968 CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) {
1969 EFI_FILE_HANDLE handle;
1973 /* do not add an entry for ourselves */
1974 if (loaded_image_path && StriCmp(loader, loaded_image_path) == 0)
1977 /* check existence */
1978 err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &handle, loader, EFI_FILE_MODE_READ, 0ULL);
1981 uefi_call_wrapper(handle->Close, 1, handle);
1983 entry = AllocateZeroPool(sizeof(ConfigEntry));
1984 entry->title = StrDuplicate(title);
1985 entry->device = device;
1986 entry->loader = StrDuplicate(loader);
1987 entry->file = StrDuplicate(file);
1988 StrLwr(entry->file);
1989 entry->no_autoselect = TRUE;
1991 config_add_entry(config, entry);
1995 static VOID config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path,
1996 CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) {
1997 if (!config_entry_add_loader(config, device, root_dir, loaded_image_path, file, key, title, loader))
2000 /* export identifiers of automatically added entries */
2001 if (config->entries_auto) {
2004 s = PoolPrint(L"%s %s", config->entries_auto, file);
2005 FreePool(config->entries_auto);
2006 config->entries_auto = s;
2008 config->entries_auto = StrDuplicate(file);
2011 static VOID config_entry_add_osx(Config *config) {
2013 UINTN handle_count = 0;
2014 EFI_HANDLE *handles = NULL;
2016 err = LibLocateHandle(ByProtocol, &FileSystemProtocol, NULL, &handle_count, &handles);
2017 if (!EFI_ERROR(err)) {
2020 for (i = 0; i < handle_count; i++) {
2023 root = LibOpenRoot(handles[i]);
2026 config_entry_add_loader_auto(config, handles[i], root, NULL, L"auto-osx", 'a', L"OS X",
2027 L"\\System\\Library\\CoreServices\\boot.efi");
2028 uefi_call_wrapper(root->Close, 1, root);
2035 static EFI_STATUS image_start(EFI_HANDLE parent_image, const Config *config, const ConfigEntry *entry) {
2038 EFI_DEVICE_PATH *path;
2041 path = FileDevicePath(entry->device, entry->loader);
2043 Print(L"Error getting device path.");
2044 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
2045 return EFI_INVALID_PARAMETER;
2048 err = uefi_call_wrapper(BS->LoadImage, 6, FALSE, parent_image, path, NULL, 0, &image);
2049 if (EFI_ERROR(err)) {
2050 Print(L"Error loading %s: %r", entry->loader, err);
2051 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
2055 if (config->options_edit)
2056 options = config->options_edit;
2057 else if (entry->options)
2058 options = entry->options;
2062 EFI_LOADED_IMAGE *loaded_image;
2064 err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (void **)&loaded_image,
2065 parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
2066 if (EFI_ERROR(err)) {
2067 Print(L"Error getting LoadedImageProtocol handle: %r", err);
2068 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
2071 loaded_image->LoadOptions = options;
2072 loaded_image->LoadOptionsSize = (StrLen(loaded_image->LoadOptions)+1) * sizeof(CHAR16);
2075 efivar_set_time_usec(L"LoaderTimeExecUSec", 0);
2076 err = uefi_call_wrapper(BS->StartImage, 3, image, NULL, NULL);
2078 uefi_call_wrapper(BS->UnloadImage, 1, image);
2084 static EFI_STATUS reboot_into_firmware(VOID) {
2090 osind = EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
2092 err = efivar_get_raw(&global_guid, L"OsIndications", &b, &size);
2093 if (!EFI_ERROR(err))
2094 osind |= (UINT64)*b;
2097 err = efivar_set_raw(&global_guid, L"OsIndications", (CHAR8 *)&osind, sizeof(UINT64), TRUE);
2101 err = uefi_call_wrapper(RT->ResetSystem, 4, EfiResetCold, EFI_SUCCESS, 0, NULL);
2102 Print(L"Error calling ResetSystem: %r", err);
2103 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
2107 static VOID config_free(Config *config) {
2110 for (i = 0; i < config->entry_count; i++)
2111 config_entry_free(config->entries[i]);
2112 FreePool(config->entries);
2113 FreePool(config->entry_default_pattern);
2114 FreePool(config->options_edit);
2115 FreePool(config->entry_oneshot);
2116 FreePool(config->entries_auto);
2119 EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
2123 EFI_LOADED_IMAGE *loaded_image;
2125 CHAR16 *loaded_image_path;
2126 EFI_DEVICE_PATH *device_path;
2130 BOOLEAN menu = FALSE;
2132 InitializeLib(image, sys_table);
2133 init_usec = time_usec();
2134 efivar_set_time_usec(L"LoaderTimeInitUSec", init_usec);
2135 efivar_set(L"LoaderInfo", L"gummiboot " VERSION, FALSE);
2136 s = PoolPrint(L"%s %d.%02d", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
2137 efivar_set(L"LoaderFirmwareInfo", s, FALSE);
2139 s = PoolPrint(L"UEFI %d.%02d", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
2140 efivar_set(L"LoaderFirmwareType", s, FALSE);
2143 err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (void **)&loaded_image,
2144 image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
2145 if (EFI_ERROR(err)) {
2146 Print(L"Error getting a LoadedImageProtocol handle: %r ", err);
2147 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
2151 /* export the device path this image is started from */
2152 device_path = DevicePathFromHandle(loaded_image->DeviceHandle);
2155 EFI_DEVICE_PATH *path, *paths;
2157 str = DevicePathToStr(device_path);
2158 efivar_set(L"LoaderDeviceIdentifier", str, FALSE);
2161 paths = UnpackDevicePath(device_path);
2162 for (path = paths; !IsDevicePathEnd(path); path = NextDevicePathNode(path)) {
2163 HARDDRIVE_DEVICE_PATH *drive;
2166 if (DevicePathType(path) != MEDIA_DEVICE_PATH)
2168 if (DevicePathSubType(path) != MEDIA_HARDDRIVE_DP)
2170 drive = (HARDDRIVE_DEVICE_PATH *)path;
2171 if (drive->SignatureType != SIGNATURE_TYPE_GUID)
2174 GuidToString(uuid, (EFI_GUID *)&drive->Signature);
2175 efivar_set(L"LoaderDevicePartUUID", uuid, FALSE);
2181 root_dir = LibOpenRoot(loaded_image->DeviceHandle);
2183 Print(L"Unable to open root directory: %r ", err);
2184 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
2185 return EFI_LOAD_ERROR;
2188 /* the filesystem path to this image, to prevent adding ourselves to the menu */
2189 loaded_image_path = DevicePathToStr(loaded_image->FilePath);
2190 efivar_set(L"LoaderImageIdentifier", loaded_image_path, FALSE);
2192 /* scan "\loader\entries\*.conf" files */
2193 ZeroMem(&config, sizeof(Config));
2194 config_load(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path);
2196 /* if we find some well-known loaders, add them to the end of the list */
2197 config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
2198 L"auto-windows", 'w', L"Windows Boot Manager", L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi");
2199 config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
2200 L"auto-efi-shell", 's', L"EFI Shell", L"\\shellx64.efi");
2201 config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
2202 L"auto-efi-default", '\0', L"EFI Default Loader", L"\\EFI\\BOOT\\BOOTX64.EFI");
2203 config_entry_add_osx(&config);
2204 efivar_set(L"LoaderEntriesAuto", config.entries_auto, FALSE);
2206 if (efivar_get_raw(&global_guid, L"OsIndicationsSupported", &b, &size) == EFI_SUCCESS) {
2207 UINT64 osind = (UINT64)*b;
2209 if (osind & EFI_OS_INDICATIONS_BOOT_TO_FW_UI)
2210 config_entry_add_call(&config, L"Reboot Into Firmware Interface", reboot_into_firmware);
2214 if (config.entry_count == 0) {
2215 Print(L"No loader found. Configuration files in \\loader\\entries\\*.conf are needed.");
2216 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
2220 config_title_generate(&config);
2222 /* select entry by configured pattern or EFI LoaderDefaultEntry= variable*/
2223 config_default_entry_select(&config);
2225 /* if no configured entry to select from was found, enable the menu */
2226 if (config.idx_default == -1) {
2227 config.idx_default = 0;
2228 if (config.timeout_sec == 0)
2229 config.timeout_sec = 10;
2232 /* select entry or show menu when key is pressed or timeout is set */
2233 if (config.timeout_sec == 0) {
2236 err = key_read(&key, FALSE);
2237 if (!EFI_ERROR(err)) {
2240 /* find matching key in config entries */
2241 idx = entry_lookup_key(&config, config.idx_default, KEYCHAR(key));
2243 config.idx_default = idx;
2253 entry = config.entries[config.idx_default];
2255 efivar_set_time_usec(L"LoaderTimeMenuUSec", 0);
2256 uefi_call_wrapper(BS->SetWatchdogTimer, 4, 0, 0x10000, 0, NULL);
2257 if (!menu_run(&config, &entry, loaded_image_path))
2260 /* run special entry like "reboot" */
2267 /* export the selected boot entry to the system */
2268 efivar_set(L"LoaderEntrySelected", entry->file, FALSE);
2270 uefi_call_wrapper(BS->SetWatchdogTimer, 4, 5 * 60, 0x10000, 0, NULL);
2271 err = image_start(image, &config, entry);
2273 if (err == EFI_ACCESS_DENIED || err == EFI_SECURITY_VIOLATION) {
2274 /* Platform is secure boot and requested image isn't
2275 * trusted. Need to go back to prior boot system and
2276 * install more keys or hashes. Signal failure by
2277 * returning the error */
2278 Print(L"\nImage %s gives a security error\n", entry->title);
2279 Print(L"Please enrol the hash or signature of %s\n", entry->loader);
2280 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
2285 config.timeout_sec = 0;
2289 FreePool(loaded_image_path);
2290 config_free(&config);
2291 uefi_call_wrapper(root_dir->Close, 1, root_dir);
2292 uefi_call_wrapper(BS->CloseProtocol, 4, image, &LoadedImageProtocol, image, NULL);