diff options
48 files changed, 996 insertions, 174 deletions
| diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index d7da466fd5c3..6a0822c98e1d 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1360,6 +1360,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) @@ -3291,12 +3309,6 @@  			This can be set from sysctl after boot.  			See Documentation/admin-guide/sysctl/vm.rst for details. -	of_devlink	[OF, KNL] Create device links between consumer and -			supplier devices by scanning the devictree to infer the -			consumer/supplier relationships.  A consumer device -			will not be probed until all the supplier devices have -			probed successfully. -  	ohci1394_dma=early	[HW] enable debugging via the ohci1394 driver.  			See Documentation/debugging-via-ohci1394.txt for more  			info. 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=<begin-addr> count=<len>" +   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/Documentation/filesystems/debugfs.rst b/Documentation/filesystems/debugfs.rst index 80f332b8eb68..db9ea0854040 100644 --- a/Documentation/filesystems/debugfs.rst +++ b/Documentation/filesystems/debugfs.rst @@ -62,10 +62,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/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c index ae923ee8e2b4..a4ea04603af3 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;  } @@ -943,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/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/base/arch_topology.c b/drivers/base/arch_topology.c index 6119e11a9f95..e5d691cf824c 100644 --- a/drivers/base/arch_topology.c +++ b/drivers/base/arch_topology.c @@ -94,7 +94,7 @@ static void update_topology_flags_workfn(struct work_struct *work)  	update_topology = 0;  } -static u32 capacity_scale; +static DEFINE_PER_CPU(u32, freq_factor) = 1;  static u32 *raw_capacity;  static int free_raw_capacity(void) @@ -108,17 +108,23 @@ static int free_raw_capacity(void)  void topology_normalize_cpu_scale(void)  {  	u64 capacity; +	u64 capacity_scale;  	int cpu;  	if (!raw_capacity)  		return; -	pr_debug("cpu_capacity: capacity_scale=%u\n", capacity_scale); +	capacity_scale = 1;  	for_each_possible_cpu(cpu) { -		pr_debug("cpu_capacity: cpu=%d raw_capacity=%u\n", -			 cpu, raw_capacity[cpu]); -		capacity = (raw_capacity[cpu] << SCHED_CAPACITY_SHIFT) -			/ capacity_scale; +		capacity = raw_capacity[cpu] * per_cpu(freq_factor, cpu); +		capacity_scale = max(capacity, capacity_scale); +	} + +	pr_debug("cpu_capacity: capacity_scale=%llu\n", capacity_scale); +	for_each_possible_cpu(cpu) { +		capacity = raw_capacity[cpu] * per_cpu(freq_factor, cpu); +		capacity = div64_u64(capacity << SCHED_CAPACITY_SHIFT, +			capacity_scale);  		topology_set_cpu_scale(cpu, capacity);  		pr_debug("cpu_capacity: CPU%d cpu_capacity=%lu\n",  			cpu, topology_get_cpu_scale(cpu)); @@ -127,6 +133,7 @@ void topology_normalize_cpu_scale(void)  bool __init topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu)  { +	struct clk *cpu_clk;  	static bool cap_parsing_failed;  	int ret;  	u32 cpu_capacity; @@ -146,10 +153,22 @@ bool __init topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu)  				return false;  			}  		} -		capacity_scale = max(cpu_capacity, capacity_scale);  		raw_capacity[cpu] = cpu_capacity;  		pr_debug("cpu_capacity: %pOF cpu_capacity=%u (raw)\n",  			cpu_node, raw_capacity[cpu]); + +		/* +		 * Update freq_factor for calculating early boot cpu capacities. +		 * For non-clk CPU DVFS mechanism, there's no way to get the +		 * frequency value now, assuming they are running at the same +		 * frequency (by keeping the initial freq_factor value). +		 */ +		cpu_clk = of_clk_get(cpu_node, 0); +		if (!PTR_ERR_OR_ZERO(cpu_clk)) { +			per_cpu(freq_factor, cpu) = +				clk_get_rate(cpu_clk) / 1000; +			clk_put(cpu_clk); +		}  	} else {  		if (raw_capacity) {  			pr_err("cpu_capacity: missing %pOF raw capacity\n", @@ -188,11 +207,8 @@ init_cpu_capacity_callback(struct notifier_block *nb,  	cpumask_andnot(cpus_to_visit, cpus_to_visit, policy->related_cpus); -	for_each_cpu(cpu, policy->related_cpus) { -		raw_capacity[cpu] = topology_get_cpu_scale(cpu) * -				    policy->cpuinfo.max_freq / 1000UL; -		capacity_scale = max(raw_capacity[cpu], capacity_scale); -	} +	for_each_cpu(cpu, policy->related_cpus) +		per_cpu(freq_factor, cpu) = policy->cpuinfo.max_freq / 1000;  	if (cpumask_empty(cpus_to_visit)) {  		topology_normalize_cpu_scale(); @@ -281,7 +297,7 @@ static int __init get_cpu_for_node(struct device_node *node)  static int __init parse_core(struct device_node *core, int package_id,  			     int core_id)  { -	char name[10]; +	char name[20];  	bool leaf = true;  	int i = 0;  	int cpu; @@ -327,7 +343,7 @@ static int __init parse_core(struct device_node *core, int package_id,  static int __init parse_cluster(struct device_node *cluster, int depth)  { -	char name[10]; +	char name[20];  	bool leaf = true;  	bool has_cores = false;  	struct device_node *c; diff --git a/drivers/base/component.c b/drivers/base/component.c index c7879f5ae2fb..e97704104784 100644 --- a/drivers/base/component.c +++ b/drivers/base/component.c @@ -528,7 +528,8 @@ static void component_unbind(struct component *component,  {  	WARN_ON(!component->bound); -	component->ops->unbind(component->dev, master->dev, data); +	if (component->ops && component->ops->unbind) +		component->ops->unbind(component->dev, master->dev, data);  	component->bound = false;  	/* Release all resources claimed in the binding of this component */ diff --git a/drivers/base/core.c b/drivers/base/core.c index dbb0f9130f42..5e3cc1651c78 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -64,12 +64,12 @@ static inline void device_links_write_unlock(void)  	mutex_unlock(&device_links_lock);  } -int device_links_read_lock(void) +int device_links_read_lock(void) __acquires(&device_links_srcu)  {  	return srcu_read_lock(&device_links_srcu);  } -void device_links_read_unlock(int idx) +void device_links_read_unlock(int idx) __releases(&device_links_srcu)  {  	srcu_read_unlock(&device_links_srcu, idx);  } @@ -523,9 +523,13 @@ static void device_link_add_missing_supplier_links(void)  	mutex_lock(&wfs_lock);  	list_for_each_entry_safe(dev, tmp, &wait_for_suppliers, -				 links.needs_suppliers) -		if (!fwnode_call_int_op(dev->fwnode, add_links, dev)) +				 links.needs_suppliers) { +		int ret = fwnode_call_int_op(dev->fwnode, add_links, dev); +		if (!ret)  			list_del_init(&dev->links.needs_suppliers); +		else if (ret != -ENODEV) +			dev->links.need_for_probe = false; +	}  	mutex_unlock(&wfs_lock);  } @@ -2341,6 +2345,31 @@ static int device_private_init(struct device *dev)  	return 0;  } +static 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. @@ -2375,6 +2404,7 @@ int device_add(struct device *dev)  	struct class_interface *class_intf;  	int error = -EINVAL, fw_ret;  	struct kobject *glue_dir = NULL; +	bool is_fwnode_dev = false;  	dev = get_device(dev);  	if (!dev) @@ -2472,8 +2502,10 @@ int device_add(struct device *dev)  	kobject_uevent(&dev->kobj, KOBJ_ADD); -	if (dev->fwnode && !dev->fwnode->dev) +	if (dev->fwnode && !dev->fwnode->dev) {  		dev->fwnode->dev = dev; +		is_fwnode_dev = true; +	}  	/*  	 * Check if any of the other devices (consumers) have been waiting for @@ -2489,7 +2521,8 @@ int device_add(struct device *dev)  	 */  	device_link_add_missing_supplier_links(); -	if (fwnode_has_op(dev->fwnode, add_links)) { +	if (fw_devlink_flags && is_fwnode_dev && +	    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/drivers/base/cpu.c b/drivers/base/cpu.c index 6265871a4af2..df19c002d747 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -231,8 +231,7 @@ static struct cpu_attr cpu_attrs[] = {  static ssize_t print_cpus_kernel_max(struct device *dev,  				     struct device_attribute *attr, char *buf)  { -	int n = snprintf(buf, PAGE_SIZE-2, "%d\n", NR_CPUS - 1); -	return n; +	return sprintf(buf, "%d\n", NR_CPUS - 1);  }  static DEVICE_ATTR(kernel_max, 0444, print_cpus_kernel_max, NULL); @@ -258,13 +257,13 @@ static ssize_t print_cpus_offline(struct device *dev,  			buf[n++] = ',';  		if (nr_cpu_ids == total_cpus-1) -			n += snprintf(&buf[n], len - n, "%u", nr_cpu_ids); +			n += scnprintf(&buf[n], len - n, "%u", nr_cpu_ids);  		else -			n += snprintf(&buf[n], len - n, "%u-%d", +			n += scnprintf(&buf[n], len - n, "%u-%d",  						      nr_cpu_ids, total_cpus-1);  	} -	n += snprintf(&buf[n], len - n, "\n"); +	n += scnprintf(&buf[n], len - n, "\n");  	return n;  }  static DEVICE_ATTR(offline, 0444, print_cpus_offline, NULL); @@ -272,7 +271,7 @@ static DEVICE_ATTR(offline, 0444, print_cpus_offline, NULL);  static ssize_t print_cpus_isolated(struct device *dev,  				  struct device_attribute *attr, char *buf)  { -	int n = 0, len = PAGE_SIZE-2; +	int n;  	cpumask_var_t isolated;  	if (!alloc_cpumask_var(&isolated, GFP_KERNEL)) @@ -280,7 +279,7 @@ static ssize_t print_cpus_isolated(struct device *dev,  	cpumask_andnot(isolated, cpu_possible_mask,  		       housekeeping_cpumask(HK_FLAG_DOMAIN)); -	n = scnprintf(buf, len, "%*pbl\n", cpumask_pr_args(isolated)); +	n = sprintf(buf, "%*pbl\n", cpumask_pr_args(isolated));  	free_cpumask_var(isolated); @@ -292,11 +291,7 @@ static DEVICE_ATTR(isolated, 0444, print_cpus_isolated, NULL);  static ssize_t print_cpus_nohz_full(struct device *dev,  				  struct device_attribute *attr, char *buf)  { -	int n = 0, len = PAGE_SIZE-2; - -	n = scnprintf(buf, len, "%*pbl\n", cpumask_pr_args(tick_nohz_full_mask)); - -	return n; +	return sprintf(buf, "%*pbl\n", cpumask_pr_args(tick_nohz_full_mask));  }  static DEVICE_ATTR(nohz_full, 0444, print_cpus_nohz_full, NULL);  #endif diff --git a/drivers/base/dd.c b/drivers/base/dd.c index b25bcab2a26b..06ec0e851fa1 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -224,76 +224,52 @@ static int deferred_devs_show(struct seq_file *s, void *data)  }  DEFINE_SHOW_ATTRIBUTE(deferred_devs); -static int deferred_probe_timeout = -1; +#ifdef CONFIG_MODULES +/* + * In the case of modules, set the default probe timeout to + * 30 seconds to give userland some time to load needed modules + */ +int driver_deferred_probe_timeout = 30; +#else +/* In the case of !modules, no probe timeout needed */ +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); -static int __driver_deferred_probe_check_state(struct device *dev) -{ -	if (!initcalls_done) -		return -EPROBE_DEFER; - -	if (!deferred_probe_timeout) { -		dev_WARN(dev, "deferred probe timeout, ignoring dependency"); -		return -ETIMEDOUT; -	} - -	return 0; -} -  /**   * driver_deferred_probe_check_state() - Check deferred probe state   * @dev: device to check   * - * Returns -ENODEV if init is done and all built-in drivers have had a chance - * to probe (i.e. initcalls are done), -ETIMEDOUT if deferred probe debug - * timeout has expired, or -EPROBE_DEFER if none of those conditions are met. + * Return: + * -ENODEV if initcalls have completed and modules are disabled. + * -ETIMEDOUT if the deferred probe timeout was set and has expired + *  and modules are enabled. + * -EPROBE_DEFER in other cases.   *   * Drivers or subsystems can opt-in to calling this function instead of directly   * returning -EPROBE_DEFER.   */  int driver_deferred_probe_check_state(struct device *dev)  { -	int ret; - -	ret = __driver_deferred_probe_check_state(dev); -	if (ret < 0) -		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 < 0) -		return ret; +	if (!driver_deferred_probe_timeout) { +		dev_WARN(dev, "deferred probe timeout, ignoring dependency"); +		return -ETIMEDOUT; +	}  	return -EPROBE_DEFER;  } @@ -302,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); @@ -336,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;  } @@ -668,9 +644,10 @@ static int really_probe_debug(struct device *dev, struct device_driver *drv)   */  int driver_probe_done(void)  { -	pr_debug("%s: probe_count = %d\n", __func__, -		 atomic_read(&probe_count)); -	if (atomic_read(&probe_count)) +	int local_probe_count = atomic_read(&probe_count); + +	pr_debug("%s: probe_count = %d\n", __func__, local_probe_count); +	if (local_probe_count)  		return -EBUSY;  	return 0;  } @@ -1222,7 +1199,7 @@ void driver_detach(struct device_driver *drv)  			spin_unlock(&drv->p->klist_devices.k_lock);  			break;  		} -		dev_prv = list_entry(drv->p->klist_devices.k_list.prev, +		dev_prv = list_last_entry(&drv->p->klist_devices.k_list,  				     struct device_private,  				     knode_driver.n_node);  		dev = dev_prv->device; 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.c b/drivers/base/firmware_loader/fallback.c index 8704e1bae175..1e9c96e3ed63 100644 --- a/drivers/base/firmware_loader/fallback.c +++ b/drivers/base/firmware_loader/fallback.c @@ -525,7 +525,7 @@ static int fw_load_sysfs_fallback(struct fw_sysfs *fw_sysfs,  	}  	retval = fw_sysfs_wait_timeout(fw_priv, timeout); -	if (retval < 0) { +	if (retval < 0 && retval != -ENOENT) {  		mutex_lock(&fw_lock);  		fw_load_abort(fw_sysfs);  		mutex_unlock(&fw_lock); 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 <linux/efi_embedded_fw.h> +#include <linux/property.h> +#include <linux/security.h> +#include <linux/vmalloc.h> + +#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 57133a9dad09..76f79913916d 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", @@ -776,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, @@ -884,6 +889,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   * @device: device for which firmware should be cached for 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/arm-init.c b/drivers/firmware/efi/arm-init.c index d99f5b0c8a09..6703bedfa9e1 100644 --- a/drivers/firmware/efi/arm-init.c +++ b/drivers/firmware/efi/arm-init.c @@ -349,7 +349,7 @@ static int efifb_add_links(const struct fwnode_handle *fwnode,  	 * If this fails, retrying this function at a later point won't  	 * change anything. So, don't return an error after this.  	 */ -	if (!device_link_add(dev, sup_dev, 0)) +	if (!device_link_add(dev, sup_dev, fw_devlink_get_flags()))  		dev_warn(dev, "device_link_add() failed\n");  	put_device(sup_dev); diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 21ea99f65113..0c2d99dfd3c5 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -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> @@ -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/drivers/firmware/efi/embedded-firmware.c b/drivers/firmware/efi/embedded-firmware.c new file mode 100644 index 000000000000..a1b199de9006 --- /dev/null +++ b/drivers/firmware/efi/embedded-firmware.c @@ -0,0 +1,150 @@ +// 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[] = { +#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 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/drivers/input/touchscreen/chipone_icn8505.c b/drivers/input/touchscreen/chipone_icn8505.c index c768186ce856..f9ca5502ac8c 100644 --- a/drivers/input/touchscreen/chipone_icn8505.c +++ b/drivers/input/touchscreen/chipone_icn8505.c @@ -288,7 +288,7 @@ static int icn8505_upload_fw(struct icn8505_data *icn8505)  	 * we may need it at resume. Having loaded it once will make the  	 * firmware class code cache it at suspend/resume.  	 */ -	error = request_firmware(&fw, icn8505->firmware_name, dev); +	error = firmware_request_platform(&fw, icn8505->firmware_name, dev);  	if (error) {  		dev_err(dev, "Firmware request error %d\n", error);  		return error; diff --git a/drivers/input/touchscreen/silead.c b/drivers/input/touchscreen/silead.c index ad8b6a2bfd36..8fa2f3b7cfd8 100644 --- a/drivers/input/touchscreen/silead.c +++ b/drivers/input/touchscreen/silead.c @@ -288,7 +288,7 @@ static int silead_ts_load_fw(struct i2c_client *client)  	dev_dbg(dev, "Firmware file name: %s", data->fw_name); -	error = request_firmware(&fw, data->fw_name, dev); +	error = firmware_request_platform(&fw, data->fw_name, dev);  	if (error) {  		dev_err(dev, "Firmware request error %d\n", error);  		return error; diff --git a/drivers/of/property.c b/drivers/of/property.c index e851c57a15b0..f104f15b57fb 100644 --- a/drivers/of/property.c +++ b/drivers/of/property.c @@ -1262,7 +1262,7 @@ static int of_link_property(struct device *dev, struct device_node *con_np,  	u32 dl_flags;  	if (dev->of_node == con_np) -		dl_flags = DL_FLAG_AUTOPROBE_CONSUMER; +		dl_flags = fw_devlink_get_flags();  	else  		dl_flags = DL_FLAG_SYNC_STATE_ONLY; @@ -1299,15 +1299,9 @@ static int of_link_to_suppliers(struct device *dev,  	return ret;  } -static bool of_devlink; -core_param(of_devlink, of_devlink, bool, 0); -  static int of_fwnode_add_links(const struct fwnode_handle *fwnode,  			       struct device *dev)  { -	if (!of_devlink) -		return 0; -  	if (unlikely(!is_of_node(fwnode)))  		return 0; diff --git a/drivers/pinctrl/devicetree.c b/drivers/pinctrl/devicetree.c index 9357f7c46cf3..1ed20ac2243f 100644 --- a/drivers/pinctrl/devicetree.c +++ b/drivers/pinctrl/devicetree.c @@ -127,11 +127,12 @@ static int dt_to_map_one_config(struct pinctrl *p,  		np_pctldev = of_get_next_parent(np_pctldev);  		if (!np_pctldev || of_node_is_root(np_pctldev)) {  			of_node_put(np_pctldev); +			ret = driver_deferred_probe_check_state(p->dev);  			/* keep deferring if modules are enabled unless we've timed out */ -			if (IS_ENABLED(CONFIG_MODULES) && !allow_default) -				return driver_deferred_probe_check_state_continue(p->dev); - -			return driver_deferred_probe_check_state(p->dev); +			if (IS_ENABLED(CONFIG_MODULES) && !allow_default && +			    (ret == -ENODEV)) +				ret = -EPROBE_DEFER; +			return ret;  		}  		/* If we're creating a hog we can use the passed pctldev */  		if (hog_pctldev && (np_pctldev == p->dev->of_node)) { 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..6ec8923dec1a 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -11,12 +11,15 @@  #include <linux/acpi.h>  #include <linux/device.h>  #include <linux/dmi.h> +#include <linux/efi_embedded_fw.h>  #include <linux/i2c.h>  #include <linux/notifier.h>  #include <linux/property.h>  #include <linux/string.h>  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,  }; @@ -120,6 +132,18 @@ static const struct ts_dmi_data chuwi_vi8_data = {  	.properties     = chuwi_vi8_props,  }; +static const struct ts_dmi_data chuwi_vi8_plus_data = { +	.embedded_fw = { +		.name	= "chipone/icn8505-HAMP0002.fw", +		.prefix = { 0xb0, 0x07, 0x00, 0x00, 0xe4, 0x07, 0x00, 0x00 }, +		.length	= 35012, +		.sha256	= { 0x93, 0xe5, 0x49, 0xe0, 0xb6, 0xa2, 0xb4, 0xb3, +			    0x88, 0x96, 0x34, 0x97, 0x5e, 0xa8, 0x13, 0x78, +			    0x72, 0x98, 0xb8, 0x29, 0xeb, 0x5c, 0xa7, 0xf1, +			    0x25, 0x13, 0x43, 0xf4, 0x30, 0x7c, 0xfc, 0x7c }, +	}, +}; +  static const struct property_entry chuwi_vi10_props[] = {  	PROPERTY_ENTRY_U32("touchscreen-min-x", 0),  	PROPERTY_ENTRY_U32("touchscreen-min-y", 4), @@ -181,6 +205,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 +420,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 +491,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 +692,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, @@ -704,6 +755,15 @@ static const struct dmi_system_id touchscreen_dmi_table[] = {  		},  	},  	{ +		/* Chuwi Vi8 Plus (CWI519) */ +		.driver_data = (void *)&chuwi_vi8_plus_data, +		.matches = { +			DMI_MATCH(DMI_SYS_VENDOR, "Hampoo"), +			DMI_MATCH(DMI_PRODUCT_NAME, "D2D3_Vi8A1"), +			DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), +		}, +	}, +	{  		/* Chuwi Vi10 (CWI505) */  		.driver_data = (void *)&chuwi_vi10_data,  		.matches = { @@ -1106,6 +1166,9 @@ static int __init ts_dmi_init(void)  		return 0; /* Not an error */  	ts_data = dmi_id->driver_data; +	/* Some dmi table entries only provide an efi_embedded_fw_desc */ +	if (!ts_data->properties) +		return 0;  	error = bus_register_notifier(&i2c_bus_type, &ts_dmi_notifier);  	if (error) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index d015d99cb59d..51b6a2dea717 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -5757,6 +5757,10 @@ static DECLARE_DELAYED_WORK(regulator_init_complete_work,  static int __init regulator_init_complete(void)  { +	int delay = driver_deferred_probe_timeout; + +	if (delay < 0) +		delay = 0;  	/*  	 * Since DT doesn't provide an idiomatic mechanism for  	 * enabling full constraints and since it's much more natural @@ -5767,18 +5771,17 @@ static int __init regulator_init_complete(void)  		has_full_constraints = true;  	/* -	 * We punt completion for an arbitrary amount of time since -	 * systems like distros will load many drivers from userspace -	 * so consumers might not always be ready yet, this is -	 * particularly an issue with laptops where this might bounce -	 * the display off then on.  Ideally we'd get a notification -	 * from userspace when this happens but we don't so just wait -	 * a bit and hope we waited long enough.  It'd be better if -	 * we'd only do this on systems that need it, and a kernel -	 * command line option might be useful. +	 * If driver_deferred_probe_timeout is set, we punt +	 * completion for that many seconds since systems like +	 * distros will load many drivers from userspace so consumers +	 * might not always be ready yet, this is particularly an +	 * issue with laptops where this might bounce the display off +	 * then on.  Ideally we'd get a notification from userspace +	 * when this happens but we don't so just wait a bit and hope +	 * we waited long enough.  It'd be better if we'd only do +	 * this on systems that need it.  	 */ -	schedule_delayed_work(®ulator_init_complete_work, -			      msecs_to_jiffies(30000)); +	schedule_delayed_work(®ulator_init_complete_work, delay * HZ);  	return 0;  } diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c index db987b5110a9..f34757e8f25f 100644 --- a/fs/debugfs/file.c +++ b/fs/debugfs/file.c @@ -175,8 +175,13 @@ static int open_proxy_open(struct inode *inode, struct file *filp)  	if (r)  		goto out; -	real_fops = fops_get(real_fops); -	if (!real_fops) { +	if (!fops_get(real_fops)) { +#ifdef MODULE +		if (real_fops->owner && +		    real_fops->owner->state == MODULE_STATE_GOING) +			goto out; +#endif +  		/* Huh? Module did not clean up after itself at exit? */  		WARN(1, "debugfs file owner did not clean up at exit: %pd",  			dentry); @@ -305,8 +310,13 @@ static int full_proxy_open(struct inode *inode, struct file *filp)  	if (r)  		goto out; -	real_fops = fops_get(real_fops); -	if (!real_fops) { +	if (!fops_get(real_fops)) { +#ifdef MODULE +		if (real_fops->owner && +		    real_fops->owner->state == MODULE_STATE_GOING) +			goto out; +#endif +  		/* Huh? Module did not cleanup after itself at exit? */  		WARN(1, "debugfs file owner did not clean up at exit: %pd",  			dentry); 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/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/fs/libfs.c b/fs/libfs.c index c686bd9caac6..3759fbacf522 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -891,7 +891,7 @@ int simple_attr_open(struct inode *inode, struct file *file,  {  	struct simple_attr *attr; -	attr = kmalloc(sizeof(*attr), GFP_KERNEL); +	attr = kzalloc(sizeof(*attr), GFP_KERNEL);  	if (!attr)  		return -ENOMEM; @@ -931,9 +931,11 @@ ssize_t simple_attr_read(struct file *file, char __user *buf,  	if (ret)  		return ret; -	if (*ppos) {		/* continued read */ +	if (*ppos && attr->get_buf[0]) { +		/* continued read */  		size = strlen(attr->get_buf); -	} else {		/* first read */ +	} else { +		/* first read */  		u64 val;  		ret = attr->get(attr->data, &val);  		if (ret) diff --git a/include/linux/arch_topology.h b/include/linux/arch_topology.h index 3015ecbb90b1..c507e9ddd909 100644 --- a/include/linux/arch_topology.h +++ b/include/linux/arch_topology.h @@ -16,9 +16,7 @@ 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) +static inline unsigned long topology_get_cpu_scale(int cpu)  {  	return per_cpu(cpu_scale, cpu);  } @@ -27,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);  } 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) diff --git a/include/linux/device/driver.h b/include/linux/device/driver.h index 1188260f9a02..ee7ba5b5417e 100644 --- a/include/linux/device/driver.h +++ b/include/linux/device/driver.h @@ -236,9 +236,9 @@ 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); -int driver_deferred_probe_check_state_continue(struct device *dev);  void driver_init(void);  /** diff --git a/include/linux/efi.h b/include/linux/efi.h index 7efd7072cca5..23392b88bcc0 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  /* @@ -1553,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..57eac5241303 --- /dev/null +++ b/include/linux/efi_embedded_fw.h @@ -0,0 +1,43 @@ +/* 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]; +}; + +extern const struct dmi_system_id touchscreen_dmi_table[]; + +int efi_get_embedded_fw(const char *name, const u8 **dat, size_t *sz); + +#endif 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 593e911d1ca0..3d69de600494 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)	\ @@ -3012,6 +3013,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/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 diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h index f242f66db19d..bdc35753ef7c 100644 --- a/include/linux/platform_device.h +++ b/include/linux/platform_device.h @@ -92,7 +92,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); diff --git a/lib/test_firmware.c b/lib/test_firmware.c index 251213c872b5..0c7fbcf07ac5 100644 --- a/lib/test_firmware.c +++ b/lib/test_firmware.c @@ -24,6 +24,7 @@  #include <linux/delay.h>  #include <linux/kthread.h>  #include <linux/vmalloc.h> +#include <linux/efi_embedded_fw.h>  #define TEST_FIRMWARE_NAME	"test-firmware.bin"  #define TEST_FIRMWARE_NUM_REQS	4 @@ -507,6 +508,57 @@ out:  }  static DEVICE_ATTR_WO(trigger_request); +#ifdef CONFIG_EFI_EMBEDDED_FIRMWARE +static ssize_t trigger_request_platform_store(struct device *dev, +					      struct device_attribute *attr, +					      const char *buf, size_t count) +{ +	static const u8 test_data[] = { +		0x55, 0xaa, 0x55, 0xaa, 0x01, 0x02, 0x03, 0x04, +		0x55, 0xaa, 0x55, 0xaa, 0x05, 0x06, 0x07, 0x08, +		0x55, 0xaa, 0x55, 0xaa, 0x10, 0x20, 0x30, 0x40, +		0x55, 0xaa, 0x55, 0xaa, 0x50, 0x60, 0x70, 0x80 +	}; +	struct efi_embedded_fw efi_embedded_fw; +	const struct firmware *firmware = NULL; +	char *name; +	int rc; + +	name = kstrndup(buf, count, GFP_KERNEL); +	if (!name) +		return -ENOSPC; + +	pr_info("inserting test platform fw '%s'\n", name); +	efi_embedded_fw.name = name; +	efi_embedded_fw.data = (void *)test_data; +	efi_embedded_fw.length = sizeof(test_data); +	list_add(&efi_embedded_fw.list, &efi_embedded_fw_list); + +	pr_info("loading '%s'\n", name); +	rc = firmware_request_platform(&firmware, name, dev); +	if (rc) { +		pr_info("load of '%s' failed: %d\n", name, rc); +		goto out; +	} +	if (firmware->size != sizeof(test_data) || +	    memcmp(firmware->data, test_data, sizeof(test_data)) != 0) { +		pr_info("firmware contents mismatch for '%s'\n", name); +		rc = -EINVAL; +		goto out; +	} +	pr_info("loaded: %zu\n", firmware->size); +	rc = count; + +out: +	release_firmware(firmware); +	list_del(&efi_embedded_fw.list); +	kfree(name); + +	return rc; +} +static DEVICE_ATTR_WO(trigger_request_platform); +#endif +  static DECLARE_COMPLETION(async_fw_done);  static void trigger_async_request_cb(const struct firmware *fw, void *context) @@ -903,6 +955,9 @@ static struct attribute *test_dev_attrs[] = {  	TEST_FW_DEV_ATTR(trigger_request),  	TEST_FW_DEV_ATTR(trigger_async_request),  	TEST_FW_DEV_ATTR(trigger_custom_fallback), +#ifdef CONFIG_EFI_EMBEDDED_FIRMWARE +	TEST_FW_DEV_ATTR(trigger_request_platform), +#endif  	/* These use the config and can use the test_result */  	TEST_FW_DEV_ATTR(trigger_batched_requests), 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_filesystem.sh b/tools/testing/selftests/firmware/fw_filesystem.sh index 56894477c8bd..fcc281373b4d 100755 --- a/tools/testing/selftests/firmware/fw_filesystem.sh +++ b/tools/testing/selftests/firmware/fw_filesystem.sh @@ -86,6 +86,29 @@ else  	fi  fi +# Try platform (EFI embedded fw) loading too +if [ ! -e "$DIR"/trigger_request_platform ]; then +	echo "$0: firmware loading: platform trigger not present, ignoring test" >&2 +else +	if printf '\000' >"$DIR"/trigger_request_platform 2> /dev/null; then +		echo "$0: empty filename should not succeed (platform)" >&2 +		exit 1 +	fi + +	# Note we echo a non-existing name, since files on the file-system +	# are preferred over firmware embedded inside the platform's firmware +	# The test adds a fake entry with the requested name to the platform's +	# fw list, so the name does not matter as long as it does not exist +	if ! echo -n "nope-$NAME" >"$DIR"/trigger_request_platform ; then +		echo "$0: could not trigger request platform" >&2 +		exit 1 +	fi + +	# The test verifies itself that the loaded firmware contents matches +	# the contents for the fake platform fw entry it added. +	echo "$0: platform loading works" +fi +  ### Batched requests tests  test_config_present()  { 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 <errno.h> +#include <fcntl.h> +#include <sched.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#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 | 
