efi: Add efi= parameter parsing to the EFI boot stub
authorMatt Fleming <matt.fleming@intel.com>
Tue, 5 Aug 2014 10:52:11 +0000 (11:52 +0100)
committerMatt Fleming <matt.fleming@intel.com>
Fri, 3 Oct 2014 17:40:57 +0000 (18:40 +0100)
We need a way to customize the behaviour of the EFI boot stub, in
particular, we need a way to disable the "chunking" workaround, used
when reading files from the EFI System Partition.

One of my machines doesn't cope well when reading files in 1MB chunks to
a buffer above the 4GB mark - it appears that the "chunking" bug
workaround triggers another firmware bug. This was only discovered with
commit 4bf7111f5016 ("x86/efi: Support initrd loaded above 4G"), and
that commit is perfectly valid. The symptom I observed was a corrupt
initrd rather than any kind of crash.

efi= is now used to specify EFI parameters in two very different
execution environments, the EFI boot stub and during kernel boot.

There is also a slight performance optimization by enabling efi=nochunk,
but that's offset by the fact that you're more likely to run into
firmware issues, at least on x86. This is the rationale behind leaving
the workaround enabled by default.

Also provide some documentation for EFI_READ_CHUNK_SIZE and why we're
using the current value of 1MB.

Tested-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Roy Franz <roy.franz@linaro.org>
Cc: Maarten Lankhorst <m.b.lankhorst@gmail.com>
Cc: Leif Lindholm <leif.lindholm@linaro.org>
Cc: Borislav Petkov <bp@suse.de>
Signed-off-by: Matt Fleming <matt.fleming@intel.com>
Documentation/kernel-parameters.txt
arch/x86/boot/compressed/eboot.c
arch/x86/platform/efi/efi.c
drivers/firmware/efi/libstub/arm-stub.c
drivers/firmware/efi/libstub/efi-stub-helper.c
include/linux/efi.h

index 5ae8608ca9f58a6331cae454ae7a0c84ab2f6c47..67d79597d9d6540310f545b27599982280a64f28 100644 (file)
@@ -992,10 +992,13 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
                        Format: {"off" | "on" | "skip[mbr]"}
 
        efi=            [EFI]
-                       Format: { "old_map" }
+                       Format: { "old_map", "nochunk" }
                        old_map [X86-64]: switch to the old ioremap-based EFI
                        runtime services mapping. 32-bit still uses this one by
                        default.
+                       nochunk: disable reading files in "chunks" in the EFI
+                       boot stub, as chunking can cause problems with some
+                       firmware implementations.
 
        efi_no_storage_paranoia [EFI; X86]
                        Using this parameter you can use more than 50% of
index f277184e2ac1356686d9b200bccf54bdf7bf7f9f..f4bdab1dbf66668b403a350cec8003aba71e6502 100644 (file)
@@ -1100,6 +1100,10 @@ struct boot_params *make_boot_params(struct efi_config *c)
        else
                initrd_addr_max = hdr->initrd_addr_max;
 
+       status = efi_parse_options(cmdline_ptr);
+       if (status != EFI_SUCCESS)
+               goto fail2;
+
        status = handle_cmdline_files(sys_table, image,
                                      (char *)(unsigned long)hdr->cmd_line_ptr,
                                      "initrd=", initrd_addr_max,
index 850da94fef305f65e20bdb52370cc75c1420fd5c..a1f745b0bf1df0b9a5f227caa8dc993d9f4dab08 100644 (file)
@@ -943,8 +943,23 @@ static int __init parse_efi_cmdline(char *str)
        if (*str == '=')
                str++;
 
-       if (!strncmp(str, "old_map", 7))
-               set_bit(EFI_OLD_MEMMAP, &efi.flags);
+       while (*str) {
+               if (!strncmp(str, "old_map", 7)) {
+                       set_bit(EFI_OLD_MEMMAP, &efi.flags);
+                       str += strlen("old_map");
+               }
+
+               /*
+                * Skip any options we don't understand. Presumably
+                * they apply to the EFI boot stub.
+                */
+               while (*str && *str != ',')
+                       str++;
+
+               /* If we hit a delimiter, skip it */
+               if (*str == ',')
+                       str++;
+       }
 
        return 0;
 }
