cosmetics for 'P' splash loop
[platform/upstream/gummiboot.git] / src / efi / gummiboot.c
index 3ea06c6..90770c8 100644 (file)
@@ -1,3 +1,5 @@
+/*-*- 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 {
@@ -62,6 +61,7 @@ typedef struct {
         enum loader_type type;
         CHAR16 *loader;
         CHAR16 *options;
+        CHAR16 *splash;
         CHAR16 key;
         EFI_STATUS (*call)(void);
         BOOLEAN no_autoselect;
@@ -77,317 +77,13 @@ typedef struct {
         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)
@@ -431,7 +127,6 @@ static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN
         enter = FALSE;
         exit = FALSE;
         while (!exit) {
-                UINTN index;
                 EFI_STATUS err;
                 UINT64 key;
                 UINTN i;
@@ -450,9 +145,7 @@ static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN
                 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;
 
@@ -488,11 +181,9 @@ static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN
                 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;
@@ -501,14 +192,13 @@ static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN
                 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;
 
@@ -530,6 +220,45 @@ static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN
                         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')):
@@ -590,7 +319,6 @@ static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN
                         }
                         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)
@@ -640,17 +368,51 @@ static UINTN entry_lookup_key(Config *config, UINTN start, CHAR16 key) {
         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);
@@ -678,6 +440,13 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
         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);
@@ -704,16 +473,24 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
         }
 
         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);
@@ -739,64 +516,20 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
                         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;
@@ -818,8 +551,9 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
         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);
@@ -957,22 +691,26 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
                         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 */
@@ -1000,6 +738,7 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
                         break;
 
                 case KEYPRESS(0, SCAN_HOME, 0):
+                case KEYPRESS(EFI_ALT_PRESSED, 0, '<'):
                         if (idx_highlight > 0) {
                                 refresh = TRUE;
                                 idx_highlight = 0;
@@ -1007,6 +746,7 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
                         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;
@@ -1110,7 +850,12 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
                         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;
 
@@ -1234,131 +979,6 @@ static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2)
         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;
@@ -1431,11 +1051,45 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) {
                         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;
+                }
         }
 }
 
@@ -1523,6 +1177,12 @@ static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR1
                         FreePool(new);
                         continue;
                 }
+
+                if (strcmpa((CHAR8 *)"splash", key) == 0) {
+                        FreePool(entry->splash);
+                        entry->splash = stra_to_path(value);
+                        continue;
+                }
         }
 
         if (entry->type == LOADER_UNDEFINED) {
@@ -1598,36 +1258,6 @@ static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR1
         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;
@@ -1642,14 +1272,14 @@ static VOID config_load(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir,
         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;
@@ -1712,7 +1342,7 @@ static VOID config_default_entry_select(Config *config) {
          * 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++) {
@@ -1737,7 +1367,7 @@ static VOID config_default_entry_select(Config *config) {
          * 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++) {
@@ -1930,16 +1560,22 @@ static BOOLEAN config_entry_add_loader(Config *config, EFI_HANDLE *device, EFI_F
         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) {
@@ -1950,6 +1586,8 @@ static VOID config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI
                 config->entries_auto = s;
         } else
                 config->entries_auto = StrDuplicate(file);
+
+        return TRUE;
 }
 
 static VOID config_entry_add_osx(Config *config) {
@@ -1958,18 +1596,21 @@ 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);
@@ -2034,12 +1675,12 @@ static EFI_STATUS reboot_into_firmware(VOID) {
         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);
@@ -2058,6 +1699,8 @@ static VOID config_free(Config *config) {
         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) {
@@ -2141,9 +1784,9 @@ 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);
 
@@ -2175,14 +1818,14 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
 
         /* 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
@@ -2198,7 +1841,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
                 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" */
@@ -2206,6 +1849,25 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
                                 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 */