From 469e1906a1b121ecb0c2ef43f28a98fd9d453831 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Sat, 8 Feb 2020 20:44:07 +0200 Subject: platform: constify properties in platform_device Constify 'struct property_entry *properties' in platform_device. It is always passed around as a pointer const struct. Reviewed-by: Andy Shevchenko Signed-off-by: Tomas Winkler Link: https://lore.kernel.org/r/20200208184407.1294-2-tomas.winkler@intel.com Signed-off-by: Greg Kroah-Hartman --- include/linux/platform_device.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h index 276a03c24691..8e83c6ff140d 100644 --- a/include/linux/platform_device.h +++ b/include/linux/platform_device.h @@ -89,7 +89,7 @@ struct platform_device_info { size_t size_data; u64 dma_mask; - struct property_entry *properties; + const struct property_entry *properties; }; extern struct platform_device *platform_device_register_full( const struct platform_device_info *pdevinfo); -- cgit v1.2.3 From 901cff7cb96140a658a848a568b606ba764239bc Mon Sep 17 00:00:00 2001 From: Topi Miettinen Date: Thu, 23 Jan 2020 14:58:38 +0200 Subject: firmware_loader: load files from the mount namespace of init I have an experimental setup where almost every possible system service (even early startup ones) runs in separate namespace, using a dedicated, minimal file system. In process of minimizing the contents of the file systems with regards to modules and firmware files, I noticed that in my system, the firmware files are loaded from three different mount namespaces, those of systemd-udevd, init and systemd-networkd. The logic of the source namespace is not very clear, it seems to depend on the driver, but the namespace of the current process is used. So, this patch tries to make things a bit clearer and changes the loading of firmware files only from the mount namespace of init. This may also improve security, though I think that using firmware files as attack vector could be too impractical anyway. Later, it might make sense to make the mount namespace configurable, for example with a new file in /proc/sys/kernel/firmware_config/. That would allow a dedicated file system only for firmware files and those need not be present anywhere else. This configurability would make more sense if made also for kernel modules and /sbin/modprobe. Modules are already loaded from init namespace (usermodehelper uses kthreadd namespace) except when directly loaded by systemd-udevd. Instead of using the mount namespace of the current process to load firmware files, use the mount namespace of init process. Link: https://lore.kernel.org/lkml/bb46ebae-4746-90d9-ec5b-fce4c9328c86@gmail.com/ Link: https://lore.kernel.org/lkml/0e3f7653-c59d-9341-9db2-c88f5b988c68@gmail.com/ Signed-off-by: Topi Miettinen Link: https://lore.kernel.org/r/20200123125839.37168-1-toiwoton@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/main.c | 6 +- fs/exec.c | 26 ++++ include/linux/fs.h | 2 + tools/testing/selftests/firmware/Makefile | 9 +- tools/testing/selftests/firmware/fw_namespace.c | 151 +++++++++++++++++++++++ tools/testing/selftests/firmware/fw_run_tests.sh | 4 + 6 files changed, 190 insertions(+), 8 deletions(-) create mode 100644 tools/testing/selftests/firmware/fw_namespace.c (limited to 'include/linux') diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 57133a9dad09..59d1dc322080 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -493,8 +493,10 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, } fw_priv->size = 0; - rc = kernel_read_file_from_path(path, &buffer, &size, - msize, id); + + /* load firmware files from the mount namespace of init */ + rc = kernel_read_file_from_path_initns(path, &buffer, + &size, msize, id); if (rc) { if (rc != -ENOENT) dev_warn(device, "loading %s failed with error %d\n", diff --git a/fs/exec.c b/fs/exec.c index db17be51b112..688c824cdac8 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -985,6 +985,32 @@ int kernel_read_file_from_path(const char *path, void **buf, loff_t *size, } EXPORT_SYMBOL_GPL(kernel_read_file_from_path); +int kernel_read_file_from_path_initns(const char *path, void **buf, + loff_t *size, loff_t max_size, + enum kernel_read_file_id id) +{ + struct file *file; + struct path root; + int ret; + + if (!path || !*path) + return -EINVAL; + + task_lock(&init_task); + get_fs_root(init_task.fs, &root); + task_unlock(&init_task); + + file = file_open_root(root.dentry, root.mnt, path, O_RDONLY, 0); + path_put(&root); + if (IS_ERR(file)) + return PTR_ERR(file); + + ret = kernel_read_file(file, buf, size, max_size, id); + fput(file); + return ret; +} +EXPORT_SYMBOL_GPL(kernel_read_file_from_path_initns); + int kernel_read_file_from_fd(int fd, void **buf, loff_t *size, loff_t max_size, enum kernel_read_file_id id) { diff --git a/include/linux/fs.h b/include/linux/fs.h index 3cd4fe6b845e..f814ccd8d929 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -3012,6 +3012,8 @@ extern int kernel_read_file(struct file *, void **, loff_t *, loff_t, enum kernel_read_file_id); extern int kernel_read_file_from_path(const char *, void **, loff_t *, loff_t, enum kernel_read_file_id); +extern int kernel_read_file_from_path_initns(const char *, void **, loff_t *, loff_t, + enum kernel_read_file_id); extern int kernel_read_file_from_fd(int, void **, loff_t *, loff_t, enum kernel_read_file_id); extern ssize_t kernel_read(struct file *, void *, size_t, loff_t *); diff --git a/tools/testing/selftests/firmware/Makefile b/tools/testing/selftests/firmware/Makefile index 012b2cf69c11..40211cd8f0e6 100644 --- a/tools/testing/selftests/firmware/Makefile +++ b/tools/testing/selftests/firmware/Makefile @@ -1,13 +1,10 @@ # SPDX-License-Identifier: GPL-2.0-only # Makefile for firmware loading selftests - -# No binaries, but make sure arg-less "make" doesn't trigger "run_tests" -all: +CFLAGS = -Wall \ + -O2 TEST_PROGS := fw_run_tests.sh TEST_FILES := fw_fallback.sh fw_filesystem.sh fw_lib.sh +TEST_GEN_FILES := fw_namespace include ../lib.mk - -# Nothing to clean up. -clean: diff --git a/tools/testing/selftests/firmware/fw_namespace.c b/tools/testing/selftests/firmware/fw_namespace.c new file mode 100644 index 000000000000..5ebc1aec7923 --- /dev/null +++ b/tools/testing/selftests/firmware/fw_namespace.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Test triggering of loading of firmware from different mount + * namespaces. Expect firmware to be always loaded from the mount + * namespace of PID 1. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef CLONE_NEWNS +# define CLONE_NEWNS 0x00020000 +#endif + +static char *fw_path = NULL; + +static void die(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + if (fw_path) + unlink(fw_path); + umount("/lib/firmware"); + exit(EXIT_FAILURE); +} + +static void trigger_fw(const char *fw_name, const char *sys_path) +{ + int fd; + + fd = open(sys_path, O_WRONLY); + if (fd < 0) + die("open failed: %s\n", + strerror(errno)); + if (write(fd, fw_name, strlen(fw_name)) != strlen(fw_name)) + exit(EXIT_FAILURE); + close(fd); +} + +static void setup_fw(const char *fw_path) +{ + int fd; + const char fw[] = "ABCD0123"; + + fd = open(fw_path, O_WRONLY | O_CREAT, 0600); + if (fd < 0) + die("open failed: %s\n", + strerror(errno)); + if (write(fd, fw, sizeof(fw) -1) != sizeof(fw) -1) + die("write failed: %s\n", + strerror(errno)); + close(fd); +} + +static bool test_fw_in_ns(const char *fw_name, const char *sys_path, bool block_fw_in_parent_ns) +{ + pid_t child; + + if (block_fw_in_parent_ns) + if (mount("test", "/lib/firmware", "tmpfs", MS_RDONLY, NULL) == -1) + die("blocking firmware in parent ns failed\n"); + + child = fork(); + if (child == -1) { + die("fork failed: %s\n", + strerror(errno)); + } + if (child != 0) { /* parent */ + pid_t pid; + int status; + + pid = waitpid(child, &status, 0); + if (pid == -1) { + die("waitpid failed: %s\n", + strerror(errno)); + } + if (pid != child) { + die("waited for %d got %d\n", + child, pid); + } + if (!WIFEXITED(status)) { + die("child did not terminate cleanly\n"); + } + if (block_fw_in_parent_ns) + umount("/lib/firmware"); + return WEXITSTATUS(status) == EXIT_SUCCESS ? true : false; + } + + if (unshare(CLONE_NEWNS) != 0) { + die("unshare(CLONE_NEWNS) failed: %s\n", + strerror(errno)); + } + if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) == -1) + die("remount root in child ns failed\n"); + + if (!block_fw_in_parent_ns) { + if (mount("test", "/lib/firmware", "tmpfs", MS_RDONLY, NULL) == -1) + die("blocking firmware in child ns failed\n"); + } else + umount("/lib/firmware"); + + trigger_fw(fw_name, sys_path); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + const char *fw_name = "test-firmware.bin"; + char *sys_path; + if (argc != 2) + die("usage: %s sys_path\n", argv[0]); + + /* Mount tmpfs to /lib/firmware so we don't have to assume + that it is writable for us.*/ + if (mount("test", "/lib/firmware", "tmpfs", 0, NULL) == -1) + die("mounting tmpfs to /lib/firmware failed\n"); + + sys_path = argv[1]; + asprintf(&fw_path, "/lib/firmware/%s", fw_name); + + setup_fw(fw_path); + + setvbuf(stdout, NULL, _IONBF, 0); + /* Positive case: firmware in PID1 mount namespace */ + printf("Testing with firmware in parent namespace (assumed to be same file system as PID1)\n"); + if (!test_fw_in_ns(fw_name, sys_path, false)) + die("error: failed to access firmware\n"); + + /* Negative case: firmware in child mount namespace, expected to fail */ + printf("Testing with firmware in child namespace\n"); + if (test_fw_in_ns(fw_name, sys_path, true)) + die("error: firmware access did not fail\n"); + + unlink(fw_path); + free(fw_path); + umount("/lib/firmware"); + exit(EXIT_SUCCESS); +} diff --git a/tools/testing/selftests/firmware/fw_run_tests.sh b/tools/testing/selftests/firmware/fw_run_tests.sh index 8e14d555c197..777377078d5e 100755 --- a/tools/testing/selftests/firmware/fw_run_tests.sh +++ b/tools/testing/selftests/firmware/fw_run_tests.sh @@ -61,6 +61,10 @@ run_test_config_0003() check_mods check_setup +echo "Running namespace test: " +$TEST_DIR/fw_namespace $DIR/trigger_request +echo "OK" + if [ -f $FW_FORCE_SYSFS_FALLBACK ]; then run_test_config_0001 run_test_config_0002 -- cgit v1.2.3 From e92a4eb490cb445fd644cc9a344db3f01b866d29 Mon Sep 17 00:00:00 2001 From: Dietmar Eggemann Date: Tue, 11 Feb 2020 19:15:14 +0100 Subject: drivers base/arch_topology: Remove 'struct sched_domain' forward declaration The sched domain pointer argument from topology_get_freq_scale() and topology_get_cpu_scale() got removed by commit 7673c8a4c75d ("sched/cpufreq: Remove arch_scale_freq_capacity()'s 'sd' parameter") and commit 8ec59c0f5f49 ("sched/topology: Remove unused 'sd' parameter from arch_scale_cpu_capacity()"). So the 'struct sched_domain' forward declaration is no longer needed. Remove it. Signed-off-by: Dietmar Eggemann Reviewed-by: Sudeep Holla Link: https://lore.kernel.org/r/20200211181515.32570-2-dietmar.eggemann@arm.com Signed-off-by: Greg Kroah-Hartman --- include/linux/arch_topology.h | 1 - 1 file changed, 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/arch_topology.h b/include/linux/arch_topology.h index 3015ecbb90b1..f4b1d4fd7efb 100644 --- a/include/linux/arch_topology.h +++ b/include/linux/arch_topology.h @@ -16,7 +16,6 @@ bool topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu); DECLARE_PER_CPU(unsigned long, cpu_scale); -struct sched_domain; static inline unsigned long topology_get_cpu_scale(int cpu) { -- cgit v1.2.3 From 99c73ce158a44a1a11cb146062bc32df0c1d95db Mon Sep 17 00:00:00 2001 From: Dietmar Eggemann Date: Tue, 11 Feb 2020 19:15:15 +0100 Subject: drivers base/arch_topology: Reformat topology_get_[cpu/freq]_scale() function name The storage class and inline definition as well as the return type, function name and parameter list fit all into one line. Signed-off-by: Dietmar Eggemann Reviewed-by: Sudeep Holla Link: https://lore.kernel.org/r/20200211181515.32570-3-dietmar.eggemann@arm.com Signed-off-by: Greg Kroah-Hartman --- include/linux/arch_topology.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/include/linux/arch_topology.h b/include/linux/arch_topology.h index f4b1d4fd7efb..c507e9ddd909 100644 --- a/include/linux/arch_topology.h +++ b/include/linux/arch_topology.h @@ -16,8 +16,7 @@ bool topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu); DECLARE_PER_CPU(unsigned long, cpu_scale); -static inline -unsigned long topology_get_cpu_scale(int cpu) +static inline unsigned long topology_get_cpu_scale(int cpu) { return per_cpu(cpu_scale, cpu); } @@ -26,8 +25,7 @@ void topology_set_cpu_scale(unsigned int cpu, unsigned long capacity); DECLARE_PER_CPU(unsigned long, freq_scale); -static inline -unsigned long topology_get_freq_scale(int cpu) +static inline unsigned long topology_get_freq_scale(int cpu) { return per_cpu(freq_scale, cpu); } -- cgit v1.2.3 From 0e72a6a3cfc3a32273f5e99bfaa4407f4917d343 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 15 Jan 2020 17:35:45 +0100 Subject: efi: Export boot-services code and data as debugfs-blobs Sometimes it is useful to be able to dump the efi boot-services code and data. This commit adds these as debugfs-blobs to /sys/kernel/debug/efi, but only if efi=debug is passed on the kernel-commandline as this requires not freeing those memory-regions, which costs 20+ MB of RAM. Reviewed-by: Greg Kroah-Hartman Acked-by: Ard Biesheuvel Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20200115163554.101315-2-hdegoede@redhat.com Signed-off-by: Ard Biesheuvel --- arch/x86/platform/efi/efi.c | 1 + arch/x86/platform/efi/quirks.c | 4 +++ drivers/firmware/efi/efi.c | 57 ++++++++++++++++++++++++++++++++++++++++++ include/linux/efi.h | 1 + 4 files changed, 63 insertions(+) (limited to 'include/linux') diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c index ae923ee8e2b4..8391d3c658b4 100644 --- a/arch/x86/platform/efi/efi.c +++ b/arch/x86/platform/efi/efi.c @@ -243,6 +243,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; } diff --git a/arch/x86/platform/efi/quirks.c b/arch/x86/platform/efi/quirks.c index 88d32c06cffa..bada1037b711 100644 --- a/arch/x86/platform/efi/quirks.c +++ b/arch/x86/platform/efi/quirks.c @@ -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; diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 621220ab3d0e..201a6799671d 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -325,6 +326,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 @@ -381,6 +435,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/include/linux/efi.h b/include/linux/efi.h index 7efd7072cca5..a6e1a2d8511e 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -1124,6 +1124,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 /* -- cgit v1.2.3 From f0df68d5bae8825ee5b62f00af237ae82247f045 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 15 Jan 2020 17:35:46 +0100 Subject: 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 Suggested-by: Peter Jones Acked-by: Ard Biesheuvel Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20200115163554.101315-3-hdegoede@redhat.com Signed-off-by: Ard Biesheuvel --- arch/x86/platform/efi/efi.c | 1 + drivers/firmware/efi/Kconfig | 5 ++ drivers/firmware/efi/Makefile | 1 + drivers/firmware/efi/embedded-firmware.c | 147 +++++++++++++++++++++++++++++++ include/linux/efi.h | 6 ++ include/linux/efi_embedded_fw.h | 41 +++++++++ 6 files changed, 201 insertions(+) create mode 100644 drivers/firmware/efi/embedded-firmware.c create mode 100644 include/linux/efi_embedded_fw.h (limited to 'include/linux') diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c index 8391d3c658b4..a4ea04603af3 100644 --- a/arch/x86/platform/efi/efi.c +++ b/arch/x86/platform/efi/efi.c @@ -944,6 +944,7 @@ static void __init __efi_enter_virtual_mode(void) goto err; } + efi_check_for_embedded_firmwares(); efi_free_boot_services(); /* diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig index ecc83e2f032c..613828d3f106 100644 --- a/drivers/firmware/efi/Kconfig +++ b/drivers/firmware/efi/Kconfig @@ -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 diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile index 554d795270d9..256d6121b2b3 100644 --- a/drivers/firmware/efi/Makefile +++ b/drivers/firmware/efi/Makefile @@ -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 diff --git a/drivers/firmware/efi/embedded-firmware.c b/drivers/firmware/efi/embedded-firmware.c new file mode 100644 index 000000000000..1bc9cdae2eed --- /dev/null +++ b/drivers/firmware/efi/embedded-firmware.c @@ -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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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); diff --git a/include/linux/efi.h b/include/linux/efi.h index a6e1a2d8511e..23392b88bcc0 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -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); diff --git a/include/linux/efi_embedded_fw.h b/include/linux/efi_embedded_fw.h new file mode 100644 index 000000000000..3d066c6370c6 --- /dev/null +++ b/include/linux/efi_embedded_fw.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_EFI_EMBEDDED_FW_H +#define _LINUX_EFI_EMBEDDED_FW_H + +#include +#include + +#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 -- cgit v1.2.3 From 8375e74f2bca9802a4ddf431a6d7bd2ab9950f27 Mon Sep 17 00:00:00 2001 From: Saravana Kannan Date: Fri, 21 Feb 2020 17:40:35 -0800 Subject: driver core: Add fw_devlink kernel commandline option fwnode_operations.add_links allows creating device links from information provided by firmware. fwnode_operations.add_links is currently implemented only by OF/devicetree code and a specific case of efi. However, there's nothing preventing ACPI or other firmware types from implementing it. The OF implementation is currently controlled by a kernel commandline parameter called of_devlink. Since this feature is generic isn't limited to OF, add a generic fw_devlink kernel commandline parameter to control this feature across firmware types. Signed-off-by: Saravana Kannan Link: https://lore.kernel.org/r/20200222014038.180923-3-saravanak@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/admin-guide/kernel-parameters.txt | 18 +++++++++++++++++ drivers/base/core.c | 27 ++++++++++++++++++++++++- include/linux/fwnode.h | 2 ++ 3 files changed, 46 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index dbc22d684627..29985152b66d 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1350,6 +1350,24 @@ can be changed at run time by the max_graph_depth file in the tracefs tracing directory. default: 0 (no limit) + fw_devlink= [KNL] Create device links between consumer and supplier + devices by scanning the firmware to infer the + consumer/supplier relationships. This feature is + especially useful when drivers are loaded as modules as + it ensures proper ordering of tasks like device probing + (suppliers first, then consumers), supplier boot state + clean up (only after all consumers have probed), + suspend/resume & runtime PM (consumers first, then + suppliers). + Format: { off | permissive | on | rpm } + off -- Don't create device links from firmware info. + permissive -- Create device links from firmware info + but use it only for ordering boot state clean + up (sync_state() calls). + on -- Create device links from firmware info and use it + to enforce probe and suspend/resume ordering. + rpm -- Like "on", but also use to order runtime PM. + gamecon.map[2|3]= [HW,JOY] Multisystem joystick and NES/SNES/PSX pad support via parallel port (up to 5 devices per port) diff --git a/drivers/base/core.c b/drivers/base/core.c index 79863354c801..922680af9051 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -2332,6 +2332,31 @@ static int device_private_init(struct device *dev) return 0; } +u32 fw_devlink_flags; +static int __init fw_devlink_setup(char *arg) +{ + if (!arg) + return -EINVAL; + + if (strcmp(arg, "off") == 0) { + fw_devlink_flags = 0; + } else if (strcmp(arg, "permissive") == 0) { + fw_devlink_flags = DL_FLAG_SYNC_STATE_ONLY; + } else if (strcmp(arg, "on") == 0) { + fw_devlink_flags = DL_FLAG_AUTOPROBE_CONSUMER; + } else if (strcmp(arg, "rpm") == 0) { + fw_devlink_flags = DL_FLAG_AUTOPROBE_CONSUMER | + DL_FLAG_PM_RUNTIME; + } + return 0; +} +early_param("fw_devlink", fw_devlink_setup); + +u32 fw_devlink_get_flags(void) +{ + return fw_devlink_flags; +} + /** * device_add - add device to device hierarchy. * @dev: device. @@ -2480,7 +2505,7 @@ int device_add(struct device *dev) */ device_link_add_missing_supplier_links(); - if (fwnode_has_op(dev->fwnode, add_links)) { + if (fw_devlink_flags && fwnode_has_op(dev->fwnode, add_links)) { fw_ret = fwnode_call_int_op(dev->fwnode, add_links, dev); if (fw_ret == -ENODEV) device_link_wait_for_mandatory_supplier(dev); diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h index 8feeb94b8acc..e0abafbb17f8 100644 --- a/include/linux/fwnode.h +++ b/include/linux/fwnode.h @@ -170,4 +170,6 @@ struct fwnode_operations { } while (false) #define get_dev_from_fwnode(fwnode) get_device((fwnode)->dev) +extern u32 fw_devlink_get_flags(void); + #endif -- cgit v1.2.3 From 0e9f8d09d2806aa2e10a04a78f82544e4ee737a1 Mon Sep 17 00:00:00 2001 From: John Stultz Date: Tue, 25 Feb 2020 05:08:26 +0000 Subject: driver core: Remove driver_deferred_probe_check_state_continue() Now that driver_deferred_probe_check_state() works better, and we've converted the only user of driver_deferred_probe_check_state_continue() we can simply remove it and simplify some of the logic. Cc: linux-pm@vger.kernel.org Cc: Greg Kroah-Hartman Cc: Linus Walleij Cc: Thierry Reding Cc: Mark Brown Cc: Liam Girdwood Cc: Bjorn Andersson Cc: Saravana Kannan Cc: Todd Kjos Cc: Len Brown Cc: Pavel Machek Cc: Ulf Hansson Cc: Kevin Hilman Cc: "Rafael J. Wysocki" Cc: Rob Herring Reviewed-by: Bjorn Andersson Reviewed-by: Rafael J. Wysocki Signed-off-by: John Stultz Link: https://lore.kernel.org/r/20200225050828.56458-5-john.stultz@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/dd.c | 53 +++++++------------------------------------ include/linux/device/driver.h | 1 - 2 files changed, 8 insertions(+), 46 deletions(-) (limited to 'include/linux') diff --git a/drivers/base/dd.c b/drivers/base/dd.c index fe26f2574a6d..c09e4e7277d4 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -244,19 +244,6 @@ static int __init deferred_probe_timeout_setup(char *str) } __setup("deferred_probe_timeout=", deferred_probe_timeout_setup); -static int __driver_deferred_probe_check_state(struct device *dev) -{ - if (!IS_ENABLED(CONFIG_MODULES) && initcalls_done) - return -ENODEV; - - if (!deferred_probe_timeout) { - dev_WARN(dev, "deferred probe timeout, ignoring dependency"); - return -ETIMEDOUT; - } - - return -EPROBE_DEFER; -} - /** * driver_deferred_probe_check_state() - Check deferred probe state * @dev: device to check @@ -272,39 +259,15 @@ static int __driver_deferred_probe_check_state(struct device *dev) */ int driver_deferred_probe_check_state(struct device *dev) { - int ret; - - ret = __driver_deferred_probe_check_state(dev); - if (ret != -ENODEV) - return ret; - - dev_warn(dev, "ignoring dependency for device, assuming no driver"); - - return -ENODEV; -} - -/** - * driver_deferred_probe_check_state_continue() - check deferred probe state - * @dev: device to check - * - * Returns -ETIMEDOUT if deferred probe debug timeout has expired, or - * -EPROBE_DEFER otherwise. - * - * Drivers or subsystems can opt-in to calling this function instead of - * directly returning -EPROBE_DEFER. - * - * This is similar to driver_deferred_probe_check_state(), but it allows the - * subsystem to keep deferring probe after built-in drivers have had a chance - * to probe. One scenario where that is useful is if built-in drivers rely on - * resources that are provided by modular drivers. - */ -int driver_deferred_probe_check_state_continue(struct device *dev) -{ - int ret; + if (!IS_ENABLED(CONFIG_MODULES) && initcalls_done) { + dev_warn(dev, "ignoring dependency for device, assuming no driver"); + return -ENODEV; + } - ret = __driver_deferred_probe_check_state(dev); - if (ret != -ENODEV) - return ret; + if (!deferred_probe_timeout) { + dev_WARN(dev, "deferred probe timeout, ignoring dependency"); + return -ETIMEDOUT; + } return -EPROBE_DEFER; } diff --git a/include/linux/device/driver.h b/include/linux/device/driver.h index 1188260f9a02..5242afabfaba 100644 --- a/include/linux/device/driver.h +++ b/include/linux/device/driver.h @@ -238,7 +238,6 @@ driver_find_device_by_acpi_dev(struct device_driver *drv, const void *adev) void driver_deferred_probe_add(struct device *dev); int driver_deferred_probe_check_state(struct device *dev); -int driver_deferred_probe_check_state_continue(struct device *dev); void driver_init(void); /** -- cgit v1.2.3 From 64c775fb4b21ab2532555e833edf52055df5fb1c Mon Sep 17 00:00:00 2001 From: John Stultz Date: Tue, 25 Feb 2020 05:08:27 +0000 Subject: driver core: Rename deferred_probe_timeout and make it global Since other subsystems (like regulator) have similar arbitrary timeouts for how long they try to resolve driver dependencies, rename deferred_probe_timeout to driver_deferred_probe_timeout and set it as global, so it can be shared. Cc: linux-pm@vger.kernel.org Cc: Greg Kroah-Hartman Cc: Linus Walleij Cc: Thierry Reding Cc: Mark Brown Cc: Liam Girdwood Cc: Bjorn Andersson Cc: Saravana Kannan Cc: Todd Kjos Cc: Len Brown Cc: Pavel Machek Cc: Ulf Hansson Cc: Kevin Hilman Cc: "Rafael J. Wysocki" Cc: Rob Herring Reviewed-by: Bjorn Andersson Reviewed-by: Rafael J. Wysocki Signed-off-by: John Stultz Link: https://lore.kernel.org/r/20200225050828.56458-6-john.stultz@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/dd.c | 16 +++++++++------- include/linux/device/driver.h | 1 + 2 files changed, 10 insertions(+), 7 deletions(-) (limited to 'include/linux') diff --git a/drivers/base/dd.c b/drivers/base/dd.c index c09e4e7277d4..76888a7459d8 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -229,17 +229,19 @@ DEFINE_SHOW_ATTRIBUTE(deferred_devs); * In the case of modules, set the default probe timeout to * 30 seconds to give userland some time to load needed modules */ -static int deferred_probe_timeout = 30; +int driver_deferred_probe_timeout = 30; #else /* In the case of !modules, no probe timeout needed */ -static int deferred_probe_timeout = -1; +int driver_deferred_probe_timeout = -1; #endif +EXPORT_SYMBOL_GPL(driver_deferred_probe_timeout); + static int __init deferred_probe_timeout_setup(char *str) { int timeout; if (!kstrtoint(str, 10, &timeout)) - deferred_probe_timeout = timeout; + driver_deferred_probe_timeout = timeout; return 1; } __setup("deferred_probe_timeout=", deferred_probe_timeout_setup); @@ -264,7 +266,7 @@ int driver_deferred_probe_check_state(struct device *dev) return -ENODEV; } - if (!deferred_probe_timeout) { + if (!driver_deferred_probe_timeout) { dev_WARN(dev, "deferred probe timeout, ignoring dependency"); return -ETIMEDOUT; } @@ -276,7 +278,7 @@ static void deferred_probe_timeout_work_func(struct work_struct *work) { struct device_private *private, *p; - deferred_probe_timeout = 0; + driver_deferred_probe_timeout = 0; driver_deferred_probe_trigger(); flush_work(&deferred_probe_work); @@ -310,9 +312,9 @@ static int deferred_probe_initcall(void) driver_deferred_probe_trigger(); flush_work(&deferred_probe_work); - if (deferred_probe_timeout > 0) { + if (driver_deferred_probe_timeout > 0) { schedule_delayed_work(&deferred_probe_timeout_work, - deferred_probe_timeout * HZ); + driver_deferred_probe_timeout * HZ); } return 0; } diff --git a/include/linux/device/driver.h b/include/linux/device/driver.h index 5242afabfaba..ee7ba5b5417e 100644 --- a/include/linux/device/driver.h +++ b/include/linux/device/driver.h @@ -236,6 +236,7 @@ driver_find_device_by_acpi_dev(struct device_driver *drv, const void *adev) } #endif +extern int driver_deferred_probe_timeout; void driver_deferred_probe_add(struct device *dev); int driver_deferred_probe_check_state(struct device *dev); void driver_init(void); -- cgit v1.2.3 From 526ee72dfdf74f4c14cbe165d68736f86bff6cf2 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 9 Mar 2020 17:36:40 +0100 Subject: debugfs: remove return value of debugfs_create_file_size() No one checks the return value of debugfs_create_file_size, as it's not needed, so make the return value void, so that no one tries to do so in the future. Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20200309163640.237984-1-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- Documentation/filesystems/debugfs.txt | 8 ++++---- fs/debugfs/inode.c | 18 ++++-------------- include/linux/debugfs.h | 20 +++++++++----------- 3 files changed, 17 insertions(+), 29 deletions(-) (limited to 'include/linux') diff --git a/Documentation/filesystems/debugfs.txt b/Documentation/filesystems/debugfs.txt index 55336a47a110..2e66df5b3827 100644 --- a/Documentation/filesystems/debugfs.txt +++ b/Documentation/filesystems/debugfs.txt @@ -55,10 +55,10 @@ missing. Create a file with an initial size, the following function can be used instead: - struct dentry *debugfs_create_file_size(const char *name, umode_t mode, - struct dentry *parent, void *data, - const struct file_operations *fops, - loff_t file_size); + void debugfs_create_file_size(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops, + loff_t file_size); file_size is the initial file size. The other parameters are the same as the function debugfs_create_file. diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index e742dfc66933..b7f2e971ecbc 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -501,26 +501,16 @@ EXPORT_SYMBOL_GPL(debugfs_create_file_unsafe); * wide range of flexibility in creating a file, or a directory (if you want * to create a directory, the debugfs_create_dir() function is * recommended to be used instead.) - * - * This function will return a pointer to a dentry if it succeeds. This - * pointer must be passed to the debugfs_remove() function when the file is - * to be removed (no automatic cleanup happens if your module is unloaded, - * you are responsible here.) If an error occurs, ERR_PTR(-ERROR) will be - * returned. - * - * If debugfs is not enabled in the kernel, the value -%ENODEV will be - * returned. */ -struct dentry *debugfs_create_file_size(const char *name, umode_t mode, - struct dentry *parent, void *data, - const struct file_operations *fops, - loff_t file_size) +void debugfs_create_file_size(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops, + loff_t file_size) { struct dentry *de = debugfs_create_file(name, mode, parent, data, fops); if (de) d_inode(de)->i_size = file_size; - return de; } EXPORT_SYMBOL_GPL(debugfs_create_file_size); diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h index 43efcc49f061..d672b7db0cfc 100644 --- a/include/linux/debugfs.h +++ b/include/linux/debugfs.h @@ -67,10 +67,10 @@ struct dentry *debugfs_create_file_unsafe(const char *name, umode_t mode, struct dentry *parent, void *data, const struct file_operations *fops); -struct dentry *debugfs_create_file_size(const char *name, umode_t mode, - struct dentry *parent, void *data, - const struct file_operations *fops, - loff_t file_size); +void debugfs_create_file_size(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops, + loff_t file_size); struct dentry *debugfs_create_dir(const char *name, struct dentry *parent); @@ -181,13 +181,11 @@ static inline struct dentry *debugfs_create_file_unsafe(const char *name, return ERR_PTR(-ENODEV); } -static inline struct dentry *debugfs_create_file_size(const char *name, umode_t mode, - struct dentry *parent, void *data, - const struct file_operations *fops, - loff_t file_size) -{ - return ERR_PTR(-ENODEV); -} +static inline void debugfs_create_file_size(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops, + loff_t file_size) +{ } static inline struct dentry *debugfs_create_dir(const char *name, struct dentry *parent) -- cgit v1.2.3 From e4c2c0ff00ecaf8e245455a199b86ce22143becf Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 15 Jan 2020 17:35:48 +0100 Subject: firmware: Add new platform fallback mechanism and firmware_request_platform() In some cases the platform's main firmware (e.g. the UEFI fw) may contain an embedded copy of device 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 a new platform fallback mechanism to the firmware loader which will try to lookup a device fw copy embedded in the platform's main firmware if direct filesystem lookup fails. Drivers which need such embedded fw copies can enable this fallback mechanism by using the new firmware_request_platform() function. Note that for now this is only supported on EFI platforms and even on these platforms firmware_fallback_platform() only works if CONFIG_EFI_EMBEDDED_FIRMWARE is enabled (this gets selected by drivers which need this), in all other cases firmware_fallback_platform() simply always returns -ENOENT. Reported-by: Dave Olsthoorn Suggested-by: Peter Jones Acked-by: Luis Chamberlain Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20200115163554.101315-5-hdegoede@redhat.com Signed-off-by: Greg Kroah-Hartman --- .../driver-api/firmware/fallback-mechanisms.rst | 103 +++++++++++++++++++++ Documentation/driver-api/firmware/lookup-order.rst | 2 + .../driver-api/firmware/request_firmware.rst | 5 + drivers/base/firmware_loader/Makefile | 1 + drivers/base/firmware_loader/fallback.h | 10 ++ drivers/base/firmware_loader/fallback_platform.c | 36 +++++++ drivers/base/firmware_loader/firmware.h | 4 + drivers/base/firmware_loader/main.c | 27 ++++++ include/linux/firmware.h | 9 ++ include/linux/fs.h | 1 + 10 files changed, 198 insertions(+) create mode 100644 drivers/base/firmware_loader/fallback_platform.c (limited to 'include/linux') diff --git a/Documentation/driver-api/firmware/fallback-mechanisms.rst b/Documentation/driver-api/firmware/fallback-mechanisms.rst index 8b041d0ab426..036383dad6d6 100644 --- a/Documentation/driver-api/firmware/fallback-mechanisms.rst +++ b/Documentation/driver-api/firmware/fallback-mechanisms.rst @@ -202,3 +202,106 @@ the following file: If you echo 0 into it means MAX_JIFFY_OFFSET will be used. The data type for the timeout is an int. + +EFI embedded firmware fallback mechanism +======================================== + +On some devices the system's EFI code / ROM may contain an embedded copy +of firmware for some of the system's integrated peripheral devices and +the peripheral's Linux device-driver needs to access this firmware. + +Device drivers which need such firmware can use the +firmware_request_platform() function for this, note that this is a +separate fallback mechanism from the other fallback mechanisms and +this does not use the sysfs interface. + +A device driver which needs this can describe the firmware it needs +using an efi_embedded_fw_desc struct: + +.. kernel-doc:: include/linux/efi_embedded_fw.h + :functions: efi_embedded_fw_desc + +The EFI embedded-fw code works by scanning all EFI_BOOT_SERVICES_CODE memory +segments for an eight byte sequence matching prefix; if the prefix is found it +then does a sha256 over length bytes and if that matches makes a copy of length +bytes and adds that to its list with found firmwares. + +To avoid doing this somewhat expensive scan on all systems, dmi matching is +used. Drivers are expected to export a dmi_system_id array, with each entries' +driver_data pointing to an efi_embedded_fw_desc. + +To register this array with the efi-embedded-fw code, a driver needs to: + +1. Always be builtin to the kernel or store the dmi_system_id array in a + separate object file which always gets builtin. + +2. Add an extern declaration for the dmi_system_id array to + include/linux/efi_embedded_fw.h. + +3. Add the dmi_system_id array to the embedded_fw_table in + drivers/firmware/efi/embedded-firmware.c wrapped in a #ifdef testing that + the driver is being builtin. + +4. Add "select EFI_EMBEDDED_FIRMWARE if EFI_STUB" to its Kconfig entry. + +The firmware_request_platform() function will always first try to load firmware +with the specified name directly from the disk, so the EFI embedded-fw can +always be overridden by placing a file under /lib/firmware. + +Note that: + +1. The code scanning for EFI embedded-firmware runs near the end + of start_kernel(), just before calling rest_init(). For normal drivers and + subsystems using subsys_initcall() to register themselves this does not + matter. This means that code running earlier cannot use EFI + embedded-firmware. + +2. At the moment the EFI embedded-fw code assumes that firmwares always start at + an offset which is a multiple of 8 bytes, if this is not true for your case + send in a patch to fix this. + +3. At the moment the EFI embedded-fw code only works on x86 because other archs + free EFI_BOOT_SERVICES_CODE before the EFI embedded-fw code gets a chance to + scan it. + +4. The current brute-force scanning of EFI_BOOT_SERVICES_CODE is an ad-hoc + brute-force solution. There has been discussion to use the UEFI Platform + Initialization (PI) spec's Firmware Volume protocol. This has been rejected + because the FV Protocol relies on *internal* interfaces of the PI spec, and: + 1. The PI spec does not define peripheral firmware at all + 2. The internal interfaces of the PI spec do not guarantee any backward + compatibility. Any implementation details in FV may be subject to change, + and may vary system to system. Supporting the FV Protocol would be + difficult as it is purposely ambiguous. + +Example how to check for and extract embedded firmware +------------------------------------------------------ + +To check for, for example Silead touchscreen controller embedded firmware, +do the following: + +1. Boot the system with efi=debug on the kernel commandline + +2. cp /sys/kernel/debug/efi/boot_services_code? to your home dir + +3. Open the boot_services_code? files in a hex-editor, search for the + magic prefix for Silead firmware: F0 00 00 00 02 00 00 00, this gives you + the beginning address of the firmware inside the boot_services_code? file. + +4. The firmware has a specific pattern, it starts with a 8 byte page-address, + typically F0 00 00 00 02 00 00 00 for the first page followed by 32-bit + word-address + 32-bit value pairs. With the word-address incrementing 4 + bytes (1 word) for each pair until a page is complete. A complete page is + followed by a new page-address, followed by more word + value pairs. This + leads to a very distinct pattern. Scroll down until this pattern stops, + this gives you the end of the firmware inside the boot_services_code? file. + +5. "dd if=boot_services_code? of=firmware bs=1 skip= count=" + will extract the firmware for you. Inspect the firmware file in a + hexeditor to make sure you got the dd parameters correct. + +6. Copy it to /lib/firmware under the expected name to test it. + +7. If the extracted firmware works, you can use the found info to fill an + efi_embedded_fw_desc struct to describe it, run "sha256sum firmware" + to get the sha256sum to put in the sha256 field. diff --git a/Documentation/driver-api/firmware/lookup-order.rst b/Documentation/driver-api/firmware/lookup-order.rst index 88c81739683c..6064672a782e 100644 --- a/Documentation/driver-api/firmware/lookup-order.rst +++ b/Documentation/driver-api/firmware/lookup-order.rst @@ -12,6 +12,8 @@ a driver issues a firmware API call. return it immediately * The ''Direct filesystem lookup'' is performed next, if found we return it immediately +* The ''Platform firmware fallback'' is performed next, but only when + firmware_request_platform() is used, if found we return it immediately * If no firmware has been found and the fallback mechanism was enabled the sysfs interface is created. After this either a kobject uevent is issued or the custom firmware loading is relied upon for firmware diff --git a/Documentation/driver-api/firmware/request_firmware.rst b/Documentation/driver-api/firmware/request_firmware.rst index f62bdcbfed5b..cd076462d235 100644 --- a/Documentation/driver-api/firmware/request_firmware.rst +++ b/Documentation/driver-api/firmware/request_firmware.rst @@ -25,6 +25,11 @@ firmware_request_nowarn .. kernel-doc:: drivers/base/firmware_loader/main.c :functions: firmware_request_nowarn +firmware_request_platform +------------------------- +.. kernel-doc:: drivers/base/firmware_loader/main.c + :functions: firmware_request_platform + request_firmware_direct ----------------------- .. kernel-doc:: drivers/base/firmware_loader/main.c diff --git a/drivers/base/firmware_loader/Makefile b/drivers/base/firmware_loader/Makefile index 0b2dfa6259c9..e87843408fe6 100644 --- a/drivers/base/firmware_loader/Makefile +++ b/drivers/base/firmware_loader/Makefile @@ -5,5 +5,6 @@ obj-$(CONFIG_FW_LOADER_USER_HELPER) += fallback_table.o obj-$(CONFIG_FW_LOADER) += firmware_class.o firmware_class-objs := main.o firmware_class-$(CONFIG_FW_LOADER_USER_HELPER) += fallback.o +firmware_class-$(CONFIG_EFI_EMBEDDED_FIRMWARE) += fallback_platform.o obj-y += builtin/ diff --git a/drivers/base/firmware_loader/fallback.h b/drivers/base/firmware_loader/fallback.h index 21063503e4ea..06f4577733a8 100644 --- a/drivers/base/firmware_loader/fallback.h +++ b/drivers/base/firmware_loader/fallback.h @@ -66,4 +66,14 @@ static inline void unregister_sysfs_loader(void) } #endif /* CONFIG_FW_LOADER_USER_HELPER */ +#ifdef CONFIG_EFI_EMBEDDED_FIRMWARE +int firmware_fallback_platform(struct fw_priv *fw_priv, enum fw_opt opt_flags); +#else +static inline int firmware_fallback_platform(struct fw_priv *fw_priv, + enum fw_opt opt_flags) +{ + return -ENOENT; +} +#endif + #endif /* __FIRMWARE_FALLBACK_H */ diff --git a/drivers/base/firmware_loader/fallback_platform.c b/drivers/base/firmware_loader/fallback_platform.c new file mode 100644 index 000000000000..c88c745590fe --- /dev/null +++ b/drivers/base/firmware_loader/fallback_platform.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include + +#include "fallback.h" +#include "firmware.h" + +int firmware_fallback_platform(struct fw_priv *fw_priv, enum fw_opt opt_flags) +{ + const u8 *data; + size_t size; + int rc; + + if (!(opt_flags & FW_OPT_FALLBACK_PLATFORM)) + return -ENOENT; + + rc = security_kernel_load_data(LOADING_FIRMWARE_EFI_EMBEDDED); + if (rc) + return rc; + + rc = efi_get_embedded_fw(fw_priv->fw_name, &data, &size); + if (rc) + return rc; /* rc == -ENOENT when the fw was not found */ + + fw_priv->data = vmalloc(size); + if (!fw_priv->data) + return -ENOMEM; + + memcpy(fw_priv->data, data, size); + fw_priv->size = size; + fw_state_done(fw_priv); + return 0; +} diff --git a/drivers/base/firmware_loader/firmware.h b/drivers/base/firmware_loader/firmware.h index 8656e5239a80..25836a6afc9f 100644 --- a/drivers/base/firmware_loader/firmware.h +++ b/drivers/base/firmware_loader/firmware.h @@ -29,6 +29,9 @@ * firmware caching mechanism. * @FW_OPT_NOFALLBACK_SYSFS: Disable the sysfs fallback mechanism. Takes * precedence over &FW_OPT_UEVENT and &FW_OPT_USERHELPER. + * @FW_OPT_FALLBACK_PLATFORM: Enable fallback to device fw copy embedded in + * the platform's main firmware. If both this fallback and the sysfs + * fallback are enabled, then this fallback will be tried first. */ enum fw_opt { FW_OPT_UEVENT = BIT(0), @@ -37,6 +40,7 @@ enum fw_opt { FW_OPT_NO_WARN = BIT(3), FW_OPT_NOCACHE = BIT(4), FW_OPT_NOFALLBACK_SYSFS = BIT(5), + FW_OPT_FALLBACK_PLATFORM = BIT(6), }; enum fw_status { diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 59d1dc322080..76f79913916d 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -778,6 +778,9 @@ _request_firmware(const struct firmware **firmware_p, const char *name, fw_decompress_xz); #endif + if (ret == -ENOENT) + ret = firmware_fallback_platform(fw->priv, opt_flags); + if (ret) { if (!(opt_flags & FW_OPT_NO_WARN)) dev_warn(device, @@ -885,6 +888,30 @@ int request_firmware_direct(const struct firmware **firmware_p, } EXPORT_SYMBOL_GPL(request_firmware_direct); +/** + * firmware_request_platform() - request firmware with platform-fw fallback + * @firmware: pointer to firmware image + * @name: name of firmware file + * @device: device for which firmware is being loaded + * + * This function is similar in behaviour to request_firmware, except that if + * direct filesystem lookup fails, it will fallback to looking for a copy of the + * requested firmware embedded in the platform's main (e.g. UEFI) firmware. + **/ +int firmware_request_platform(const struct firmware **firmware, + const char *name, struct device *device) +{ + int ret; + + /* Need to pin this module until return */ + __module_get(THIS_MODULE); + ret = _request_firmware(firmware, name, device, NULL, 0, + FW_OPT_UEVENT | FW_OPT_FALLBACK_PLATFORM); + module_put(THIS_MODULE); + return ret; +} +EXPORT_SYMBOL_GPL(firmware_request_platform); + /** * firmware_request_cache() - cache firmware for suspend so resume can use it * @name: name of firmware file diff --git a/include/linux/firmware.h b/include/linux/firmware.h index 2dd566c91d44..4bbd0afd91b7 100644 --- a/include/linux/firmware.h +++ b/include/linux/firmware.h @@ -44,6 +44,8 @@ int request_firmware(const struct firmware **fw, const char *name, struct device *device); int firmware_request_nowarn(const struct firmware **fw, const char *name, struct device *device); +int firmware_request_platform(const struct firmware **fw, const char *name, + struct device *device); int request_firmware_nowait( struct module *module, bool uevent, const char *name, struct device *device, gfp_t gfp, void *context, @@ -69,6 +71,13 @@ static inline int firmware_request_nowarn(const struct firmware **fw, return -EINVAL; } +static inline int firmware_request_platform(const struct firmware **fw, + const char *name, + struct device *device) +{ + return -EINVAL; +} + static inline int request_firmware_nowait( struct module *module, bool uevent, const char *name, struct device *device, gfp_t gfp, void *context, diff --git a/include/linux/fs.h b/include/linux/fs.h index f814ccd8d929..8e08f7f0cd5f 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2982,6 +2982,7 @@ extern int do_pipe_flags(int *, int); id(UNKNOWN, unknown) \ id(FIRMWARE, firmware) \ id(FIRMWARE_PREALLOC_BUFFER, firmware) \ + id(FIRMWARE_EFI_EMBEDDED, firmware) \ id(MODULE, kernel-module) \ id(KEXEC_IMAGE, kexec-image) \ id(KEXEC_INITRAMFS, kexec-initramfs) \ -- cgit v1.2.3 From 835e1b86ef8c9b466df5c9c949319690df700760 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 15 Jan 2020 17:35:53 +0100 Subject: platform/x86: touchscreen_dmi: Add EFI embedded firmware info support Sofar we have been unable to get permission from the vendors to put the firmware for touchscreens listed in touchscreen_dmi in linux-firmware. Some of the tablets with such a touchscreen have a touchscreen driver, and thus a copy of the firmware, as part of their EFI code. This commit adds the necessary info for the new EFI embedded-firmware code to extract these firmwares, making the touchscreen work OOTB without the user needing to manually add the firmware. Acked-by: Andy Shevchenko Acked-by: Ard Biesheuvel Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20200115163554.101315-10-hdegoede@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/efi/embedded-firmware.c | 3 +++ drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/touchscreen_dmi.c | 41 +++++++++++++++++++++++++++++++- include/linux/efi_embedded_fw.h | 2 ++ 4 files changed, 46 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/drivers/firmware/efi/embedded-firmware.c b/drivers/firmware/efi/embedded-firmware.c index 1bc9cdae2eed..a1b199de9006 100644 --- a/drivers/firmware/efi/embedded-firmware.c +++ b/drivers/firmware/efi/embedded-firmware.c @@ -21,6 +21,9 @@ EXPORT_SYMBOL_GPL(efi_embedded_fw_list); static bool checked_for_fw; static const struct dmi_system_id * const embedded_fw_table[] = { +#ifdef CONFIG_TOUCHSCREEN_DMI + touchscreen_dmi_table, +#endif NULL }; diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 587403c44598..cd9e2758c479 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1252,6 +1252,7 @@ config INTEL_TURBO_MAX_3 config TOUCHSCREEN_DMI bool "DMI based touchscreen configuration info" depends on ACPI && DMI && I2C=y && TOUCHSCREEN_SILEAD + select EFI_EMBEDDED_FIRMWARE if EFI ---help--- Certain ACPI based tablets with e.g. Silead or Chipone touchscreens do not have enough data in ACPI tables for the touchscreen driver to diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c index 93177e6e5ecd..a92198a06fb2 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -11,12 +11,15 @@ #include #include #include +#include #include #include #include #include struct ts_dmi_data { + /* The EFI embedded-fw code expects this to be the first member! */ + struct efi_embedded_fw_desc embedded_fw; const char *acpi_name; const struct property_entry *properties; }; @@ -64,6 +67,15 @@ static const struct property_entry chuwi_hi8_pro_props[] = { }; static const struct ts_dmi_data chuwi_hi8_pro_data = { + .embedded_fw = { + .name = "silead/gsl3680-chuwi-hi8-pro.fw", + .prefix = { 0xf0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }, + .length = 39864, + .sha256 = { 0xc0, 0x88, 0xc5, 0xef, 0xd1, 0x70, 0x77, 0x59, + 0x4e, 0xe9, 0xc4, 0xd8, 0x2e, 0xcd, 0xbf, 0x95, + 0x32, 0xd9, 0x03, 0x28, 0x0d, 0x48, 0x9f, 0x92, + 0x35, 0x37, 0xf6, 0x8b, 0x2a, 0xe4, 0x73, 0xff }, + }, .acpi_name = "MSSL1680:00", .properties = chuwi_hi8_pro_props, }; @@ -181,6 +193,15 @@ static const struct property_entry cube_iwork8_air_props[] = { }; static const struct ts_dmi_data cube_iwork8_air_data = { + .embedded_fw = { + .name = "silead/gsl3670-cube-iwork8-air.fw", + .prefix = { 0xf0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }, + .length = 38808, + .sha256 = { 0xff, 0x62, 0x2d, 0xd1, 0x8a, 0x78, 0x04, 0x7b, + 0x33, 0x06, 0xb0, 0x4f, 0x7f, 0x02, 0x08, 0x9c, + 0x96, 0xd4, 0x9f, 0x04, 0xe1, 0x47, 0x25, 0x25, + 0x60, 0x77, 0x41, 0x33, 0xeb, 0x12, 0x82, 0xfc }, + }, .acpi_name = "MSSL1680:00", .properties = cube_iwork8_air_props, }; @@ -387,6 +408,15 @@ static const struct property_entry onda_v80_plus_v3_props[] = { }; static const struct ts_dmi_data onda_v80_plus_v3_data = { + .embedded_fw = { + .name = "silead/gsl3676-onda-v80-plus-v3.fw", + .prefix = { 0xf0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }, + .length = 37224, + .sha256 = { 0x8f, 0xbd, 0x8f, 0x0c, 0x6b, 0xba, 0x5b, 0xf5, + 0xa3, 0xc7, 0xa3, 0xc0, 0x4f, 0xcd, 0xdf, 0x32, + 0xcc, 0xe4, 0x70, 0xd6, 0x46, 0x9c, 0xd7, 0xa7, + 0x4b, 0x82, 0x3f, 0xab, 0xc7, 0x90, 0xea, 0x23 }, + }, .acpi_name = "MSSL1680:00", .properties = onda_v80_plus_v3_props, }; @@ -449,6 +479,15 @@ static const struct property_entry pipo_w2s_props[] = { }; static const struct ts_dmi_data pipo_w2s_data = { + .embedded_fw = { + .name = "silead/gsl1680-pipo-w2s.fw", + .prefix = { 0xf0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }, + .length = 39072, + .sha256 = { 0xd0, 0x58, 0xc4, 0x7d, 0x55, 0x2d, 0x62, 0x18, + 0xd1, 0x6a, 0x71, 0x73, 0x0b, 0x3f, 0xbe, 0x60, + 0xbb, 0x45, 0x8c, 0x52, 0x27, 0xb7, 0x18, 0xf4, + 0x31, 0x00, 0x6a, 0x49, 0x76, 0xd8, 0x7c, 0xd3 }, + }, .acpi_name = "MSSL1680:00", .properties = pipo_w2s_props, }; @@ -641,7 +680,7 @@ static const struct ts_dmi_data trekstor_surftab_wintron70_data = { }; /* NOTE: Please keep this table sorted alphabetically */ -static const struct dmi_system_id touchscreen_dmi_table[] = { +const struct dmi_system_id touchscreen_dmi_table[] = { { /* Chuwi Hi8 */ .driver_data = (void *)&chuwi_hi8_data, diff --git a/include/linux/efi_embedded_fw.h b/include/linux/efi_embedded_fw.h index 3d066c6370c6..57eac5241303 100644 --- a/include/linux/efi_embedded_fw.h +++ b/include/linux/efi_embedded_fw.h @@ -36,6 +36,8 @@ struct efi_embedded_fw_desc { u8 sha256[32]; }; +extern const struct dmi_system_id touchscreen_dmi_table[]; + int efi_get_embedded_fw(const char *name, const u8 **dat, size_t *sz); #endif -- cgit v1.2.3