+/*-*- 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;
BOOLEAN non_unique;
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);
-}
-
-/* remap EFI unicode and EFI scan code pair */
-#define CHAR_CTRL(c) ((c) - 'a' + 1)
-static void key_remap(EFI_INPUT_KEY *key, const EFI_INPUT_KEY *map) {
- UINTN i;
-
- for (i = 0; ; i += 2) {
- /* map unicode char to scan code*/
- if (map[i].UnicodeChar > 0) {
- if (key->UnicodeChar != map[i].UnicodeChar)
- continue;
- key->ScanCode = map[i+1].ScanCode ;
- key->UnicodeChar = 0;
- continue;
- }
-
- /* map scan code to unicode char */
- if (map[i].ScanCode > 0) {
- if (key->ScanCode != map[i].ScanCode)
- continue;
- key->ScanCode = 0 ;
- key->UnicodeChar = map[i+1].UnicodeChar;
- continue;
- }
-
- break;
- }
-}
-
static void cursor_left(UINTN *cursor, UINTN *first)
{
if ((*cursor) > 0)
UINTN first;
CHAR16 *print;
UINTN cursor;
+ UINTN clear;
BOOLEAN exit;
BOOLEAN enter;
- static const EFI_INPUT_KEY keymap[] = {
- { 0, CHAR_CTRL('a') }, { SCAN_HOME, 0 },
- { 0, CHAR_CTRL('e') }, { SCAN_END, 0 },
- { 0, CHAR_CTRL('f') }, { SCAN_DOWN, 0 },
- { 0, CHAR_CTRL('b') }, { SCAN_UP, 0 }
- };
if (!line_in)
line_in = L"";
first = 0;
cursor = 0;
+ clear = 0;
enter = FALSE;
exit = FALSE;
while (!exit) {
- UINTN index;
EFI_STATUS err;
- EFI_INPUT_KEY key;
+ UINT64 key;
UINTN i;
i = len - first;
if (i >= x_max-1)
i = x_max-1;
CopyMem(print, line + first, i * sizeof(CHAR16));
- print[i++] = ' ';
+ while (clear > 0 && i < x_max-1) {
+ clear--;
+ print[i++] = ' ';
+ }
print[i] = '\0';
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_pos);
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 = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
+ err = console_key_read(&key, TRUE);
if (EFI_ERROR(err))
continue;
- key_remap(&key, keymap);
-
- switch (key.ScanCode) {
- case SCAN_ESC:
+ switch (key) {
+ case KEYPRESS(0, SCAN_ESC, 0):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')):
exit = TRUE;
break;
- case SCAN_HOME:
+
+ case KEYPRESS(0, SCAN_HOME, 0):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')):
+ /* beginning-of-line */
cursor = 0;
first = 0;
continue;
- case SCAN_END:
- cursor = len;
+
+ case KEYPRESS(0, SCAN_END, 0):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')):
+ /* end-of-line */
+ cursor = len - first;
if (cursor+1 >= x_max) {
cursor = x_max-1;
first = len - (x_max-1);
}
continue;
- case SCAN_UP:
- while((first + cursor) && line[first + cursor] == ' ')
- cursor_left(&cursor, &first);
- while((first + cursor) && line[first + cursor] != ' ')
- cursor_left(&cursor, &first);
- while((first + cursor) && line[first + cursor] == ' ')
- cursor_left(&cursor, &first);
- if (first + cursor != len && first + cursor)
+
+ case KEYPRESS(0, SCAN_DOWN, 0):
+ case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0):
+ /* forward-word */
+ 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);
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
continue;
- case SCAN_DOWN:
- 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] == ' ')
- cursor_right(&cursor, &first, x_max, len);
+
+ case KEYPRESS(0, SCAN_UP, 0):
+ case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0):
+ /* backward-word */
+ if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
+ cursor_left(&cursor, &first);
+ while ((first + cursor) > 0 && line[first + cursor] == ' ')
+ cursor_left(&cursor, &first);
+ }
+ while ((first + cursor) > 0 && line[first + cursor-1] != ' ')
+ cursor_left(&cursor, &first);
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
continue;
- case SCAN_RIGHT:
+
+ case KEYPRESS(0, SCAN_RIGHT, 0):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')):
+ /* forward-char */
if (first + cursor == len)
continue;
cursor_right(&cursor, &first, x_max, len);
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
continue;
- case SCAN_LEFT:
+
+ case KEYPRESS(0, SCAN_LEFT, 0):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')):
+ /* backward-char */
cursor_left(&cursor, &first);
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
continue;
- case SCAN_DELETE:
+
+ 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')):
if (len == 0)
continue;
if (first + cursor == len)
continue;
for (i = first + cursor; i < len; i++)
line[i] = line[i+1];
- line[len-1] = ' ';
+ clear = 1;
len--;
continue;
- }
- switch (key.UnicodeChar) {
- case CHAR_LINEFEED:
- case CHAR_CARRIAGE_RETURN:
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')):
+ /* kill-line */
+ line[first + cursor] = '\0';
+ clear = len - (first + cursor);
+ len = first + cursor;
+ continue;
+
+ case KEYPRESS(0, 0, CHAR_LINEFEED):
+ case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN):
if (StrCmp(line, line_in) != 0) {
*line_out = line;
line = NULL;
enter = TRUE;
exit = TRUE;
break;
- case CHAR_BACKSPACE:
+
+ case KEYPRESS(0, 0, CHAR_BACKSPACE):
if (len == 0)
continue;
if (first == 0 && cursor == 0)
continue;
for (i = first + cursor-1; i < len; i++)
line[i] = line[i+1];
+ clear = 1;
len--;
if (cursor > 0)
cursor--;
first = 0;
}
continue;
- case '\t':
- case ' ' ... '~':
- case 0x80 ... 0xffff:
+
+ case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'):
+ case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff):
if (len+1 == size)
continue;
for (i = len; i > first + cursor; i--)
line[i] = line[i-1];
- line[first + cursor] = key.UnicodeChar;
+ line[first + cursor] = KEYCHAR(key);
len++;
line[len] = '\0';
if (cursor+1 < x_max)
return enter;
}
-static VOID dump_status(Config *config, CHAR16 *loaded_image_path) {
- UINTN index;
- EFI_INPUT_KEY key;
+static UINTN entry_lookup_key(Config *config, UINTN start, CHAR16 key) {
+ UINTN i;
+
+ if (key == 0)
+ return -1;
+
+ /* select entry by number key */
+ if (key >= '1' && key <= '9') {
+ i = key - '0';
+ if (i > config->entry_count)
+ i = config->entry_count;
+ return i-1;
+ }
+
+ /* find matching key in config entries */
+ for (i = start; i < config->entry_count; i++)
+ if (config->entries[i]->key == key)
+ return i;
+
+ for (i = 0; i < start; i++)
+ if (config->entries[i]->key == key)
+ return i;
+
+ return -1;
+}
+
+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 == 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);
- if (key.ScanCode == SCAN_ESC)
- break;
+ 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;
CHAR16 *status;
CHAR16 *clearline;
INTN timeout_remain;
+ INT16 idx;
BOOLEAN exit = FALSE;
BOOLEAN run = TRUE;
- static const EFI_INPUT_KEY keymap[] = {
- { 0, 'j' }, { SCAN_DOWN, 0 },
- { 0, 'k' }, { SCAN_UP, 0 },
- { SCAN_F1, 0 }, { 0, 'h' },
- { 0, 0 }, { 0, 0 }
- };
-
- console_text_mode();
+ BOOLEAN wait = FALSE;
+
+ 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);
clearline[i] = 0;
while (!exit) {
- EFI_INPUT_KEY key;
+ UINT64 key;
if (refresh) {
for (i = 0; i < config->entry_count; i++) {
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + x + len);
}
- err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &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 */
idx_highlight_prev = idx_highlight;
- key_remap(&key, keymap);
-
- switch (key.ScanCode) {
- case SCAN_UP:
+ switch (key) {
+ case KEYPRESS(0, SCAN_UP, 0):
+ case KEYPRESS(0, 0, 'k'):
if (idx_highlight > 0)
idx_highlight--;
break;
- case SCAN_DOWN:
+
+ case KEYPRESS(0, SCAN_DOWN, 0):
+ case KEYPRESS(0, 0, 'j'):
if (idx_highlight < config->entry_count-1)
idx_highlight++;
break;
- case SCAN_HOME:
+
+ case KEYPRESS(0, SCAN_HOME, 0):
+ case KEYPRESS(EFI_ALT_PRESSED, 0, '<'):
if (idx_highlight > 0) {
refresh = TRUE;
idx_highlight = 0;
}
break;
- case SCAN_END:
+
+ 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 SCAN_PAGE_UP:
+
+ case KEYPRESS(0, SCAN_PAGE_UP, 0):
if (idx_highlight > visible_max)
idx_highlight -= visible_max;
else
idx_highlight = 0;
break;
- case SCAN_PAGE_DOWN:
+
+ case KEYPRESS(0, SCAN_PAGE_DOWN, 0):
idx_highlight += visible_max;
if (idx_highlight > config->entry_count-1)
idx_highlight = config->entry_count-1;
break;
- }
- if (idx_highlight > idx_last) {
- idx_last = idx_highlight;
- idx_first = 1 + idx_highlight - visible_max;
- refresh = TRUE;
- }
- if (idx_highlight < idx_first) {
- idx_first = idx_highlight;
- idx_last = idx_highlight + visible_max-1;
- refresh = TRUE;
- }
- idx_last = idx_first + visible_max-1;
-
- switch (key.UnicodeChar) {
- case CHAR_LINEFEED:
- case CHAR_CARRIAGE_RETURN:
+ case KEYPRESS(0, 0, CHAR_LINEFEED):
+ case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN):
exit = TRUE;
break;
- case 'h':
- status = StrDuplicate(L"(d)efault, (+/-)timeout, (e)dit, (v)ersion (q)uit (*)dump (h)elp");
+
+ case KEYPRESS(0, SCAN_F1, 0):
+ case KEYPRESS(0, 0, 'h'):
+ case KEYPRESS(0, 0, '?'):
+ status = StrDuplicate(L"(d)efault, (t/T)timeout, (e)dit, (v)ersion (Q)uit (P)rint (h)elp");
break;
- case 'q':
+
+ case KEYPRESS(0, 0, 'Q'):
exit = TRUE;
run = FALSE;
break;
- case 'd':
+
+ case KEYPRESS(0, 0, 'd'):
if (config->idx_default_efivar != (INTN)idx_highlight) {
/* store the selected entry in a persistent EFI variable */
efivar_set(L"LoaderEntryDefault", config->entries[idx_highlight]->file, TRUE);
}
refresh = TRUE;
break;
- case '-':
+
+ case KEYPRESS(0, 0, '-'):
+ case KEYPRESS(0, 0, 'T'):
if (config->timeout_sec_efivar > 0) {
config->timeout_sec_efivar--;
efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE);
status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
}
break;
- case '+':
+
+ case KEYPRESS(0, 0, '+'):
+ case KEYPRESS(0, 0, 't'):
if (config->timeout_sec_efivar == -1 && config->timeout_sec_config == 0)
config->timeout_sec_efivar++;
config->timeout_sec_efivar++;
else
status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
break;
- case 'e':
+
+ case KEYPRESS(0, 0, 'e'):
/* only the options of configured entries can be edited */
if (config->entries[idx_highlight]->type == LOADER_UNDEFINED)
break;
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
break;
- case 'v':
+
+ case KEYPRESS(0, 0, 'v'):
status = PoolPrint(L"gummiboot " VERSION ", UEFI %d.%02d, %s %d.%02d",
ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff,
ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
break;
- case '*':
- dump_status(config, loaded_image_path);
+
+ case KEYPRESS(0, 0, 'P'):
+ 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;
+
+ default:
+ /* jump with a hotkey directly to a matching entry */
+ idx = entry_lookup_key(config, idx_highlight+1, KEYCHAR(key));
+ if (idx < 0)
+ break;
+ idx_highlight = idx;
+ refresh = TRUE;
+ }
+
+ if (idx_highlight > idx_last) {
+ idx_last = idx_highlight;
+ idx_first = 1 + idx_highlight - visible_max;
+ refresh = TRUE;
+ }
+ if (idx_highlight < idx_first) {
+ idx_first = idx_highlight;
+ idx_last = idx_highlight + visible_max-1;
+ refresh = TRUE;
}
+ idx_last = idx_first + visible_max-1;
+
if (!refresh && idx_highlight != idx_highlight_prev)
highlight = TRUE;
}
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(entry->loader);
entry->type = LOADER_LINUX;
entry->loader = stra_to_path(value);
+ entry->key = 'l';
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++) {
}
static BOOLEAN config_entry_add_loader(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path,
- CHAR16 *file, CHAR16 *title, CHAR16 *loader) {
+ CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) {
EFI_FILE_HANDLE handle;
EFI_STATUS err;
ConfigEntry *entry;
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,
- CHAR16 *file, CHAR16 *title, CHAR16 *loader) {
- if (!config_entry_add_loader(config, device, root_dir, loaded_image_path, file, title, loader))
- return;
+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 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", 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) {
/* if we find some well-known loaders, add them to the end of the list */
config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
- L"auto-windows", L"Windows Boot Manager", L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi");
+ 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", 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", 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);
config.timeout_sec = 10;
}
- /* show menu when key is pressed or timeout is set */
+ /* select entry or show menu when key is pressed or timeout is set */
if (config.timeout_sec == 0) {
- EFI_INPUT_KEY key;
+ UINT64 key;
- err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
- if (err == EFI_SUCCESS)
- menu = TRUE;
+ 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, KEYCHAR(key));
+ if (idx >= 0)
+ config.idx_default = idx;
+ else
+ menu = TRUE;
+ }
} else
menu = TRUE;
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 */