efi: Add embedded peripheral firmware support
Just like with PCI options ROMs, which we save in the setup_efi_pci* functions from arch/x86/boot/compressed/eboot.c, the EFI code / ROM itself sometimes may contain data which is useful/necessary for peripheral drivers to have access to. Specifically the EFI code may contain an embedded copy of firmware which needs to be (re)loaded into the peripheral. Normally such firmware would be part of linux-firmware, but in some cases this is not feasible, for 2 reasons: 1) The firmware is customized for a specific use-case of the chipset / use with a specific hardware model, so we cannot have a single firmware file for the chipset. E.g. touchscreen controller firmwares are compiled specifically for the hardware model they are used with, as they are calibrated for a specific model digitizer. 2) Despite repeated attempts we have failed to get permission to redistribute the firmware. This is especially a problem with customized firmwares, these get created by the chip vendor for a specific ODM and the copyright may partially belong with the ODM, so the chip vendor cannot give a blanket permission to distribute these. This commit adds support for finding peripheral firmware embedded in the EFI code and makes the found firmware available through the new efi_get_embedded_fw() function. Support for loading these firmwares through the standard firmware loading mechanism is added in a follow-up commit in this patch-series. Note we check the EFI_BOOT_SERVICES_CODE for embedded firmware near the end of start_kernel(), just before calling rest_init(), this is on purpose because the typical EFI_BOOT_SERVICES_CODE memory-segment is too large for early_memremap(), so the check must be done after mm_init(). This relies on EFI_BOOT_SERVICES_CODE not being free-ed until efi_free_boot_services() is called, which means that this will only work on x86 for now. Reported-by: Dave Olsthoorn <dave@bewaar.me> Suggested-by: Peter Jones <pjones@redhat.com> Acked-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> Signed-off-by: Hans de Goede <hdegoede@redhat.com> Link: https://lore.kernel.org/r/20200115163554.101315-3-hdegoede@redhat.com Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
This commit is contained in:
Родитель
0e72a6a3cf
Коммит
f0df68d5ba
|
@ -944,6 +944,7 @@ static void __init __efi_enter_virtual_mode(void)
|
|||
goto err;
|
||||
}
|
||||
|
||||
efi_check_for_embedded_firmwares();
|
||||
efi_free_boot_services();
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -26,6 +26,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
|
||||
|
|
|
@ -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);
|
|
@ -1554,6 +1554,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);
|
||||
|
|
|
@ -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
|
Загрузка…
Ссылка в новой задаче