forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
3.4 KiB
147 lines
3.4 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Support for extracting embedded firmware for peripherals from EFI code, |
|
* |
|
* Copyright (c) 2018 Hans de Goede <[email protected]> |
|
*/ |
|
|
|
#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/sha2.h> |
|
|
|
/* Exported for use by lib/test_firmware.c only */ |
|
LIST_HEAD(efi_embedded_fw_list); |
|
EXPORT_SYMBOL_NS_GPL(efi_embedded_fw_list, TEST_FIRMWARE); |
|
bool efi_embedded_fw_checked; |
|
EXPORT_SYMBOL_NS_GPL(efi_embedded_fw_checked, TEST_FIRMWARE); |
|
|
|
static const struct dmi_system_id * const embedded_fw_table[] = { |
|
#ifdef CONFIG_TOUCHSCREEN_DMI |
|
touchscreen_dmi_table, |
|
#endif |
|
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 efi_embedded_fw *fw; |
|
u8 hash[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(map + i, desc->length, hash); |
|
if (memcmp(hash, 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; |
|
} |
|
} |
|
|
|
efi_embedded_fw_checked = true; |
|
} |
|
|
|
int efi_get_embedded_fw(const char *name, const u8 **data, size_t *size) |
|
{ |
|
struct efi_embedded_fw *iter, *fw = NULL; |
|
|
|
if (!efi_embedded_fw_checked) { |
|
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);
|
|
|