OSDN Git Service

efibc: EFI Bootloader control driver
authorMatt Gumbel <matthew.k.gumbel@intel.com>
Fri, 18 Jan 2013 17:42:11 +0000 (09:42 -0800)
committerChih-Wei Huang <cwhuang@linux.org.tw>
Sat, 13 Dec 2014 17:04:41 +0000 (01:04 +0800)
Android userspace tells the kernel that it wants to boot into recovery
or some other non-default OS environment by passing a string argument
to reboot(). It is left to the OEM to hook this up to their specific
bootloader.

This driver uses an EFI variable to communicate the next boot target.

It is the bootloader's job to guard against this variable being
uninitialzed or containing invalid data, and just boot normally if that
is the case.

Signed-off-by: Matt Gumbel <matthew.k.gumbel@intel.com>
Signed-off-by: Daniel Leung <daniel.leung@intel.com>
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
drivers/staging/android/Kconfig
drivers/staging/android/Makefile
drivers/staging/android/efibc.c [new file with mode: 0644]

index 7a0e288..20f253f 100644 (file)
@@ -112,6 +112,17 @@ config SW_SYNC_USER
          *WARNING* improper use of this can result in deadlocking kernel
          drivers from userspace.
 
+config EFI_BOOTLOADER_CONTROL
+       tristate "EFI Bootloader Control module"
+       depends on EFI_VARS
+       default n
+       help
+         This driver installs a reboot hook, such that if reboot() is
+         invoked with a string argument NNN, "bootonce-NNN" is copied to
+         the EFI variable, to be read by the bootloader. If the string
+         matches one of the boot labels defined in its configuration,
+         the bootloader will boot once to that label.
+
 source "drivers/staging/android/ion/Kconfig"
 
 endif # if ANDROID
index 517ad5f..65e249c 100644 (file)
@@ -11,3 +11,4 @@ obj-$(CONFIG_ANDROID_LOW_MEMORY_KILLER)       += lowmemorykiller.o
 obj-$(CONFIG_ANDROID_INTF_ALARM_DEV)   += alarm-dev.o
 obj-$(CONFIG_SYNC)                     += sync.o sync_debug.o
 obj-$(CONFIG_SW_SYNC)                  += sw_sync.o
+obj-$(CONFIG_EFI_BOOTLOADER_CONTROL)   += efibc.o
diff --git a/drivers/staging/android/efibc.c b/drivers/staging/android/efibc.c
new file mode 100644 (file)
index 0000000..51b1926
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * efibc: control EFI bootloaders which obey LoaderEntryOneShot var
+ * Copyright (c) 2013, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*
+ * This driver intercepts system reboot requests and populates the
+ * LoaderEntryOneShot EFI variable with the user-supplied reboot argument. EFI
+ * bootloaders such as Gummiboot will consume this variable and use it to
+ * control which OS is booted next.
+ */
+
+#include <linux/efi.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/reboot.h>
+
+#define LOADER_ENTRY_ONE_SHOT "LoaderEntryOneShot"
+#define LOADER_GUID EFI_GUID(0x4a67b082, 0x0a4c, 0x41cf, 0xb6, 0xc7, \
+                               0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f)
+
+/*
+ * Convert char string to efi_char16_t string. Null byte at end is always
+ * preserved.
+ */
+static unsigned long
+efichar_from_char(efi_char16_t *dest, const char *src, size_t dest_len)
+{
+       int i;
+       for (i = 0; src[i] && i < (dest_len / sizeof(*dest)) - 1; i++)
+               dest[i] = src[i];
+       dest[i] = 0; /* Gummiboot does need nul-terminated strings */
+       return i;
+}
+
+/*
+ *  Returns required size of utf16 buffer for given char string. Space for
+ *  trailing nul is included. str must not be NULL.
+ */
+static size_t efi_char16_bufsz(char *str)
+{
+       return (1 + strlen(str)) * sizeof(efi_char16_t);
+}
+
+/*
+ * Set EFI variable to specify next boot target for EFI bootloader. Meant to be
+ * called as a reboot notifier. DO NOT call this function without guaranteeing
+ * efi_enabled == true; it will crash.
+ */
+static int efibc_reboot_notifier_call(
+               struct notifier_block *notifier,
+               unsigned long what, void *data)
+{
+       int ret = NOTIFY_DONE;
+       char *cmd = data;
+       efi_status_t status;
+       efi_char16_t *name_efichar = NULL, *cmd_efichar = NULL;
+       size_t name_efichar_blen, cmd_efichar_blen;
+
+       if (what != SYS_RESTART || cmd == NULL)
+               goto out;
+
+       name_efichar_blen = efi_char16_bufsz(LOADER_ENTRY_ONE_SHOT);
+       cmd_efichar_blen = efi_char16_bufsz(cmd);
+
+       name_efichar = kzalloc(name_efichar_blen, GFP_KERNEL);
+       if (name_efichar == NULL) {
+               pr_err("efibc: %s: failed to allocate memory.\n", __func__);
+               goto out;
+       }
+
+       cmd_efichar = kzalloc(cmd_efichar_blen, GFP_KERNEL);
+       if (cmd_efichar == NULL) {
+               pr_err("efibc: %s: failed to allocate memory.\n", __func__);
+               goto out;
+       }
+
+       if (efichar_from_char(name_efichar, LOADER_ENTRY_ONE_SHOT,
+                       name_efichar_blen) != strlen(LOADER_ENTRY_ONE_SHOT)) {
+               pr_err("efibc: %s: Failed to convert char to efi_char16_t. length=%lu",
+                       __func__, name_efichar_blen);
+               goto out;
+       }
+
+       if (efichar_from_char(cmd_efichar, cmd, cmd_efichar_blen)
+                       != strlen(cmd)) {
+               pr_err("efibc: %s: Failed to convert char to efi_char16_t. length=%lu",
+                       __func__, cmd_efichar_blen);
+               goto out;
+       }
+
+       status = efi.set_variable(
+                       name_efichar,
+                       &LOADER_GUID,
+                       EFI_VARIABLE_NON_VOLATILE
+                               | EFI_VARIABLE_BOOTSERVICE_ACCESS
+                               | EFI_VARIABLE_RUNTIME_ACCESS,
+                       cmd_efichar_blen,
+                       cmd_efichar);
+
+       if (status != EFI_SUCCESS) {
+               pr_err("efibc: set_variable() failed. " "status=%lx\n", status);
+               goto out;
+       }
+
+       ret = NOTIFY_OK;
+out:
+       kfree(cmd_efichar);
+       kfree(name_efichar);
+       return ret;
+}
+
+static struct notifier_block efibc_reboot_notifier = {
+       .notifier_call = efibc_reboot_notifier_call,
+};
+
+static int __init efibc_init(void)
+{
+       int ret;
+
+       if (!efi_enabled(EFI_RUNTIME_SERVICES))
+               return 0;
+
+       ret = register_reboot_notifier(&efibc_reboot_notifier);
+       if (ret) {
+               pr_err("efibc: unable to register reboot notifier\n");
+               return ret;
+       }
+
+       return 0;
+}
+module_init(efibc_init);
+
+static void __exit efibc_exit(void)
+{
+       unregister_reboot_notifier(&efibc_reboot_notifier);
+}
+module_exit(efibc_exit);
+
+MODULE_AUTHOR("Matt Gumbel <matthew.k.gumbel@intel.com");
+MODULE_DESCRIPTION("EFI bootloader communication module");
+MODULE_LICENSE("GPL v2");