Merge tag 'efi-next' of git://git.kernel.org/pub/scm/linux/kernel/git/efi/efi into...
authorIngo Molnar <mingo@kernel.org>
Sun, 8 Mar 2020 07:59:47 +0000 (08:59 +0100)
committerIngo Molnar <mingo@kernel.org>
Sun, 8 Mar 2020 08:23:36 +0000 (09:23 +0100)
More EFI updates for v5.7

 - Incorporate a stable branch with the EFI pieces of Hans's work on
   loading device firmware from EFI boot service memory regions

Signed-off-by: Ingo Molnar <mingo@kernel.org>
arch/x86/platform/efi/efi.c
arch/x86/platform/efi/quirks.c
drivers/firmware/efi/Kconfig
drivers/firmware/efi/Makefile
drivers/firmware/efi/efi.c
drivers/firmware/efi/embedded-firmware.c [new file with mode: 0644]
include/linux/efi.h
include/linux/efi_embedded_fw.h [new file with mode: 0644]

index aca9bdd..1aae530 100644 (file)
@@ -246,6 +246,7 @@ int __init efi_memblock_x86_reserve_range(void)
             efi.memmap.desc_version);
 
        memblock_reserve(pmap, efi.memmap.nr_map * efi.memmap.desc_size);
+       set_bit(EFI_PRESERVE_BS_REGIONS, &efi.flags);
 
        return 0;
 }
@@ -901,6 +902,7 @@ static void __init __efi_enter_virtual_mode(void)
                goto err;
        }
 
+       efi_check_for_embedded_firmwares();
        efi_free_boot_services();
 
        if (!efi_is_mixed())
index b0e0161..ce82f49 100644 (file)
@@ -410,6 +410,10 @@ void __init efi_free_boot_services(void)
        int num_entries = 0;
        void *new, *new_md;
 
