summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKay Sievers <kay@vrfy.org>2012-06-28 20:10:31 +0200
committerKay Sievers <kay@vrfy.org>2012-06-28 20:10:31 +0200
commita9f0f13910eddddcc4d48cb6ea44f85bd249a67c (patch)
treeae6b2419460378d760aedc0add753c6f26eb410a
initial import1
-rw-r--r--.gitignore3
-rw-r--r--.vimrc4
-rw-r--r--Makefile50
-rw-r--r--README93
-rw-r--r--gummiboot.c1163
5 files changed, 1313 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..566679a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/*.o
+/gummiboot.so
+/gummiboot.efi
diff --git a/.vimrc b/.vimrc
new file mode 100644
index 0000000..366fbdc
--- /dev/null
+++ b/.vimrc
@@ -0,0 +1,4 @@
+" 'set exrc' in ~/.vimrc will read .vimrc from the current directory
+set tabstop=8
+set shiftwidth=8
+set expandtab
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a368a45
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,50 @@
+VERSION=1
+
+ARCH=$(shell $(CC) -dumpmachine | sed "s/\(-\).*$$//")
+LIBDIR=$(shell echo $$(cd /usr/lib/$$(gcc -print-multi-os-directory); pwd))
+
+CPPFLAGS = \
+ -I. \
+ -I/usr/include/efi \
+ -I/usr/include/efi/$(ARCH)
+
+CFLAGS = \
+ -DVERSION=$(VERSION) \
+ -Wall \
+ -g -O0 \
+ -fpic \
+ -fshort-wchar \
+ -ffreestanding \
+ -DEFI_FUNCTION_WRAPPER
+
+LDFLAGS = -T $(LIBDIR)/gnuefi/elf_$(ARCH)_efi.lds \
+ -shared \
+ -Bsymbolic \
+ -nostdlib \
+ -znocombreloc \
+ -L $(LIBDIR) \
+ $(LIBDIR)/gnuefi/crt0-efi-$(ARCH).o
+
+%.o: %.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
+
+gummiboot.efi: gummiboot.so
+ objcopy -j .text -j .sdata -j .data -j .dynamic \
+ -j .dynsym -j .rel -j .rela -j .reloc \
+ --target=efi-app-$(ARCH) $< $@
+
+gummiboot.so: gummiboot.o
+ $(LD) $(LDFLAGS) gummiboot.o -o $@ -lefi -lgnuefi
+
+clean:
+ rm -f gummiboot.o gummiboot.so gummiboot.efi
+
+test: gummiboot.efi
+ @# UUID=677B-ECF2 /boot2 vfat noauto,x-systemd.automount,x-gvfs-hide 1 3
+ cp -v gummiboot.efi /boot2/EFI/gummiboot/
+ @# unmount to sync EFI partition to disk
+ sync
+ umount /boot2
+ echo 3 > /proc/sys/vm/drop_caches
+ @# run UEFI KVM
+ qemu-kvm -m 512 -L /home/kay/data/bios -snapshot /dev/sda
diff --git a/README b/README
new file mode 100644
index 0000000..831aea9
--- /dev/null
+++ b/README
@@ -0,0 +1,93 @@
+Simple UEFI boot loader which executes configured EFI images, where the
+default entry is selected by a configured pattern (glob) or an on-screen
+menu.
+
+Operates on the EFI System Partition (ESP) only. Configuration file fragments,
+kernels, initrds, other EFI images need to reside on the EFS. Linux kernels
+need to be built with CONFIG_EFI_STUB to be able to be directly executed as
+an EFI image.
+
+Reads simple and entirely generic boot loader configurion files; one
+file per boot loader entry to select from.
+
+Pressing the space (or most other) keys during bootup will show
+an on-screen menu with all configured loader entries to select from.
+Pressing enter on the selected entry loads and starts the EFI image.
+
+If no timeout is configured and no key pressed during bootup, the default
+entry is booted right away.
+
+The config files should be named like:
+ (ESP)/loader/entries/<vendor>-<release>.conf
+
+ $ cat /boot/loader/entries/fedora-3.5.4-1.fc18.x86_64.conf
+ title Fedora 18 (3.5.4-1.fc17.x86_64)
+ linux /fedora/vmlinuz-3.5.4-1.fc17.x86_64
+ initrd /fedora/initrd-3.5.4-1.fc17.x86_64
+ options root=UUID=f8f83f73-df71-445c-87f7-31f70263b83b
+
+ $ cat /boot/loader/entries/custom-kernel.conf
+ title My test Kernel - without initramfs
+ options root=PARTUUID=084917b7-8be2-4e86-838d-f771a9902e08
+ linux /bzImage
+
+ $ cat /boot/loader/entries/redhat-grub.conf
+ title GRUB
+ efi /EFI/redhat/grub2-efi/grub.efi
+
+ $ cat /boot/loader/entries/zzz-efi-shell.conf
+ title EFI Shell
+ efi /EFI/shell/Shell.efi
+
+Paths are automatically converted from '/' to '\', and a leading '\' is
+added if needed.
+
+Loader options can be stored in:
+ (ESP)/loader/loader.conf
+
+ $ cat /boot/loader/loader.conf
+ timeout 10
+ default fedora-*
+
+The timeout is specified in seconds, the default is 0, which suppresses the
+menu. If a timeout is given, in the config file or in the EFI variable
+"LoaderConfigTimeout", the on-screen menu with the default entry selected is
+shown, and a timer counting downwards.
+
+Pressing the '+' or '-' key in the menu will set or clear a custom timeout
+value. The setting is stored persistently in the EFI variable "LoaderConfigTimeout".
+
+The default entry is selected by a pattern match on the configuration file
+names. The matched strings are basenames of the config files translated to
+lowercase and without the ".conf" suffix. The last matching entry in the
+sorted list is selected. If no match pattern is specified, the last entry in
+the list is selected.
+
+This will automatically select the latest Fedora kernel entry:
+ default fedora-*
+
+This will automatically select the latest Fedora 18 kernel:
+ default fedora-*.fc18.*
+
+Pressing the 'd' key in the menu will mark the selected entry as the default
+boot entry. The entry is identified by a leading '*'. Pressing the 'd' key again
+will clear the setting. The setting is stored persistently in the EFI variable
+"LoaderConfigDefault".
+
+Pressing 'o' allows to edit the options for this bootup.
+
+Pressing 'v' shows the loader and EFI version numbers.
+
+Pressing 'F1' will show the available keys.
+
+The EFI variable LoaderEntryOneShot= can be used to specify a loader entry
+for the next and only the next reboot. The variable will always be cleared
+directly after it is read.
+
+Before executing the selected image, the boot entry indentifier is stored in
+the EFI variable "LoaderEntrySelected" and available during runtime at:
+ /sys/firmware/efi/vars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f/
+
+The allocated random UUID 4a67b082-0a4c-41cf-b6c7-440b29bb8c4f is meant to be
+shared across tools which implement the config file layout, format, and the EFI
+variables.
diff --git a/gummiboot.c b/gummiboot.c
new file mode 100644
index 0000000..666c760
--- /dev/null
+++ b/gummiboot.c
@@ -0,0 +1,1163 @@
+/*
+ * Simple UEFI boot loader which executes configured EFI images, where the
+ * default entry is selected by a configured pattern (glob) or an on-screen
+ * menu.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * Copyright (C) 2012 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2012 Harald Hoyer <harald@redhat.com>
+ *
+ * "Any intelligent fool can make things bigger, more complex, and more violent."
+ * -- Albert Einstein
+ */
+
+#include "efi.h"
+#include "efilib.h"
+
+/*
+ * 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} };
+
+enum loader_type {
+ LOADER_UNDEFINED,
+ LOADER_EFI,
+ LOADER_LINUX
+};
+
+typedef struct {
+ CHAR16 *file;
+ CHAR16 *title;
+ enum loader_type type;
+ CHAR16 *loader;
+ CHAR16 *initrd;
+ CHAR16 *options;
+ BOOLEAN no_default;
+} ConfigEntry;
+
+typedef struct {
+ ConfigEntry **entries;
+ UINTN entry_count;
+ UINTN idx_default;
+ INTN idx_default_efivar;
+ UINTN timeout_sec;
+ UINTN timeout_sec_config;
+ INTN timeout_sec_efivar;
+ CHAR16 *entry_default_pattern;
+ CHAR16 *options_edit;
+ BOOLEAN no_initrd;
+} Config;
+
+#ifdef __x86_64__
+static UINT64 tsc() {
+ UINT64 a, d;
+ __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
+ return (d << 32) | a;
+}
+#else
+static UINT64 tsc() { return 0; }
+#endif
+
+static EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, 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, &loader_guid, flags,
+ value ? (StrLen(value)+1) * sizeof(CHAR16) : 0, value);
+}
+
+static EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value) {
+ CHAR16 *val;
+ UINTN size;
+ EFI_STATUS err;
+
+ size = sizeof(CHAR16 *) * EFI_MAXIMUM_VARIABLE_SIZE;
+ val = AllocatePool(size);
+ if (!val)
+ return EFI_OUT_OF_RESOURCES;
+
+ err = uefi_call_wrapper(RT->GetVariable, 5, name, &loader_guid, NULL, &size, val);
+ if (EFI_ERROR(err) == 0)
+ *value = val;
+ else
+ FreePool(val);
+ return err;
+
+}
+
+static EFI_STATUS efivar_set_int(CHAR16 *name, INTN 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, INTN *i) {
+ CHAR16 *val;
+ EFI_STATUS err;
+
+ err = efivar_get(name, &val);
+ if (EFI_ERROR(err) == 0) {
+ *i = Atoi(val);
+ FreePool(val);
+ }
+ return err;
+}
+
+static VOID efivar_set_ticks(CHAR16 *name, UINT64 ticks) {
+ CHAR16 str[32];
+
+ SPrint(str, 32, L"%ld", ticks ? ticks : tsc());
+ efivar_set(name, str, FALSE);
+}
+
+static BOOLEAN edit_line(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN y_pos) {
+ CHAR16 *line;
+ UINTN size;
+ UINTN len;
+ UINTN first;
+ CHAR16 *print;
+ UINTN cursor;
+ BOOLEAN exit;
+ BOOLEAN edit;
+
+ if (!line_in)
+ line_in = L"";
+ size = StrLen(line_in) + 1024;
+ line = AllocatePool(size * sizeof(CHAR16));
+ StrCpy(line, line_in);
+ len = StrLen(line);
+ print = AllocatePool(x_max * sizeof(CHAR16));
+
+ uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE);
+
+ first = 0;
+ cursor = 0;
+ edit = FALSE;
+ exit = FALSE;
+ while (!exit) {
+ UINTN index;
+ EFI_STATUS err;
+ EFI_INPUT_KEY key;
+ UINTN i;
+
+ i = len - first;
+ if (i >= x_max-2)
+ i = x_max-2;
+ CopyMem(print, line + first, i * sizeof(CHAR16));
+ 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);
+ if (EFI_ERROR(err))
+ continue;
+
+ switch (key.ScanCode) {
+ case SCAN_ESC:
+ exit = TRUE;
+ break;
+ case SCAN_HOME:
+ case SCAN_UP:
+ cursor = 0;
+ first = 0;
+ continue;
+ case SCAN_END:
+ case SCAN_DOWN:
+ cursor = len;
+ if (cursor >= x_max) {
+ cursor = x_max-2;
+ first = len - (x_max-2);
+ }
+ continue;
+ case SCAN_RIGHT:
+ if (first + cursor == len)
+ continue;
+ if (cursor+2 < x_max)
+ cursor++;
+ else if (first + cursor < len)
+ first++;
+ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
+ continue;
+ case SCAN_LEFT:
+ if (cursor > 0)
+ cursor--;
+ else if (first > 0)
+ first--;
+ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
+ continue;
+ case SCAN_DELETE:
+ if (len == 0)
+ continue;
+ if (first + cursor == len)
+ continue;
+ for (i = first + cursor; i < len; i++)
+ line[i] = line[i+1];
+ line[len-1] = ' ';
+ len--;
+ continue;
+ }
+
+ switch (key.UnicodeChar) {
+ case CHAR_LINEFEED:
+ case CHAR_CARRIAGE_RETURN:
+ if (StrCmp(line, line_in) != 0) {
+ edit = TRUE;
+ *line_out = line;
+ line = NULL;
+ }
+ exit = TRUE;
+ break;
+ case 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];
+ len--;
+ if (cursor > 0)
+ cursor--;
+ if (cursor > 0 || first == 0)
+ continue;
+ /* show full line if it fits */
+ if (len < x_max-2) {
+ cursor = first;
+ first = 0;
+ continue;
+ }
+ /* jump left to see what we delete */
+ if (first > 10) {
+ first -= 10;
+ cursor = 10;
+ } else {
+ cursor = first;
+ first = 0;
+ }
+ continue;
+ case '\t':
+ case ' ' ... '~':
+ case 0x80 ... 0xffff:
+ if (len+1 == size)
+ continue;
+ for (i = len; i > first + cursor; i--)
+ line[i] = line[i-1];
+ line[first + cursor] = key.UnicodeChar;
+ len++;
+ if (cursor+2 < x_max)
+ cursor++;
+ else if (first + cursor < len)
+ first++;
+ continue;
+ }
+ }
+
+ uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE);
+ FreePool(line);
+ return edit;
+}
+
+static VOID menu_run(Config *config, ConfigEntry **chosen_entry) {
+ EFI_STATUS err;
+ INTN visible_max;
+ INTN idx_highlight;
+ INTN idx_highlight_prev;
+ INTN idx_first;
+ INTN idx_last;
+ BOOLEAN refresh;
+ BOOLEAN highlight;
+ INTN i;
+ UINTN line_width;
+ CHAR16 **lines;
+ UINTN x_max;
+ UINTN y_max;
+ CHAR16 *status;
+ CHAR16 *clearline;
+ INTN timeout_remain;
+ BOOLEAN exit = 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->ClearScreen, 1, ST->ConOut);
+
+ err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x_max, &y_max);
+ if (EFI_ERROR(err)) {
+ x_max = 80;
+ y_max = 25;
+ }
+
+ /* we check 10 times per second for a keystroke */
+ if (config->timeout_sec > 0)
+ timeout_remain = config->timeout_sec * 10;
+ else
+ timeout_remain = -1;
+
+ idx_highlight = config->idx_default;
+ idx_highlight_prev = 0;
+
+ visible_max = y_max - 2;
+
+ if (config->idx_default >= visible_max)
+ idx_first = config->idx_default-1;
+ else
+ idx_first = 0;
+
+ idx_last = idx_first + visible_max-1;
+
+ refresh = TRUE;
+ highlight = FALSE;
+
+ /* length of higlighted selector bar */
+ line_width = 20;
+ for (i = 0; i < config->entry_count; i++) {
+ UINTN entry_len;
+
+ entry_len = StrLen(config->entries[i]->title);
+ if (line_width < entry_len)
+ line_width = entry_len;
+ }
+ if (line_width > x_max)
+ line_width = x_max;
+
+ /* menu entries title lines */
+ lines = AllocatePool(sizeof(CHAR16 *) * config->entry_count);
+ for (i = 0; i < config->entry_count; i++)
+ lines[i] = PoolPrint(L" %-.*s ", line_width, config->entries[i]->title);
+
+ status = NULL;
+ clearline = AllocatePool((x_max+1) * sizeof(CHAR16));
+ for (i = 0; i < x_max; i++)
+ clearline[i] = ' ';
+ clearline[i] = 0;
+
+ while (!exit) {
+ EFI_INPUT_KEY key;
+
+ if (refresh) {
+ for (i = 0; i < config->entry_count; i++) {
+ if (i < idx_first || i > idx_last)
+ continue;
+ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, i - idx_first);
+ if (i == idx_highlight)
+ uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut,
+ EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY);
+ else
+ uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut,
+ EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
+ uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[i]);
+ if (i == config->idx_default_efivar) {
+ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, i - idx_first);
+ uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"*");
+ }
+ }
+ refresh = FALSE;
+ } else if (highlight) {
+ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, idx_highlight_prev - idx_first);
+ uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
+ uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight_prev]);
+ if (idx_highlight_prev == config->idx_default_efivar) {
+ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, idx_highlight_prev - idx_first);
+ uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"*");
+ }
+
+ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, idx_highlight - idx_first);
+ uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY);
+ uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight]);
+ if (idx_highlight == config->idx_default_efivar) {
+ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, idx_highlight - idx_first);
+ uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"*");
+ }
+ highlight = FALSE;
+ }
+
+ if (timeout_remain > 0) {
+ FreePool(status);
+ status = PoolPrint(L"Boot in %d seconds.", (timeout_remain + 5) / 10);
+ }
+
+ /* print status at last line of screen */
+ if (status) {
+ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
+ uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
+ uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, status);
+ uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + StrLen(status));
+ }
+
+ err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
+ if (err == EFI_NOT_READY) {
+ UINTN index;
+
+ if (timeout_remain == 0) {
+ exit = TRUE;
+ break;
+ }
+ 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);
+ continue;
+ }
+ timeout_remain = -1;
+
+ /* clear status after keystroke */
+ if (status) {
+ FreePool(status);
+ status = NULL;
+ uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
+ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
+ uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
+ }
+
+ idx_highlight_prev = idx_highlight;
+
+ switch (key.ScanCode) {
+ case SCAN_UP:
+ if (idx_highlight > 0)
+ idx_highlight--;
+ break;
+ case SCAN_DOWN:
+ if (idx_highlight < config->entry_count-1)
+ idx_highlight++;
+ break;
+ case SCAN_HOME:
+ if (idx_highlight > 0) {
+ refresh = TRUE;
+ idx_highlight = 0;
+ }
+ break;
+ case SCAN_END:
+ if (idx_highlight < config->entry_count-1) {
+ refresh = TRUE;
+ idx_highlight = config->entry_count-1;
+ }
+ break;
+ case SCAN_PAGE_UP:
+ idx_highlight -= visible_max;
+ if (idx_highlight < 0)
+ idx_highlight = 0;
+ break;
+ case SCAN_PAGE_DOWN:
+ idx_highlight += visible_max;
+ if (idx_highlight > config->entry_count-1)
+ idx_highlight = config->entry_count-1;
+ break;
+ case SCAN_F1:
+ status = StrDuplicate(L"(d)efault, (+/-)timeout, (o)ptions, (i)nitrd, (v)ersion");
+ 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;
+
+ if (!refresh && idx_highlight != idx_highlight_prev)
+ highlight = TRUE;
+
+ switch (key.UnicodeChar) {
+ case CHAR_LINEFEED:
+ case CHAR_CARRIAGE_RETURN:
+ exit = TRUE;
+ break;
+ case 'd':
+ if (config->idx_default_efivar != idx_highlight) {
+ /* store the selected entry in a persistent EFI variable */
+ efivar_set(L"LoaderConfigDefault", config->entries[idx_highlight]->file, TRUE);
+ config->idx_default_efivar = idx_highlight;
+ status = StrDuplicate(L"Default boot entry permanently stored.");
+ } else {
+ /* clear the default entry EFI variable */
+ efivar_set(L"LoaderConfigDefault", NULL, TRUE);
+ config->idx_default_efivar = -1;
+ status = StrDuplicate(L"Default boot entry cleared.");
+ }
+ refresh = TRUE;
+ break;
+ case '-':
+ if (config->timeout_sec_efivar > 0) {
+ config->timeout_sec_efivar--;
+ efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE);
+ if (config->timeout_sec_efivar > 0)
+ status = PoolPrint(L"Timeout of %d sec permanently stored.",
+ config->timeout_sec_efivar);
+ else
+ status = StrDuplicate(L"Menu permanently disabled. "
+ "Hold down key at bootup to show menu.");
+ } else if (config->timeout_sec_efivar <= 0){
+ config->timeout_sec_efivar = -1;
+ efivar_set(L"LoaderConfigTimeout", NULL, TRUE);
+ status = PoolPrint(L"Timeout permanantly set to the configured value of %d sec.",
+ config->timeout_sec_config);
+ }
+ break;
+ case '+':
+ config->timeout_sec_efivar++;
+ efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE);
+ if (config->timeout_sec_efivar)
+ status = PoolPrint(L"Timeout of %d sec permanently stored.",
+ config->timeout_sec_efivar);
+ else
+ status = StrDuplicate(L"Menu permanently disabled. "
+ "Hold down key at bootup to show menu.");
+ break;
+ case 'i':
+ if (!config->entries[idx_highlight]->initrd)
+ break;
+ if (config->no_initrd) {
+ config->no_initrd = FALSE;
+ status = StrDuplicate(L"Initrd enabled for this bootup.");
+ } else {
+ config->no_initrd = TRUE;
+ status = StrDuplicate(L"Initrd disabled for this bootup.");
+ }
+ break;
+ case 'o':
+ uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
+ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
+ uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
+ if (edit_line(config->entries[idx_highlight]->options, &config->options_edit, x_max, y_max-1))
+ exit = TRUE;
+ 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':
+ status = PoolPrint(L"gummiboot %d, EFI %d.%02d", VERSION,
+ ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
+ break;
+ }
+ }
+
+ for (i = 0; i < config->entry_count; i++)
+ FreePool(lines[i]);
+ FreePool(lines);
+ FreePool(clearline);
+ *chosen_entry = config->entries[idx_highlight];
+
+ uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_WHITE|EFI_BACKGROUND_BLACK);
+ uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
+}
+
+static VOID config_add_entry(Config *config, ConfigEntry *entry) {
+ if ((config->entry_count & 15) == 0) {
+ UINTN i;
+
+ i = config->entry_count + 16;
+ if (config->entry_count == 0)
+ config->entries = AllocatePool(sizeof(VOID *) * i);
+ else
+ config->entries = ReallocatePool(config->entries,
+ sizeof(VOID *) * config->entry_count, sizeof(VOID *) * i);
+ }
+ config->entries[config->entry_count++] = entry;
+}
+
+static BOOLEAN is_digit(CHAR16 c)
+{
+ return (c >= '0') && (c <= '9');
+}
+
+static UINTN c_order(CHAR16 c)
+{
+ if (c == '\0')
+ return 0;
+ if (is_digit(c))
+ return 0;
+ else if ((c >= 'a') && (c <= 'z'))
+ return c;
+ else
+ return c + 0x10000;
+}
+
+static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2)
+{
+ CHAR16 *os1;
+ CHAR16 *os2;
+
+ os1 = s1;
+ os2 = s2;
+ while (*s1 || *s2) {
+ INTN first;
+
+ while ((*s2 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) {
+ INTN order;
+
+ order = c_order(*s1) - c_order(*s2);
+ if (order)
+ return order;
+ s1++;
+ s2++;
+ }
+
+ while (*s1 == '0')
+ s1++;
+ while (*s2 == '0')
+ s2++;
+
+ first = 0;
+ while (is_digit(*s1) && is_digit(*s2)) {
+ if (first == 0)
+ first = *s1 - *s2;
+ s1++;
+ s2++;
+ }
+
+ if (is_digit(*s1))
+ return 1;
+ if (is_digit(*s2))
+ return -1;
+
+ if (first)
+ return first;
+ }
+
+ return StrCmp(os1, os2);
+}
+
+CHAR16 *stra_to_str(CHAR8 *stra) {
+ UINTN len;
+ UINTN i;
+ CHAR16 *str;
+
+ len = strlena(stra);
+ str = AllocatePool((len + 1) * sizeof(CHAR16));
+
+ for (i = 0; i < len; i++)
+ str[i] = stra[i];
+ str[i] = '\0';
+
+ return str;
+}
+
+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;
+ for (i = 0; i < len; i++) {
+ if (stra[i] == '/' || stra[i] == '\\') {
+ if (str[strlen-1] == '\\')
+ continue;
+ str[strlen++] = '\\';
+ continue;
+ }
+ str[strlen++] = stra[i];
+ }
+ 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 *line, CHAR8 **key_ret, CHAR8 **value_ret) {
+ CHAR8 *next;
+ CHAR8 *key, *value;
+ UINTN linelen;
+
+ /* terminate */
+skip:
+ next = line;
+ while (*next && !strchra((CHAR8 *)"\n\r", *next))
+ next++;
+ *next = '\0';
+
+ linelen = next - line;
+ if (linelen == 0)
+ return NULL;
+
+ /* next line */
+ next++;
+ while (*next && strchra((CHAR8 *)"\n\r", *next))
+ next++;
+
+ /* trailing whitespace */
+ while (linelen && strchra((CHAR8 *)" \t", line[linelen-1]))
+ linelen--;
+ line[linelen] = '\0';
+
+ /* leading whitespace */
+ while (strchra((CHAR8 *)" \t", *line))
+ line++;
+
+ key = line;
+ line = next;
+
+ if (*key == '#')
+ goto skip;
+
+ /* split key/value */
+ value = key;
+ while (*value && !strchra((CHAR8 *)" \t", *value))
+ value++;
+ if (*value == '\0')
+ goto skip;
+ *value = '\0';
+ value++;
+ while (*value && strchra((CHAR8 *)" \t", *value))
+ value++;
+
+ *key_ret = key;
+ *value_ret = value;
+ return next;
+}
+
+static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) {
+ CHAR8 *line;
+ CHAR8 *key, *value;
+
+ line = content;
+ while ((line = line_get_key_value(line, &key, &value))) {
+ if (strcmpa((CHAR8 *)"timeout", key) == 0) {
+ CHAR16 *s;
+
+ s = stra_to_str(value);
+ config->timeout_sec_config = Atoi(s);
+ config->timeout_sec = config->timeout_sec_config;
+ FreePool(s);
+ continue;
+ }
+ if (strcmpa((CHAR8 *)"default", key) == 0) {
+ config->entry_default_pattern = stra_to_str(value);
+ StrLwr(config->entry_default_pattern);
+ continue;
+ }
+ }
+}
+
+static VOID config_entry_add_from_file(Config *config, CHAR16 *file, CHAR8 *content, CHAR16 *loaded_image_path) {
+ ConfigEntry *entry;
+ CHAR8 *line;
+ CHAR8 *key, *value;
+ UINTN len;
+
+ entry = AllocateZeroPool(sizeof(ConfigEntry));
+
+ line = content;
+ while ((line = line_get_key_value(line, &key, &value))) {
+ if (strcmpa((CHAR8 *)"title", key) == 0) {
+ entry->title = stra_to_str(value);
+ continue;
+ }
+
+ if (strcmpa((CHAR8 *)"linux", key) == 0) {
+ entry->type = LOADER_LINUX;
+ entry->loader = stra_to_path(value);
+ continue;
+ }
+
+ if (strcmpa((CHAR8 *)"efi", key) == 0) {
+ entry->type = LOADER_EFI;
+ entry->loader = stra_to_path(value);
+ /* do not add an entry for ourselves */
+ if (StrCmp(entry->loader, loaded_image_path) == 0) {
+ entry->type = LOADER_UNDEFINED;
+ break;
+ }
+ continue;
+ }
+
+ if (strcmpa((CHAR8 *)"initrd", key) == 0) {
+ entry->initrd = stra_to_path(value);
+ continue;
+ }
+
+ if (strcmpa((CHAR8 *)"options", key) == 0) {
+ entry->options = stra_to_str(value);
+ continue;
+ }
+ }
+
+ if (entry->type == LOADER_UNDEFINED) {
+ FreePool(entry->title);
+ FreePool(entry->loader);
+ FreePool(entry->initrd);
+ FreePool(entry->options);
+ FreePool(entry);
+ return;
+ }
+
+ entry->file = StrDuplicate(file);
+ len = StrLen(entry->file);
+ /* remove ".conf" */
+ if (len > 5)
+ entry->file[len - 5] = '\0';
+ StrLwr(entry->file);
+
+ if (!entry->title)
+ entry->title = StrDuplicate(entry->loader);
+
+ config_add_entry(config, entry);
+}
+
+static UINTN file_read(Config *config, EFI_FILE_HANDLE dir, const 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, 0);
+ 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_FILE *root_dir, CHAR16 *loaded_image_path) {
+ EFI_FILE_HANDLE entries_dir;
+ EFI_STATUS err;
+ CHAR8 *content;
+ INTN sec;
+ UINTN len;
+ UINTN i;
+
+ len = file_read(config, root_dir, L"\\loader\\loader.conf", &content);
+ if (len > 0)
+ config_defaults_load_from_file(config, content);
+
+ err = efivar_get_int(L"LoaderConfigTimeout", &sec);
+ if (EFI_ERROR(err) == EFI_SUCCESS) {
+ 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, 0);
+ if (EFI_ERROR(err) == EFI_SUCCESS) {
+ for (;;) {
+ CHAR16 buf[256];
+ UINTN bufsize;
+ EFI_FILE_INFO *f;
+ CHAR8 *content;
+ UINTN len;
+
+ bufsize = sizeof(buf);
+ err = uefi_call_wrapper(entries_dir->Read, 3, entries_dir, &bufsize, buf);
+ if (bufsize == 0 || EFI_ERROR(err))
+ break;
+
+ f = (EFI_FILE_INFO *) buf;
+ if (f->FileName[0] == '.')
+ continue;
+ if (f->Attribute & EFI_FILE_DIRECTORY)
+ continue;
+ len = StrLen(f->FileName);
+ if (len < 6)
+ continue;
+ if (StriCmp(f->FileName + len - 5, L".conf") != 0)
+ continue;
+
+ len = file_read(config, entries_dir, f->FileName, &content);
+ if (len > 0)
+ config_entry_add_from_file(config, f->FileName, content, loaded_image_path);
+ }
+ uefi_call_wrapper(entries_dir->Close, 1, entries_dir);
+ }
+
+ /* sort entries after version number */
+ for (i = 1; i < config->entry_count; i++) {
+ BOOLEAN more;
+ UINTN j;
+
+ more = FALSE;
+ for (j = 0; j < config->entry_count - i; j++) {
+ ConfigEntry *entry;
+
+ if (str_verscmp(config->entries[j]->file, config->entries[j+1]->file) <= 0)
+ continue;
+ entry = config->entries[j];
+ config->entries[j] = config->entries[j+1];
+ config->entries[j+1] = entry;
+ more = TRUE;
+ }
+ if (!more)
+ break;
+ }
+}
+
+static VOID config_default_entry_select(Config *config) {
+ CHAR16 *var;
+ EFI_STATUS err;
+
+ /*
+ * The EFI variable to specify a boot entry for the next, and only the
+ * next reboot. The variable is always cleared directly after it is read.
+ */
+ err = efivar_get(L"LoaderEntryOneShot", &var);
+ if (EFI_ERROR(err) == EFI_SUCCESS) {
+ BOOLEAN found = FALSE;
+ UINTN i;
+
+ for (i = 0; i < config->entry_count; i++) {
+ if (!config->entries[i]->file)
+ continue;
+ if (StrCmp(config->entries[i]->file, var) == 0) {
+ config->idx_default = i;
+ found = TRUE;
+ break;
+ }
+ }
+ efivar_set(L"LoaderEntryOneShot", NULL, TRUE);
+ FreePool(var);
+ if (found)
+ return;
+ }
+
+ /*
+ * The EFI variable to select the default boot entry overrides the
+ * configured pattern. The variable can be set and cleared by pressing
+ * the 'd' key in the loader selection menu, the entry is marked with
+ * an '*'.
+ */
+ err = efivar_get(L"LoaderConfigDefault", &var);
+ if (EFI_ERROR(err) == EFI_SUCCESS) {
+ BOOLEAN found = FALSE;
+ UINTN i;
+
+ for (i = 0; i < config->entry_count; i++) {
+ if (!config->entries[i]->file)
+ continue;
+ if (StrCmp(config->entries[i]->file, var) == 0) {
+ config->idx_default = i;
+ config->idx_default_efivar = i;
+ found = TRUE;
+ break;
+ }
+ }
+ FreePool(var);
+ if (found)
+ return;
+ }
+ config->idx_default_efivar = -1;
+
+ /*
+ * Match the pattern from the end of the list to the start, find last
+ * entry (largest number) matching the given pattern.
+ */
+ if (config->entry_default_pattern) {
+ UINTN i;
+
+ for (i = config->entry_count-1; i >= 0; i--) {
+ if (!config->entries[i]->file)
+ continue;
+ if (config->entries[i]->no_default)
+ continue;
+ if (MetaiMatch(config->entries[i]->file, config->entry_default_pattern)) {
+ config->idx_default = i;
+ return;
+ }
+ }
+ }
+
+ /* select the last entry */
+ if (config->entry_count)
+ config->idx_default = config->entry_count-1;
+}
+
+static VOID config_entry_add_loader(Config *config, EFI_FILE *root_dir, CHAR16 *file, CHAR16 *title, CHAR16 *loader) {
+ EFI_FILE_HANDLE handle;
+ EFI_STATUS err;
+ ConfigEntry *entry;
+
+ /* check existence */
+ err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &handle, loader, EFI_FILE_MODE_READ, 0);
+ if (EFI_ERROR(err))
+ return;
+ uefi_call_wrapper(handle->Close, 1, handle);
+
+ entry = AllocateZeroPool(sizeof(ConfigEntry));
+ entry->title = StrDuplicate(title);
+ entry->loader = StrDuplicate(loader);
+ if (file)
+ entry->file = StrDuplicate(file);
+ entry->no_default = TRUE;
+ config_add_entry(config, entry);
+}
+
+static EFI_STATUS image_start(EFI_HANDLE parent_image, EFI_LOADED_IMAGE *parent_loaded_image,
+ const Config *config, const ConfigEntry *entry) {
+ EFI_STATUS err;
+ EFI_HANDLE image;
+ EFI_DEVICE_PATH *path;
+ EFI_LOADED_IMAGE *loaded_image;
+ CHAR16 *options;
+
+ path = FileDevicePath(parent_loaded_image->DeviceHandle, entry->loader);
+ if (!path) {
+ Print(L"Error getting device path.");
+ uefi_call_wrapper(BS->Stall, 1, 1000 * 1000);
+ return EFI_INVALID_PARAMETER;
+ }
+
+ err = uefi_call_wrapper(BS->LoadImage, 6, FALSE, parent_image, path, NULL, 0, &image);
+ if (EFI_ERROR(err)) {
+ Print(L"Error loading %s: %r", entry->loader, err);
+ uefi_call_wrapper(BS->Stall, 1, 1000 * 1000);
+ goto out;
+ }
+
+ if (config->options_edit)
+ options = config->options_edit;
+ else if (entry->options)
+ options = entry->options;
+ else
+ options = NULL;
+ if (options || (entry->initrd && !config->no_initrd)) {
+ err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, &loaded_image,
+ parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+ if (EFI_ERROR(err)) {
+ Print(L"Error getting LoadedImageProtocol handle: %r", err);
+ uefi_call_wrapper(BS->Stall, 1, 1000 * 1000);
+ goto out_unload;
+ }
+ if (entry->type == LOADER_LINUX && entry->initrd && !config->no_initrd) {
+ if (options)
+ loaded_image->LoadOptions = PoolPrint(L"initrd=%s %s", entry->initrd, options);
+ else
+ loaded_image->LoadOptions = PoolPrint(L"initrd=%s", entry->initrd);
+ } else
+ loaded_image->LoadOptions = options;
+ loaded_image->LoadOptionsSize = (StrLen(loaded_image->LoadOptions)+1) * sizeof(CHAR16);
+ }
+
+ efivar_set_ticks(L"LoaderTicksStartImage", 0);
+ err = uefi_call_wrapper(BS->StartImage, 3, image, NULL, NULL);
+out_unload:
+ uefi_call_wrapper(BS->UnloadImage, 1, image);
+out:
+ FreePool(path);
+ return err;
+}
+
+EFI_STATUS EFIAPI efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
+ EFI_LOADED_IMAGE *loaded_image;
+ EFI_FILE *root_dir;
+ CHAR16 *loaded_image_path;
+ EFI_STATUS err;
+ Config config;
+ UINT64 ticks;
+ BOOLEAN menu = FALSE;
+
+ ticks = tsc();
+ InitializeLib(image, sys_table);
+ efivar_set_ticks(L"LoaderTicksInit", ticks);
+
+ err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, &loaded_image,
+ image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+ if (EFI_ERROR(err)) {
+ Print(L"Error getting a LoadedImageProtocol handle: %r ", err);
+ uefi_call_wrapper(BS->Stall, 1, 1000 * 1000);
+ return err;
+ }
+
+ root_dir = LibOpenRoot(loaded_image->DeviceHandle);
+ if (!root_dir) {
+ Print(L"Unable to open root directory: %r ", err);
+ uefi_call_wrapper(BS->Stall, 1, 1000 * 1000);
+ return EFI_LOAD_ERROR;
+ }
+
+ ZeroMem(&config, sizeof(Config));
+
+ /* scan "\loader\entries\*.conf" files */
+ loaded_image_path = DevicePathToStr(loaded_image->FilePath);
+ config_load(&config, root_dir, loaded_image_path);
+ FreePool(loaded_image_path);
+
+ /* add fallback entry to the end of the list */
+ config_entry_add_loader(&config, root_dir, L"fallback", L"EFI default loader", L"\\EFI\\BOOT\\BOOTX64.EFI");
+
+ /* select entry by configured pattern or EFI LoaderDefaultEntry= variable*/
+ config_default_entry_select(&config);
+
+ /* show menu when key is pressed or timeout is set */
+ if (config.timeout_sec == 0) {
+ EFI_INPUT_KEY key;
+
+ err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
+ menu = err != EFI_NOT_READY;
+ } else
+ menu = TRUE;
+
+ for (;;) {
+ ConfigEntry *entry;
+
+ entry = config.entries[config.idx_default];
+ if (menu) {
+ efivar_set_ticks(L"LoaderTicksStartMenu", 0);
+ menu_run(&config, &entry);
+ }
+
+ /* export the selected boot entry to the system */
+ err = efivar_set(L"LoaderEntrySelected", entry->file, FALSE);
+ if (EFI_ERROR(err)) {
+ Print(L"Error storing LoaderEntrySelected variable: %r ", err);
+ uefi_call_wrapper(BS->Stall, 1, 1000 * 1000);
+ }
+
+ image_start(image, loaded_image, &config, entry);
+
+ menu = TRUE;
+ config.timeout_sec = 0;
+ }
+
+ uefi_call_wrapper(root_dir->Close, 1, root_dir);
+ uefi_call_wrapper(BS->CloseProtocol, 4, image, &LoadedImageProtocol, image, NULL);
+ return EFI_SUCCESS;
+}