index 480339b6b110db047848d33c010c7c217ddd04b2..75ee05964cbc460fc0b6686a50a1f664fac8c0e7 100644 (file)
@@ -226,6 +226,10 @@ unsigned long __init efi_entry(void *handle, efi_system_table_t *sys_table,
                goto fail_free_image;
        }
 
+       status = efi_parse_options(cmdline_ptr);
+       if (status != EFI_SUCCESS)
+               pr_efi_err(sys_table, "Failed to parse EFI cmdline options\n");
+
        /*
         * Unauthenticated device tree data is a security hazard, so
         * ignore 'dtb=' unless UEFI Secure Boot is disabled.
index 32d5cca30f496d88594de5f74d0d1de9bce5057d..a920fec8fe8856132191e5b08b95c227310078f8 100644 (file)
 
 #include "efistub.h"
 
+/*
+ * Some firmware implementations have problems reading files in one go.
+ * A read chunk size of 1MB seems to work for most platforms.
+ *
+ * Unfortunately, reading files in chunks triggers *other* bugs on some
+ * platforms, so we provide a way to disable this workaround, which can
+ * be done by passing "efi=nochunk" on the EFI boot stub command line.
+ *
+ * If you experience issues with initrd images being corrupt it's worth
+ * trying efi=nochunk, but chunking is enabled by default because there
+ * are far more machines that require the workaround than those that
+ * break with it enabled.
+ */
 #define EFI_READ_CHUNK_SIZE    (1024 * 1024)
 
+static unsigned long __chunk_size = EFI_READ_CHUNK_SIZE;
+
 struct file_info {
        efi_file_handle_t *handle;
        u64 size;
@@ -281,6 +296,49 @@ void efi_free(efi_system_table_t *sys_table_arg, unsigned long size,
        efi_call_early(free_pages, addr, nr_pages);
 }
 
+/*
+ * Parse the ASCII string 'cmdline' for EFI options, denoted by the efi=
+ * option, e.g. efi=nochunk.
+ *
+ * It should be noted that efi= is parsed in two very different
+ * environments, first in the early boot environment of the EFI boot
+ * stub, and subsequently during the kernel boot.
+ */
+efi_status_t efi_parse_options(char *cmdline)
+{
+       char *str;
+
+       /*
+        * If no EFI parameters were specified on the cmdline we've got
+        * nothing to do.
+        */
+       str = strstr(cmdline, "efi=");
+       if (!str)
+               return EFI_SUCCESS;
+
+       /* Skip ahead to first argument */
+       str += strlen("efi=");
+
+       /*
+        * Remember, because efi= is also used by the kernel we need to
+        * skip over arguments we don't understand.
+        */
+       while (*str) {
+               if (!strncmp(str, "nochunk", 7)) {
+                       str += strlen("nochunk");
+                       __chunk_size = -1UL;
+               }
+
+               /* Group words together, delimited by "," */
+               while (*str && *str != ',')
+                       str++;
+
+               if (*str == ',')
+                       str++;
+       }
+
+       return EFI_SUCCESS;
+}
 
 /*
  * Check the cmdline for a LILO-style file= arguments.
@@ -423,8 +481,8 @@ efi_status_t handle_cmdline_files(efi_system_table_t *sys_table_arg,
                        size = files[j].size;
                        while (size) {
                                unsigned long chunksize;
-                               if (size > EFI_READ_CHUNK_SIZE)
-                                       chunksize = EFI_READ_CHUNK_SIZE;
+                               if (size > __chunk_size)
+                                       chunksize = __chunk_size;
                                else
                                        chunksize = size;
 
index 45cb4ffdea621281aa2068676b9cad9115e48c65..518779fb5e90fa38fdc3ee6dd01bdb4a4157c1d7 100644 (file)
@@ -1227,4 +1227,6 @@ efi_status_t handle_cmdline_files(efi_system_table_t *sys_table_arg,
                                  unsigned long *load_addr,
                                  unsigned long *load_size);
 
+efi_status_t efi_parse_options(char *cmdline);
+
 #endif /* _LINUX_EFI_H */
This page took 0.03544 seconds and 5 git commands to generate.