+       /* Keep all regions for /sys/kernel/debug/efi */
+       if (efi_enabled(EFI_DBG))
+               return;
+
        for_each_efi_memory_desc(md) {
                unsigned long long start = md->phys_addr;
                unsigned long long size = md->num_pages << EFI_PAGE_SHIFT;
index ecc83e2..613828d 100644 (file)
@@ -239,6 +239,11 @@ config EFI_DISABLE_PCI_DMA
 
 endmenu
 
+config EFI_EMBEDDED_FIRMWARE
+       bool
+       depends on EFI
+       select CRYPTO_LIB_SHA256
+
 config UEFI_CPER
        bool
 
index 3c5a969..317a05c 100644 (file)
@@ -27,6 +27,7 @@ obj-$(CONFIG_EFI_TEST)                        += test/
 obj-$(CONFIG_EFI_DEV_PATH_PARSER)      += dev-path-parser.o
 obj-$(CONFIG_APPLE_PROPERTIES)         += apple-properties.o
 obj-$(CONFIG_EFI_RCI2_TABLE)           += rci2-table.o
+obj-$(CONFIG_EFI_EMBEDDED_FIRMWARE)    += embedded-firmware.o
 
 fake_map-y                             += fake_mem.o
 fake_map-$(CONFIG_X86)                 += x86_fake_mem.o
index d1746a5..1d5e9a0 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/kobject.h>
 #include <linux/module.h>
 #include <linux/init.h>
+#include <linux/debugfs.h>
 #include <linux/device.h>
 #include <linux/efi.h>
 #include <linux/of.h>
@@ -291,6 +292,59 @@ free_entry:
 static inline int efivar_ssdt_load(void) { return 0; }
 #endif
 
+#ifdef CONFIG_DEBUG_FS
+
+#define EFI_DEBUGFS_MAX_BLOBS 32
+
+static struct debugfs_blob_wrapper debugfs_blob[EFI_DEBUGFS_MAX_BLOBS];
+
+static void __init efi_debugfs_init(void)
+{
+       struct dentry *efi_debugfs;
+       efi_memory_desc_t *md;
+       char name[32];
+       int type_count[EFI_BOOT_SERVICES_DATA + 1] = {};
+       int i = 0;
+
+       efi_debugfs = debugfs_create_dir("efi", NULL);
+       if (IS_ERR_OR_NULL(efi_debugfs))
+               return;
+
+       for_each_efi_memory_desc(md) {
+               switch (md->type) {
+               case EFI_BOOT_SERVICES_CODE:
+                       snprintf(name, sizeof(name), "boot_services_code%d",
+                                type_count[md->type]++);
+                       break;
+               case EFI_BOOT_SERVICES_DATA:
+                       snprintf(name, sizeof(name), "boot_services_data%d",
+                                type_count[md->type]++);
+                       break;
+               default:
+                       continue;
+               }
+
+               if (i >= EFI_DEBUGFS_MAX_BLOBS) {
+                       pr_warn("More then %d EFI boot service segments, only showing first %d in debugfs\n",
+                               EFI_DEBUGFS_MAX_BLOBS, EFI_DEBUGFS_MAX_BLOBS);
+                       break;
+               }
+
+               debugfs_blob[i].size = md->num_pages << EFI_PAGE_SHIFT;
+               debugfs_blob[i].data = memremap(md->phys_addr,
+                                               debugfs_blob[i].size,
+                                               MEMREMAP_WB);
+               if (!debugfs_blob[i].data)
+                       continue;
+
+               debugfs_create_blob(name, 0400, efi_debugfs, &debugfs_blob[i]);
+               i++;
+       }
+}
+#else
+static inline void efi_debugfs_init(void) {}
+#endif
+
 /*
  * We register the efi subsystem with the firmware subsystem and the
  * efivars subsystem with the efi subsystem, if the system was booted with
@@ -357,6 +411,9 @@ static int __init efisubsys_init(void)
                goto err_remove_group;
        }
 
+       if (efi_enabled(EFI_DBG) && efi_enabled(EFI_PRESERVE_BS_REGIONS))
+               efi_debugfs_init();
+
        return 0;
 
 err_remove_group:
diff --git a/drivers/firmware/efi/embedded-firmware.c b/drivers/firmware/efi/embedded-firmware.c
new file mode 100644 (file)
index 0000000..1bc9cda
--- /dev/null
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for extracting embedded firmware for peripherals from EFI code,
+ *
+ * Copyright (c) 2018 Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/dmi.h>
+#include <linux/efi.h>
+#include <linux/efi_embedded_fw.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/vmalloc.h>
+#include <crypto/sha.h>
+
+/* Exported for use by lib/test_firmware.c only */
+LIST_HEAD(efi_embedded_fw_list);
+EXPORT_SYMBOL_GPL(efi_embedded_fw_list);
+
+static bool checked_for_fw;
+
+static const struct dmi_system_id * const embedded_fw_table[] = {
+       NULL
+};
+
+/*
+ * Note the efi_check_for_embedded_firmwares() code currently makes the
+ * following 2 assumptions. This may needs to be revisited if embedded firmware
+ * is found where this is not true:
+ * 1) The firmware is only found in EFI_BOOT_SERVICES_CODE memory segments
+ * 2) The firmware always starts at an offset which is a multiple of 8 bytes
+ */
+static int __init efi_check_md_for_embedded_firmware(
+       efi_memory_desc_t *md, const struct efi_embedded_fw_desc *desc)
+{
+       struct sha256_state sctx;
+       struct efi_embedded_fw *fw;
+       u8 sha256[32];
+       u64 i, size;
+       u8 *map;
+
+       size = md->num_pages << EFI_PAGE_SHIFT;
+       map = memremap(md->phys_addr, size, MEMREMAP_WB);
+       if (!map) {
+               pr_err("Error mapping EFI mem at %#llx\n", md->phys_addr);
+               return -ENOMEM;
+       }
+
+       for (i = 0; (i + desc->length) <= size; i += 8) {
+               if (memcmp(map + i, desc->prefix, EFI_EMBEDDED_FW_PREFIX_LEN))
+                       continue;
+
+               sha256_init(&sctx);
+               sha256_update(&sctx, map + i, desc->length);
+               sha256_final(&sctx, sha256);
+               if (memcmp(sha256, desc->sha256, 32) == 0)
+                       break;
+       }
+       if ((i + desc->length) > size) {
+               memunmap(map);
+               return -ENOENT;
+       }
+
+       pr_info("Found EFI embedded fw '%s'\n", desc->name);
+
+       fw = kmalloc(sizeof(*fw), GFP_KERNEL);
+       if (!fw) {
+               memunmap(map);
+               return -ENOMEM;
+       }
+
+       fw->data = kmemdup(map + i, desc->length, GFP_KERNEL);
+       memunmap(map);
+       if (!fw->data) {
+               kfree(fw);
+               return -ENOMEM;
+       }
+
+       fw->name = desc->name;
+       fw->length = desc->length;
+       list_add(&fw->list, &efi_embedded_fw_list);
+
+       return 0;
+}
+
+void __init efi_check_for_embedded_firmwares(void)
+{
+       const struct efi_embedded_fw_desc *fw_desc;
+       const struct dmi_system_id *dmi_id;
+       efi_memory_desc_t *md;
+       int i, r;
+
+       for (i = 0; embedded_fw_table[i]; i++) {
+               dmi_id = dmi_first_match(embedded_fw_table[i]);
+               if (!dmi_id)
+                       continue;
+
+               fw_desc = dmi_id->driver_data;
+
+               /*
+                * In some drivers the struct driver_data contains may contain
+                * other driver specific data after the fw_desc struct; and
+                * the fw_desc struct itself may be empty, skip these.
+                */
+               if (!fw_desc->name)
+                       continue;
+
+               for_each_efi_memory_desc(md) {
+                       if (md->type != EFI_BOOT_SERVICES_CODE)
+                               continue;
+
+                       r = efi_check_md_for_embedded_firmware(md, fw_desc);
+                       if (r == 0)
+                               break;
+               }
+       }
+
+       checked_for_fw = true;
+}
+
+int efi_get_embedded_fw(const char *name, const u8 **data, size_t *size)
+{
+       struct efi_embedded_fw *iter, *fw = NULL;
+
+       if (!checked_for_fw) {
+               pr_warn("Warning %s called while we did not check for embedded fw\n",
+                       __func__);
+               return -ENOENT;
+       }
+
+       list_for_each_entry(iter, &efi_embedded_fw_list, list) {
+               if (strcmp(name, iter->name) == 0) {
+                       fw = iter;
+                       break;
+               }
+       }
+
+       if (!fw)
+               return -ENOENT;
+
+       *data = fw->data;
+       *size = fw->length;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(efi_get_embedded_fw);
index e8a08a4..abfc98e 100644 (file)
@@ -775,6 +775,7 @@ extern int __init efi_setup_pcdp_console(char *);
 #define EFI_NX_PE_DATA         9       /* Can runtime data regions be mapped non-executable? */
 #define EFI_MEM_ATTR           10      /* Did firmware publish an EFI_MEMORY_ATTRIBUTES table? */
 #define EFI_MEM_NO_SOFT_RESERVE        11      /* Is the kernel configured to ignore soft reservations? */
+#define EFI_PRESERVE_BS_REGIONS        12      /* Are EFI boot-services memory segments available? */
 
 #ifdef CONFIG_EFI
 /*
@@ -1097,6 +1098,12 @@ static inline void
 efi_enable_reset_attack_mitigation(void) { }
 #endif
 
+#ifdef CONFIG_EFI_EMBEDDED_FIRMWARE
+void efi_check_for_embedded_firmwares(void);
+#else
+static inline void efi_check_for_embedded_firmwares(void) { }
+#endif
+
 efi_status_t efi_random_get_seed(void);
 
 void efi_retrieve_tpm2_eventlog(void);
diff --git a/include/linux/efi_embedded_fw.h b/include/linux/efi_embedded_fw.h
new file mode 100644 (file)
index 0000000..3d066c6
--- /dev/null
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_EFI_EMBEDDED_FW_H
+#define _LINUX_EFI_EMBEDDED_FW_H
+
+#include <linux/list.h>
+#include <linux/mod_devicetable.h>
+
+#define EFI_EMBEDDED_FW_PREFIX_LEN             8
+
+/*
+ * This struct and efi_embedded_fw_list are private to the efi-embedded fw
+ * implementation they are in this header for use by lib/test_firmware.c only!
+ */
+struct efi_embedded_fw {
+       struct list_head list;
+       const char *name;
+       const u8 *data;
+       size_t length;
+};
+
+extern struct list_head efi_embedded_fw_list;
+
+/**
+ * struct efi_embedded_fw_desc - This struct is used by the EFI embedded-fw
+ *                               code to search for embedded firmwares.
+ *
+ * @name:   Name to register the firmware with if found
+ * @prefix: First 8 bytes of the firmware
+ * @length: Length of the firmware in bytes including prefix
+ * @sha256: SHA256 of the firmware
+ */
+struct efi_embedded_fw_desc {
+       const char *name;
+       u8 prefix[EFI_EMBEDDED_FW_PREFIX_LEN];
+       u32 length;
+       u8 sha256[32];
+};
+
+int efi_get_embedded_fw(const char *name, const u8 **dat, size_t *sz);
+
+#endif