Hi Hans, One comment below, which I missed in review before. On 29 April 2018 at 11:35, Hans de Goede <hdegoede@xxxxxxxxxx> wrote: > 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 making this available to peripheral drivers through the > standard firmware loading mechanism. > > 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@xxxxxxxxx> > Suggested-by: Peter Jones <pjones@xxxxxxxxxx> > Acked-by: Ard Biesheuvel <ard.biesheuvel@xxxxxxxxxx> > Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx> > --- [...] > diff --git a/drivers/firmware/efi/embedded-firmware.c b/drivers/firmware/efi/embedded-firmware.c > new file mode 100644 > index 000000000000..22a0f598b53d > --- /dev/null > +++ b/drivers/firmware/efi/embedded-firmware.c > @@ -0,0 +1,149 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Support for extracting embedded firmware for peripherals from EFI code, > + * > + * Copyright (c) 2018 Hans de Goede <hdegoede@xxxxxxxxxx> > + */ > + > +#include <linux/crc32.h> > +#include <linux/dmi.h> > +#include <linux/efi.h> > +#include <linux/efi_embedded_fw.h> > +#include <linux/io.h> > +#include <linux/types.h> > +#include <linux/vmalloc.h> > + > +struct embedded_fw { > + struct list_head list; > + const char *name; > + void *data; > + size_t length; > +}; > + > +static LIST_HEAD(found_fw_list); > + > +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 embedded_fw *fw; > + u64 i, size; > + u32 crc; > + u8 *mem; > + > + size = md->num_pages << EFI_PAGE_SHIFT; > + mem = memremap(md->phys_addr, size, MEMREMAP_WB); > + if (!mem) { > + pr_err("Error mapping EFI mem at %#llx\n", md->phys_addr); > + return -ENOMEM; > + } > + > + size -= desc->length; > + for (i = 0; i < size; i += 8) { > + if (*((u64 *)(mem + i)) != *((u64 *)desc->prefix)) > + continue; > + Please use the proper APIs here to cast u8* to u64*, i.e., either use get_unaligned64() or use memcmp() > + /* Seed with ~0, invert to match crc32 userspace utility */ > + crc = ~crc32(~0, mem + i, desc->length); > + if (crc == desc->crc) > + break; > + } > + > + memunmap(mem); > + > + if (i >= size) > + return -ENOENT; > + > + pr_info("Found EFI embedded fw '%s' crc %08x\n", desc->name, desc->crc); > + > + fw = kmalloc(sizeof(*fw), GFP_KERNEL); > + if (!fw) > + return -ENOMEM; > + > + mem = memremap(md->phys_addr + i, desc->length, MEMREMAP_WB); > + if (!mem) { > + pr_err("Error mapping embedded firmware\n"); > + goto error_free_fw; > + } > + fw->data = kmemdup(mem, desc->length, GFP_KERNEL); > + memunmap(mem); > + if (!fw->data) > + goto error_free_fw; > + > + fw->name = desc->name; > + fw->length = desc->length; > + list_add(&fw->list, &found_fw_list); > + > + return 0; > + > +error_free_fw: > + kfree(fw); > + return -ENOMEM; > +} > + > +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; > + 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; > + } > + } > +} > + > +int efi_get_embedded_fw(const char *name, void **data, size_t *size, > + size_t msize) > +{ > + struct embedded_fw *iter, *fw = NULL; > + void *buf = *data; > + > + list_for_each_entry(iter, &found_fw_list, list) { > + if (strcmp(name, iter->name) == 0) { > + fw = iter; > + break; > + } > + } > + > + if (!fw) > + return -ENOENT; > + > + if (msize && msize < fw->length) > + return -EFBIG; > + > + if (!buf) { > + buf = vmalloc(fw->length); > + if (!buf) > + return -ENOMEM; > + } > + > + memcpy(buf, fw->data, fw->length); > + *size = fw->length; > + *data = buf; > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(efi_get_embedded_fw); > diff --git a/include/linux/efi.h b/include/linux/efi.h > index 791088360c1e..23e8a9c26ce2 100644 > --- a/include/linux/efi.h > +++ b/include/linux/efi.h > @@ -1575,6 +1575,12 @@ static inline void > efi_enable_reset_attack_mitigation(efi_system_table_t *sys_table_arg) { } > #endif > > +#ifdef CONFIG_EFI_EMBEDDED_FIRMWARE > +void efi_check_for_embedded_firmwares(void); > +#else > +static inline void efi_check_for_embedded_firmwares(void) { } > +#endif > + > void efi_retrieve_tpm2_eventlog(efi_system_table_t *sys_table); > > /* > diff --git a/include/linux/efi_embedded_fw.h b/include/linux/efi_embedded_fw.h > new file mode 100644 > index 000000000000..0f7d4df3f57a > --- /dev/null > +++ b/include/linux/efi_embedded_fw.h > @@ -0,0 +1,25 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +#ifndef _LINUX_EFI_EMBEDDED_FW_H > +#define _LINUX_EFI_EMBEDDED_FW_H > + > +#include <linux/mod_devicetable.h> > + > +/** > + * 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 > + * @crc: Inverted little endian Ethernet style CRC32, with 0xffffffff seed > + */ > +struct efi_embedded_fw_desc { > + const char *name; > + u8 prefix[8]; > + u32 length; > + u32 crc; > +}; > + > +int efi_get_embedded_fw(const char *name, void **dat, size_t *sz, size_t msize); > + > +#endif > diff --git a/init/main.c b/init/main.c > index b795aa341a3a..ab29775b35db 100644 > --- a/init/main.c > +++ b/init/main.c > @@ -729,6 +729,9 @@ asmlinkage __visible void __init start_kernel(void) > arch_post_acpi_subsys_init(); > sfi_init_late(); > > + if (efi_enabled(EFI_PRESERVE_BS_REGIONS)) > + efi_check_for_embedded_firmwares(); > + > if (efi_enabled(EFI_RUNTIME_SERVICES)) { > efi_free_boot_services(); > } > -- > 2.17.0 > -- To unsubscribe from this list: send the line "unsubscribe linux-efi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html