+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
/*
* Simple UEFI boot loader which executes configured EFI images, where the
* default entry is selected by a configured pattern (glob) or an on-screen
#include <efi.h>
#include <efilib.h>
+#include "util.h"
+#include "console.h"
+#include "graphics.h"
+
#ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI
#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL
#endif
/* magic string to find in the binary image */
static const char __attribute__((used)) magic[] = "#### LoaderInfo: gummiboot " VERSION " ####";
-/*
- * Allocated random UUID, intended to be shared across tools that implement
- * the (ESP)\loader\entries\<vendor>-<revision>.conf convention and the
- * associated EFI variables.
- */
-static const EFI_GUID loader_guid = { 0x4a67b082, 0x0a4c, 0x41cf, {0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f} };
-
static const EFI_GUID global_guid = EFI_GLOBAL_VARIABLE;
enum loader_type {
enum loader_type type;
CHAR16 *loader;
CHAR16 *options;
+ CHAR16 *splash;
CHAR16 key;
EFI_STATUS (*call)(void);
BOOLEAN no_autoselect;
UINTN timeout_sec_config;
INTN timeout_sec_efivar;
CHAR16 *entry_default_pattern;
+ CHAR16 *splash;
+ EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background;
CHAR16 *entry_oneshot;
CHAR16 *options_edit;
CHAR16 *entries_auto;
} Config;
-static CHAR16 *stra_to_str(CHAR8 *stra);
-
-#ifdef __x86_64__
-static UINT64 ticks_read(void) {
- UINT64 a, d;
- __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
- return (d << 32) | a;
-}
-
-static void cpuid_read(UINT32 info, UINT32 *eax, UINT32 *ebx, UINT32 *ecx, UINT32 *edx) {
- *eax = info;
- __asm__ volatile (
- "mov %%ebx, %%edi;"
- "cpuid;"
- "mov %%ebx, %%esi;"
- "mov %%edi, %%ebx;"
- :"+a" (*eax), "=S" (*ebx), "=c" (*ecx), "=d" (*edx)
- : :"edi"
- );
-}
-
-static UINT64 cpufreq_read(void) {
- UINT32 eax, ebx, ecx, edx;
- union {
- UINT32 i[3][4];
- CHAR8 s[4 * 4 * 3 + 1];
- } brand;
- UINTN i;
- CHAR8 *s;
- UINT64 scale;
- CHAR16 *str;
- static UINT64 usec;
-
- if (usec > 0)
- return usec;
-
- for (i = 0; i < 3; i++) {
- cpuid_read(0x80000002 + i, &eax, &ebx, &ecx, &edx);
- brand.i[i][0] = eax;
- brand.i[i][1] = ebx;
- brand.i[i][2] = ecx;
- brand.i[i][3] = edx;
- }
- brand.s[4 * 4 * 3] = '\0';
-
- /*
- * Extract:
- * “x.xxyHz” or “xxxxyHz”, where y=M,G,T
- * from CPUID brand string:
- * Intel(R) Core(TM) i5-2540M CPU @ 2.60GHz
- *
- * http://www.intel.com/content/dam/www/public/us/en/documents/
- * application-notes/processor-identification-cpuid-instruction-note.pdf
- */
- s = NULL;
- for (i = 4; i < (4 * 4 * 3) - 2; i++) {
- if (brand.s[i+1] == 'H' && brand.s[i+2] == 'z') {
- s = brand.s + i;
- break;
- }
- }
- if (!s)
- return 0;
-
- scale = 1000;
- switch(*s){
- case 'T':
- scale *= 1000;
- case 'G':
- scale *= 1000;
- case 'M':
- scale *= 1000;
- break;
- default:
- return 0;
- }
-
- s -= 4;
- s[4] = '\0';
- if (s[1] == '.') {
- s[1] = s[0];
- s++;
- scale /= 100;
- }
-
- str = stra_to_str(s);
- usec = Atoi(str) * scale;
- FreePool(str);
- return usec;
-}
-
-static UINT64 time_usec(void) {
- UINT64 ticks;
- UINT64 cpufreq;
-
- ticks = ticks_read();
- if (ticks == 0)
- return 0;
-
- cpufreq = cpufreq_read();
- if (cpufreq == 0)
- return 0;
-
- return 1000 * 1000 * ticks / cpufreq;
-}
-#else
-static UINT64 time_usec(void) { return 0; }
-#endif
-
-static EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 *buf, UINTN size, BOOLEAN persistent) {
- UINT32 flags;
-
- flags = EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS;
- if (persistent)
- flags |= EFI_VARIABLE_NON_VOLATILE;
-
- return uefi_call_wrapper(RT->SetVariable, 5, name, (EFI_GUID *)vendor, flags, size, buf);
-}
-
-static EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent) {
- return efivar_set_raw(&loader_guid, name, (CHAR8 *)value, value ? (StrLen(value)+1) * sizeof(CHAR16) : 0, persistent);
-}
-
-static EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, UINTN *size) {
- CHAR8 *buf;
- UINTN l;
- EFI_STATUS err;
-
- l = sizeof(CHAR16 *) * EFI_MAXIMUM_VARIABLE_SIZE;
- buf = AllocatePool(l);
- if (!buf)
- return EFI_OUT_OF_RESOURCES;
-
- err = uefi_call_wrapper(RT->GetVariable, 5, name, (EFI_GUID *)vendor, NULL, &l, buf);
- if (EFI_ERROR(err) == EFI_SUCCESS) {
- *buffer = buf;
- if (size)
- *size = l;
- } else
- FreePool(buf);
- return err;
-
-}
-
-static EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value) {
- CHAR8 *buf;
- CHAR16 *val;
- UINTN size;
- EFI_STATUS err;
-
- err = efivar_get_raw(&loader_guid, name, &buf, &size);
- if (EFI_ERROR(err) != EFI_SUCCESS)
- return err;
-
- val = StrDuplicate((CHAR16 *)buf);
- if (!val) {
- FreePool(val);
- return EFI_OUT_OF_RESOURCES;
- }
-
- *value = val;
- return EFI_SUCCESS;
-}
-
-static EFI_STATUS efivar_set_int(CHAR16 *name, UINTN i, BOOLEAN persistent) {
- CHAR16 str[32];
-
- SPrint(str, 32, L"%d", i);
- return efivar_set(name, str, persistent);
-}
-
-static EFI_STATUS efivar_get_int(CHAR16 *name, UINTN *i) {
- CHAR16 *val;
- EFI_STATUS err;
-
- err = efivar_get(name, &val);
- if (EFI_ERROR(err) == EFI_SUCCESS) {
- *i = Atoi(val);
- FreePool(val);
- }
- return err;
-}
-
-static VOID efivar_set_time_usec(CHAR16 *name, UINT64 usec) {
- CHAR16 str[32];
-
- if (usec == 0)
- usec = time_usec();
- if (usec == 0)
- return;
-
- SPrint(str, 32, L"%ld", usec);
- efivar_set(name, str, FALSE);
-}
-
-#define EFI_SHIFT_STATE_VALID 0x80000000
-#define EFI_RIGHT_CONTROL_PRESSED 0x00000004
-#define EFI_LEFT_CONTROL_PRESSED 0x00000008
-#define EFI_RIGHT_ALT_PRESSED 0x00000010
-#define EFI_LEFT_ALT_PRESSED 0x00000020
-#define EFI_CONTROL_PRESSED (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED)
-#define EFI_ALT_PRESSED (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED)
-#define KEYPRESS(keys, scan, uni) ((((UINT64)keys) << 32) | ((scan) << 16) | (uni))
-#define KEYCHAR(k) ((k) & 0xffff)
-#define CHAR_CTRL(c) ((c) - 'a' + 1)
-
-static EFI_STATUS key_read(UINT64 *key) {
- #define EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID \
- { 0xdd9e7534, 0x7762, 0x4698, { 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa } }
-
- struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL;
-
- typedef EFI_STATUS (EFIAPI *EFI_INPUT_RESET_EX)(
- struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This;
- BOOLEAN ExtendedVerification;
- );
-
- typedef UINT8 EFI_KEY_TOGGLE_STATE;
-
- typedef struct {
- UINT32 KeyShiftState;
- EFI_KEY_TOGGLE_STATE KeyToggleState;
- } EFI_KEY_STATE;
-
- typedef struct {
- EFI_INPUT_KEY Key;
- EFI_KEY_STATE KeyState;
- } EFI_KEY_DATA;
-
- typedef EFI_STATUS (EFIAPI *EFI_INPUT_READ_KEY_EX)(
- struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This;
- EFI_KEY_DATA *KeyData;
- );
-
- typedef EFI_STATUS (EFIAPI *EFI_SET_STATE)(
- struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This;
- EFI_KEY_TOGGLE_STATE *KeyToggleState;
- );
-
- typedef EFI_STATUS (EFIAPI *EFI_KEY_NOTIFY_FUNCTION)(
- EFI_KEY_DATA *KeyData;
- );
-
- typedef EFI_STATUS (EFIAPI *EFI_REGISTER_KEYSTROKE_NOTIFY)(
- struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This;
- EFI_KEY_DATA KeyData;
- EFI_KEY_NOTIFY_FUNCTION KeyNotificationFunction;
- VOID **NotifyHandle;
- );
-
- typedef EFI_STATUS (EFIAPI *EFI_UNREGISTER_KEYSTROKE_NOTIFY)(
- struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This;
- VOID *NotificationHandle;
- );
-
- typedef struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL {
- EFI_INPUT_RESET_EX Reset;
- EFI_INPUT_READ_KEY_EX ReadKeyStrokeEx;
- EFI_EVENT WaitForKeyEx;
- EFI_SET_STATE SetState;
- EFI_REGISTER_KEYSTROKE_NOTIFY RegisterKeyNotify;
- EFI_UNREGISTER_KEYSTROKE_NOTIFY UnregisterKeyNotify;
- } EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL;
-
- EFI_GUID EfiSimpleTextInputExProtocolGuid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID;
- static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *TextInputEx;
- static BOOLEAN checked;
- EFI_KEY_DATA keydata;
- UINT32 shift = 0;
- EFI_STATUS err;
-
- if (!checked) {
- err = LibLocateProtocol(&EfiSimpleTextInputExProtocolGuid, (VOID **)&TextInputEx);
- if (EFI_ERROR(err))
- TextInputEx = NULL;
- checked = TRUE;
- }
-
- if (!TextInputEx) {
- EFI_INPUT_KEY k;
-
- err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &k);
- if (EFI_ERROR(err))
- return err;
- *key = KEYPRESS(0, k.ScanCode, k.UnicodeChar);
- return 0;
- }
-
-
- err = uefi_call_wrapper(TextInputEx->ReadKeyStrokeEx, 2, TextInputEx, &keydata);
- if (EFI_ERROR(err))
- return err;
-
- /* do not distinguish between left and right keys */
- if (keydata.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) {
- if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED))
- shift |= EFI_CONTROL_PRESSED;
- if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED))
- shift |= EFI_ALT_PRESSED;
- };
-
- /* 32 bit modifier keys + 16 bit scan code + 16 bit unicode */
- *key = KEYPRESS(shift, keydata.Key.ScanCode, keydata.Key.UnicodeChar);
- return 0;
-}
-
static void cursor_left(UINTN *cursor, UINTN *first)
{
if ((*cursor) > 0)
enter = FALSE;
exit = FALSE;
while (!exit) {
- UINTN index;
EFI_STATUS err;
UINT64 key;
UINTN i;
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, print);
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
- uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
-
- err = key_read(&key);
+ err = console_key_read(&key, TRUE);
if (EFI_ERROR(err))
continue;
case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'):
case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0):
/* forward-word */
- while(line[first + cursor] && line[first + cursor] == ' ')
+ while (line[first + cursor] && line[first + cursor] == ' ')
cursor_right(&cursor, &first, x_max, len);
- while(line[first + cursor] && line[first + cursor] != ' ')
- cursor_right(&cursor, &first, x_max, len);
- while(line[first + cursor] && line[first + cursor] == ' ')
+ while (line[first + cursor] && line[first + cursor] != ' ')
cursor_right(&cursor, &first, x_max, len);
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
continue;
case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'):
case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0):
/* backward-word */
- while((first + cursor) && line[first + cursor] == ' ')
- cursor_left(&cursor, &first);
- while((first + cursor) && line[first + cursor] != ' ')
+ if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
cursor_left(&cursor, &first);
- while((first + cursor) && line[first + cursor] == ' ')
+ while ((first + cursor) > 0 && line[first + cursor] == ' ')
+ cursor_left(&cursor, &first);
+ }
+ while ((first + cursor) > 0 && line[first + cursor-1] != ' ')
cursor_left(&cursor, &first);
- if (first + cursor != len && first + cursor)
- cursor_right(&cursor, &first, x_max, len);
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
continue;
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
continue;
+ case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'):
+ /* kill-word */
+ clear = 0;
+ for (i = first + cursor; i < len && line[i] == ' '; i++)
+ clear++;
+ for (; i < len && line[i] != ' '; i++)
+ clear++;
+
+ for (i = first + cursor; i + clear < len; i++)
+ line[i] = line[i + clear];
+ len -= clear;
+ line[len] = '\0';
+ continue;
+
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'w'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('w')):
+ case KEYPRESS(EFI_ALT_PRESSED, 0, CHAR_BACKSPACE):
+ /* backward-kill-word */
+ clear = 0;
+ if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
+ cursor_left(&cursor, &first);
+ clear++;
+ while ((first + cursor) > 0 && line[first + cursor] == ' ') {
+ cursor_left(&cursor, &first);
+ clear++;
+ }
+ }
+ while ((first + cursor) > 0 && line[first + cursor-1] != ' ') {
+ cursor_left(&cursor, &first);
+ clear++;
+ }
+ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
+
+ for (i = first + cursor; i + clear < len; i++)
+ line[i] = line[i + clear];
+ len -= clear;
+ line[len] = '\0';
+ continue;
+
case KEYPRESS(0, SCAN_DELETE, 0):
case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'):
case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')):
}
continue;
- case KEYPRESS(0, 0, '\t'):
case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'):
case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff):
if (len+1 == size)
return -1;
}
-static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
- UINTN index;
- EFI_INPUT_KEY key;
+static VOID print_status(Config *config, EFI_FILE *root_dir, CHAR16 *loaded_image_path) {
+ UINT64 key;
UINTN i;
CHAR16 *s;
CHAR8 *b;
UINTN size;
+ EFI_STATUS err;
+ UINTN color = 0;
+ const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *pixel = config->background;
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
+ /* show splash and wait for key */
+ for (;;) {
+ static const EFI_GRAPHICS_OUTPUT_BLT_PIXEL colors[] = {
+ { .Red = 255, .Green = 255, .Blue = 255 },
+ { .Red = 255, .Green = 0, .Blue = 0 },
+ { .Red = 0, .Green = 255, .Blue = 0 },
+ { .Red = 0, .Green = 0, .Blue = 255 },
+ { .Red = 0, .Green = 0, .Blue = 0 },
+ };
+
+ err = EFI_NOT_FOUND;
+ if (config->splash)
+ err = graphics_splash(root_dir, config->splash, pixel);
+ if (EFI_ERROR(err))
+ err = graphics_splash(root_dir, L"\\EFI\\gummiboot\\splash.bmp", pixel);
+ if (EFI_ERROR(err))
+ break;
+
+ /* 'b' rotates through background colors */
+ console_key_read(&key, TRUE);
+ if (key == KEYPRESS(0, 0, 'b')) {
+ pixel = &colors[color++];
+ if (color == ELEMENTSOF(colors))
+ color = 0;
+
+ continue;
+ }
+
+ graphics_mode(FALSE);
+ break;
+ }
+
Print(L"gummiboot version: " VERSION "\n");
Print(L"loaded image: %s\n", loaded_image_path);
Print(L"UEFI version: %d.%02d\n", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
Print(L"timeout (config): %d\n", config->timeout_sec_config);
if (config->entry_default_pattern)
Print(L"default pattern: '%s'\n", config->entry_default_pattern);
+ if (config->splash)
+ Print(L"splash '%s'\n", config->splash);
+ if (config->background)
+ Print(L"background '#%02x%02x%02x'\n",
+ config->background->Red,
+ config->background->Green,
+ config->background->Blue);
Print(L"\n");
Print(L"config entry count: %d\n", config->entry_count);
}
Print(L"\n--- press key ---\n\n");
- uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
- uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
+ console_key_read(&key, TRUE);
for (i = 0; i < config->entry_count; i++) {
ConfigEntry *entry;
- if (key.ScanCode == SCAN_ESC || key.UnicodeChar == 'q')
+ if (key == KEYPRESS(0, SCAN_ESC, 0) || key == KEYPRESS(0, 0, 'q'))
break;
entry = config->entries[i];
+
+ if (entry->splash) {
+ err = graphics_splash(root_dir, entry->splash, config->background);
+ if (!EFI_ERROR(err)) {
+ console_key_read(&key, TRUE);
+ graphics_mode(FALSE);
+ }
+ }
+
Print(L"config entry: %d/%d\n", i+1, config->entry_count);
if (entry->file)
Print(L"file '%s'\n", entry->file);
Print(L"loader '%s'\n", entry->loader);
if (entry->options)
Print(L"options '%s'\n", entry->options);
+ if (entry->splash)
+ Print(L"splash '%s'\n", entry->splash);
Print(L"auto-select %s\n", entry->no_autoselect ? L"no" : L"yes");
if (entry->call)
Print(L"internal call yes\n");
Print(L"\n--- press key ---\n\n");
- uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
- uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
+ console_key_read(&key, TRUE);
}
uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
}
-static EFI_STATUS console_text_mode(VOID) {
- #define EFI_CONSOLE_CONTROL_PROTOCOL_GUID \
- { 0xf42f7782, 0x12e, 0x4c12, { 0x99, 0x56, 0x49, 0xf9, 0x43, 0x4, 0xf7, 0x21 } };
-
- struct _EFI_CONSOLE_CONTROL_PROTOCOL;
-
- typedef enum {
- EfiConsoleControlScreenText,
- EfiConsoleControlScreenGraphics,
- EfiConsoleControlScreenMaxValue,
- } EFI_CONSOLE_CONTROL_SCREEN_MODE;
-
- typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE)(
- struct _EFI_CONSOLE_CONTROL_PROTOCOL *This,
- EFI_CONSOLE_CONTROL_SCREEN_MODE *Mode,
- BOOLEAN *UgaExists,
- BOOLEAN *StdInLocked
- );
-
- typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE)(
- struct _EFI_CONSOLE_CONTROL_PROTOCOL *This,
- EFI_CONSOLE_CONTROL_SCREEN_MODE Mode
- );
-
- typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN)(
- struct _EFI_CONSOLE_CONTROL_PROTOCOL *This,
- CHAR16 *Password
- );
-
- typedef struct _EFI_CONSOLE_CONTROL_PROTOCOL {
- EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE GetMode;
- EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE SetMode;
- EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN LockStdIn;
- } EFI_CONSOLE_CONTROL_PROTOCOL;
-
- EFI_GUID ConsoleControlProtocolGuid = EFI_CONSOLE_CONTROL_PROTOCOL_GUID;
- EFI_CONSOLE_CONTROL_PROTOCOL *ConsoleControl = NULL;
- EFI_STATUS err;
-
- err = LibLocateProtocol(&ConsoleControlProtocolGuid, (VOID **)&ConsoleControl);
- if (EFI_ERROR(err))
- return err;
- return uefi_call_wrapper(ConsoleControl->SetMode, 2, ConsoleControl, EfiConsoleControlScreenText);
-}
-
-static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *loaded_image_path) {
+static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, EFI_FILE *root_dir, CHAR16 *loaded_image_path) {
EFI_STATUS err;
UINTN visible_max;
UINTN idx_highlight;
INT16 idx;
BOOLEAN exit = FALSE;
BOOLEAN run = TRUE;
+ BOOLEAN wait = FALSE;
- console_text_mode();
+ graphics_mode(FALSE);
uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE);
uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE);
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + x + len);
}
- err = key_read(&key);
- if (err != EFI_SUCCESS) {
- UINTN index;
-
+ err = console_key_read(&key, wait);
+ if (EFI_ERROR(err)) {
+ /* timeout reached */
if (timeout_remain == 0) {
exit = TRUE;
break;
}
+
+ /* sleep and update status */
if (timeout_remain > 0) {
uefi_call_wrapper(BS->Stall, 1, 100 * 1000);
timeout_remain--;
continue;
}
- uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
+
+ /* timeout disabled, wait for next key */
+ wait = TRUE;
continue;
}
+
timeout_remain = -1;
/* clear status after keystroke */
break;
case KEYPRESS(0, SCAN_HOME, 0):
+ case KEYPRESS(EFI_ALT_PRESSED, 0, '<'):
if (idx_highlight > 0) {
refresh = TRUE;
idx_highlight = 0;
break;
case KEYPRESS(0, SCAN_END, 0):
+ case KEYPRESS(EFI_ALT_PRESSED, 0, '>'):
if (idx_highlight < config->entry_count-1) {
refresh = TRUE;
idx_highlight = config->entry_count-1;
break;
case KEYPRESS(0, 0, 'P'):
- print_status(config, loaded_image_path);
+ print_status(config, root_dir, loaded_image_path);
+ refresh = TRUE;
+ break;
+
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'l'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('l')):
refresh = TRUE;
break;
return StrCmp(os1, os2);
}
-static INTN utf8_to_16(CHAR8 *stra, CHAR16 *c) {
- CHAR16 unichar;
- UINTN len;
- UINTN i;
-
- if (stra[0] < 0x80)
- len = 1;
- else if ((stra[0] & 0xe0) == 0xc0)
- len = 2;
- else if ((stra[0] & 0xf0) == 0xe0)
- len = 3;
- else if ((stra[0] & 0xf8) == 0xf0)
- len = 4;
- else if ((stra[0] & 0xfc) == 0xf8)
- len = 5;
- else if ((stra[0] & 0xfe) == 0xfc)
- len = 6;
- else
- return -1;
-
- switch (len) {
- case 1:
- unichar = stra[0];
- break;
- case 2:
- unichar = stra[0] & 0x1f;
- break;
- case 3:
- unichar = stra[0] & 0x0f;
- break;
- case 4:
- unichar = stra[0] & 0x07;
- break;
- case 5:
- unichar = stra[0] & 0x03;
- break;
- case 6:
- unichar = stra[0] & 0x01;
- break;
- }
-
- for (i = 1; i < len; i++) {
- if ((stra[i] & 0xc0) != 0x80)
- return -1;
- unichar <<= 6;
- unichar |= stra[i] & 0x3f;
- }
-
- *c = unichar;
- return len;
-}
-
-static CHAR16 *stra_to_str(CHAR8 *stra) {
- UINTN strlen;
- UINTN len;
- UINTN i;
- CHAR16 *str;
-
- len = strlena(stra);
- str = AllocatePool((len + 1) * sizeof(CHAR16));
-
- strlen = 0;
- i = 0;
- while (i < len) {
- INTN utf8len;
-
- utf8len = utf8_to_16(stra + i, str + strlen);
- if (utf8len <= 0) {
- /* invalid utf8 sequence, skip the garbage */
- i++;
- continue;
- }
-
- strlen++;
- i += utf8len;
- }
- str[strlen] = '\0';
- return str;
-}
-
-static CHAR16 *stra_to_path(CHAR8 *stra) {
- CHAR16 *str;
- UINTN strlen;
- UINTN len;
- UINTN i;
-
- len = strlena(stra);
- str = AllocatePool((len + 2) * sizeof(CHAR16));
-
- str[0] = '\\';
- strlen = 1;
- i = 0;
- while (i < len) {
- INTN utf8len;
-
- utf8len = utf8_to_16(stra + i, str + strlen);
- if (utf8len <= 0) {
- /* invalid utf8 sequence, skip the garbage */
- i++;
- continue;
- }
-
- if (str[strlen] == '/')
- str[strlen] = '\\';
- if (str[strlen] == '\\' && str[strlen-1] == '\\') {
- /* skip double slashes */
- i += utf8len;
- continue;
- }
-
- strlen++;
- i += utf8len;
- }
- str[strlen] = '\0';
- return str;
-}
-
-static CHAR8 *strchra(CHAR8 *s, CHAR8 c) {
- do {
- if (*s == c)
- return s;
- } while (*s++);
- return NULL;
-}
-
static CHAR8 *line_get_key_value(CHAR8 *content, UINTN *pos, CHAR8 **key_ret, CHAR8 **value_ret) {
CHAR8 *line;
UINTN linelen;
FreePool(s);
continue;
}
+
if (strcmpa((CHAR8 *)"default", key) == 0) {
config->entry_default_pattern = stra_to_str(value);
StrLwr(config->entry_default_pattern);
continue;
}
+
+ if (strcmpa((CHAR8 *)"splash", key) == 0) {
+ config->splash = stra_to_path(value);
+ continue;
+ }
+
+ if (strcmpa((CHAR8 *)"background", key) == 0) {
+ CHAR16 c[3];
+
+ /* accept #RRGGBB hex notation */
+ if (value[0] != '#')
+ continue;
+ if (value[7] != '\0')
+ continue;
+
+ config->background = AllocateZeroPool(sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
+ if (!config->background)
+ continue;
+
+ c[0] = value[1];
+ c[1] = value[2];
+ c[2] = '\0';
+ config->background->Red = xtoi(c);
+
+ c[0] = value[3];
+ c[1] = value[4];
+ config->background->Green = xtoi(c);
+
+ c[0] = value[5];
+ c[1] = value[6];
+ config->background->Blue = xtoi(c);
+ continue;
+ }
}
}
FreePool(new);
continue;
}
+
+ if (strcmpa((CHAR8 *)"splash", key) == 0) {
+ FreePool(entry->splash);
+ entry->splash = stra_to_path(value);
+ continue;
+ }
}
if (entry->type == LOADER_UNDEFINED) {
config_add_entry(config, entry);
}
-static UINTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, CHAR8 **content) {
- EFI_FILE_HANDLE handle;
- EFI_FILE_INFO *info;
- CHAR8 *buf;
- UINTN buflen;
- EFI_STATUS err;
- UINTN len = 0;
-
- err = uefi_call_wrapper(dir->Open, 5, dir, &handle, name, EFI_FILE_MODE_READ, 0ULL);
- if (EFI_ERROR(err))
- goto out;
-
- info = LibFileInfo(handle);
- buflen = info->FileSize+1;
- buf = AllocatePool(buflen);
-
- err = uefi_call_wrapper(handle->Read, 3, handle, &buflen, buf);
- if (EFI_ERROR(err) == EFI_SUCCESS) {
- buf[buflen] = '\0';
- *content = buf;
- len = buflen;
- } else
- FreePool(buf);
-
- FreePool(info);
- uefi_call_wrapper(handle->Close, 1, handle);
-out:
- return len;
-}
-
static VOID config_load(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path) {
EFI_FILE_HANDLE entries_dir;
EFI_STATUS err;
FreePool(content);
err = efivar_get_int(L"LoaderConfigTimeout", &sec);
- if (EFI_ERROR(err) == EFI_SUCCESS) {
+ if (!EFI_ERROR(err)) {
config->timeout_sec_efivar = sec;
config->timeout_sec = sec;
} else
config->timeout_sec_efivar = -1;
err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &entries_dir, L"\\loader\\entries", EFI_FILE_MODE_READ, 0ULL);
- if (EFI_ERROR(err) == EFI_SUCCESS) {
+ if (!EFI_ERROR(err)) {
for (;;) {
CHAR16 buf[256];
UINTN bufsize;
* next reboot. The variable is always cleared directly after it is read.
*/
err = efivar_get(L"LoaderEntryOneShot", &var);
- if (EFI_ERROR(err) == EFI_SUCCESS) {
+ if (!EFI_ERROR(err)) {
BOOLEAN found = FALSE;
for (i = 0; i < config->entry_count; i++) {
* an '*'.
*/
err = efivar_get(L"LoaderEntryDefault", &var);
- if (EFI_ERROR(err) == EFI_SUCCESS) {
+ if (!EFI_ERROR(err)) {
BOOLEAN found = FALSE;
for (i = 0; i < config->entry_count; i++) {
entry->loader = StrDuplicate(loader);
entry->file = StrDuplicate(file);
StrLwr(entry->file);
- entry->no_autoselect = TRUE;
entry->key = key;
config_add_entry(config, entry);
+
+ /* do not boot right away into aut-detected entries */
+ entry->no_autoselect = TRUE;
+
+ /* do not show a splash; they do not need one, or they draw their own */
+ entry->splash = StrDuplicate(L"");
+
return TRUE;
}
-static VOID config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path,
+static BOOLEAN config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path,
CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) {
if (!config_entry_add_loader(config, device, root_dir, loaded_image_path, file, key, title, loader))
- return;
+ return FALSE;
/* export identifiers of automatically added entries */
if (config->entries_auto) {
config->entries_auto = s;
} else
config->entries_auto = StrDuplicate(file);
+
+ return TRUE;
}
static VOID config_entry_add_osx(Config *config) {
EFI_HANDLE *handles = NULL;
err = LibLocateHandle(ByProtocol, &FileSystemProtocol, NULL, &handle_count, &handles);
- if (EFI_ERROR(err) == EFI_SUCCESS) {
+ if (!EFI_ERROR(err)) {
UINTN i;
for (i = 0; i < handle_count; i++) {
EFI_FILE *root;
+ BOOLEAN found;
root = LibOpenRoot(handles[i]);
if (!root)
continue;
- config_entry_add_loader_auto(config, handles[i], root, NULL, L"auto-osx", 'a', L"OS X",
- L"\\System\\Library\\CoreServices\\boot.efi");
+ found = config_entry_add_loader_auto(config, handles[i], root, NULL, L"auto-osx", 'a', L"OS X",
+ L"\\System\\Library\\CoreServices\\boot.efi");
uefi_call_wrapper(root->Close, 1, root);
+ if (found)
+ break;
}
FreePool(handles);
osind = EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
err = efivar_get_raw(&global_guid, L"OsIndications", &b, &size);
- if (err == EFI_SUCCESS)
+ if (!EFI_ERROR(err))
osind |= (UINT64)*b;
FreePool(b);
err = efivar_set_raw(&global_guid, L"OsIndications", (CHAR8 *)&osind, sizeof(UINT64), TRUE);
- if (err != EFI_SUCCESS)
+ if (EFI_ERROR(err))
return err;
err = uefi_call_wrapper(RT->ResetSystem, 4, EfiResetCold, EFI_SUCCESS, 0, NULL);
FreePool(config->options_edit);
FreePool(config->entry_oneshot);
FreePool(config->entries_auto);
+ FreePool(config->splash);
+ FreePool(config->background);
}
EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
L"auto-windows", 'w', L"Windows Boot Manager", L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi");
config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
- L"auto-efi-shell", 's', L"EFI Shell", L"\\shellx64.efi");
+ L"auto-efi-shell", 's', L"EFI Shell", L"\\shell" MACHINE_TYPE_NAME ".efi");
config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
- L"auto-efi-default", '\0', L"EFI Default Loader", L"\\EFI\\BOOT\\BOOTX64.EFI");
+ L"auto-efi-default", '\0', L"EFI Default Loader", L"\\EFI\\Boot\\boot" MACHINE_TYPE_NAME ".efi");
config_entry_add_osx(&config);
efivar_set(L"LoaderEntriesAuto", config.entries_auto, FALSE);
/* select entry or show menu when key is pressed or timeout is set */
if (config.timeout_sec == 0) {
- EFI_INPUT_KEY k;
+ UINT64 key;
- err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &k);
- if (err == EFI_SUCCESS) {
+ err = console_key_read(&key, FALSE);
+ if (!EFI_ERROR(err)) {
INT16 idx;
/* find matching key in config entries */
- idx = entry_lookup_key(&config, config.idx_default, k.UnicodeChar);
+ idx = entry_lookup_key(&config, config.idx_default, KEYCHAR(key));
if (idx >= 0)
config.idx_default = idx;
else
if (menu) {
efivar_set_time_usec(L"LoaderTimeMenuUSec", 0);
uefi_call_wrapper(BS->SetWatchdogTimer, 4, 0, 0x10000, 0, NULL);
- if (!menu_run(&config, &entry, loaded_image_path))
+ if (!menu_run(&config, &entry, root_dir, loaded_image_path))
break;
/* run special entry like "reboot" */
entry->call();
continue;
}
+ } else {
+ err = EFI_NOT_FOUND;
+
+ /* splash from entry file */
+ if (entry->splash) {
+ /* some entries disable the splash because they draw their own */
+ if (entry->splash[0] == '\0')
+ err = EFI_SUCCESS;
+ else
+ err = graphics_splash(root_dir, entry->splash, config.background);
+ }
+
+ /* splash from config file */
+ if (EFI_ERROR(err) && config.splash)
+ err = graphics_splash(root_dir, config.splash, config.background);
+
+ /* default splash */
+ if (EFI_ERROR(err))
+ graphics_splash(root_dir, L"\\EFI\\gummiboot\\splash.bmp", config.background);
}
/* export the selected boot entry to the system */