mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 22:21:40 +00:00
Driver core patches for 4.17-rc1
Here is the "big" set of driver core patches for 4.17-rc1. There's really not much here, just a bunch of firmware code refactoring from Luis as he attempts to wrangle that codebase into something that is managable, along with a bunch of userspace tests for it. Other than that, a handful of small bugfixes and reverts of things that didn't work out. Full details are in the shortlog, it's not all that much. All of these have been in linux-next for a while with no reported issues. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCWsSiGg8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+ylPpACgyEKOur8rwp/3uBRxqhoFeWp1RtAAoIvlMjn6 MQ8LIeHNLRnpqGX5L78L =93HA -----END PGP SIGNATURE----- Merge tag 'driver-core-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core Pull driver core updates from Greg KH: "Here is the "big" set of driver core patches for 4.17-rc1. There's really not much here, just a bunch of firmware code refactoring from Luis as he attempts to wrangle that codebase into something that is managable, along with a bunch of userspace tests for it. Other than that, a handful of small bugfixes and reverts of things that didn't work out. Full details are in the shortlog, it's not all that much. All of these have been in linux-next for a while with no reported issues" * tag 'driver-core-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core: (30 commits) drivers: base: remove check for callback in coredump_store() mt7601u: use firmware_request_cache() to address cache on reboot firmware: add firmware_request_cache() to help with cache on reboot firmware: fix typo on pr_info_once() when ignore_sysfs_fallback is used firmware: explicitly include vmalloc.h firmware: ensure the firmware cache is not used on incompatible calls test_firmware: modify custom fallback tests to use unique files firmware: add helper to check to see if fw cache is setup firmware: fix checking for return values for fw_add_devm_name() rename: _request_firmware_load() fw_load_sysfs_fallback() test_firmware: test three firmware kernel configs using a proc knob test_firmware: expand on library with shared helpers firmware: enable to force disable the fallback mechanism at run time firmware: enable run time change of forcing fallback loader firmware: move firmware loader into its own directory firmware: split firmware fallback functionality into its own file firmware: move loading timeout under struct firmware_fallback_config firmware: use helpers for setting up a temporary cache timeout firmware: simplify CONFIG_FW_LOADER_USER_HELPER_FALLBACK further drivers: base: add description for .coredump() callback ...
This commit is contained in:
commit
38047d5c26
@ -112,7 +112,7 @@ Since a device is created for the sysfs interface to help load firmware as a
|
|||||||
fallback mechanism userspace can be informed of the addition of the device by
|
fallback mechanism userspace can be informed of the addition of the device by
|
||||||
relying on kobject uevents. The addition of the device into the device
|
relying on kobject uevents. The addition of the device into the device
|
||||||
hierarchy means the fallback mechanism for firmware loading has been initiated.
|
hierarchy means the fallback mechanism for firmware loading has been initiated.
|
||||||
For details of implementation refer to _request_firmware_load(), in particular
|
For details of implementation refer to fw_load_sysfs_fallback(), in particular
|
||||||
on the use of dev_set_uevent_suppress() and kobject_uevent().
|
on the use of dev_set_uevent_suppress() and kobject_uevent().
|
||||||
|
|
||||||
The kernel's kobject uevent mechanism is implemented in lib/kobject_uevent.c,
|
The kernel's kobject uevent mechanism is implemented in lib/kobject_uevent.c,
|
||||||
|
@ -44,6 +44,20 @@ request_firmware_nowait
|
|||||||
.. kernel-doc:: drivers/base/firmware_class.c
|
.. kernel-doc:: drivers/base/firmware_class.c
|
||||||
:functions: request_firmware_nowait
|
:functions: request_firmware_nowait
|
||||||
|
|
||||||
|
Special optimizations on reboot
|
||||||
|
===============================
|
||||||
|
|
||||||
|
Some devices have an optimization in place to enable the firmware to be
|
||||||
|
retained during system reboot. When such optimizations are used the driver
|
||||||
|
author must ensure the firmware is still available on resume from suspend,
|
||||||
|
this can be done with firmware_request_cache() insted of requesting for the
|
||||||
|
firmare to be loaded.
|
||||||
|
|
||||||
|
firmware_request_cache()
|
||||||
|
-----------------------
|
||||||
|
.. kernel-doc:: drivers/base/firmware_class.c
|
||||||
|
:functions: firmware_request_cache
|
||||||
|
|
||||||
request firmware API expected driver use
|
request firmware API expected driver use
|
||||||
========================================
|
========================================
|
||||||
|
|
||||||
|
@ -5524,7 +5524,7 @@ M: Luis R. Rodriguez <mcgrof@kernel.org>
|
|||||||
L: linux-kernel@vger.kernel.org
|
L: linux-kernel@vger.kernel.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: Documentation/firmware_class/
|
F: Documentation/firmware_class/
|
||||||
F: drivers/base/firmware*.c
|
F: drivers/base/firmware_loader/
|
||||||
F: include/linux/firmware.h
|
F: include/linux/firmware.h
|
||||||
|
|
||||||
FLASH ADAPTER DRIVER (IBM Flash Adapter 900GB Full Height PCI Flash Card)
|
FLASH ADAPTER DRIVER (IBM Flash Adapter 900GB Full Height PCI Flash Card)
|
||||||
|
@ -13,7 +13,7 @@ obj-y += power/
|
|||||||
obj-$(CONFIG_HAS_DMA) += dma-mapping.o
|
obj-$(CONFIG_HAS_DMA) += dma-mapping.o
|
||||||
obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.o
|
obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.o
|
||||||
obj-$(CONFIG_ISA_BUS_API) += isa.o
|
obj-$(CONFIG_ISA_BUS_API) += isa.o
|
||||||
obj-$(CONFIG_FW_LOADER) += firmware_class.o
|
obj-y += firmware_loader/
|
||||||
obj-$(CONFIG_NUMA) += node.o
|
obj-$(CONFIG_NUMA) += node.o
|
||||||
obj-$(CONFIG_MEMORY_HOTPLUG_SPARSE) += memory.o
|
obj-$(CONFIG_MEMORY_HOTPLUG_SPARSE) += memory.o
|
||||||
ifeq ($(CONFIG_SYSFS),y)
|
ifeq ($(CONFIG_SYSFS),y)
|
||||||
|
@ -169,11 +169,11 @@ bool __init topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_CPU_FREQ
|
#ifdef CONFIG_CPU_FREQ
|
||||||
static cpumask_var_t cpus_to_visit __initdata;
|
static cpumask_var_t cpus_to_visit;
|
||||||
static void __init parsing_done_workfn(struct work_struct *work);
|
static void parsing_done_workfn(struct work_struct *work);
|
||||||
static __initdata DECLARE_WORK(parsing_done_work, parsing_done_workfn);
|
static DECLARE_WORK(parsing_done_work, parsing_done_workfn);
|
||||||
|
|
||||||
static int __init
|
static int
|
||||||
init_cpu_capacity_callback(struct notifier_block *nb,
|
init_cpu_capacity_callback(struct notifier_block *nb,
|
||||||
unsigned long val,
|
unsigned long val,
|
||||||
void *data)
|
void *data)
|
||||||
@ -209,7 +209,7 @@ init_cpu_capacity_callback(struct notifier_block *nb,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct notifier_block init_cpu_capacity_notifier __initdata = {
|
static struct notifier_block init_cpu_capacity_notifier = {
|
||||||
.notifier_call = init_cpu_capacity_callback,
|
.notifier_call = init_cpu_capacity_callback,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -242,7 +242,7 @@ static int __init register_cpufreq_notifier(void)
|
|||||||
}
|
}
|
||||||
core_initcall(register_cpufreq_notifier);
|
core_initcall(register_cpufreq_notifier);
|
||||||
|
|
||||||
static void __init parsing_done_workfn(struct work_struct *work)
|
static void parsing_done_workfn(struct work_struct *work)
|
||||||
{
|
{
|
||||||
cpufreq_unregister_notifier(&init_cpu_capacity_notifier,
|
cpufreq_unregister_notifier(&init_cpu_capacity_notifier,
|
||||||
CPUFREQ_POLICY_NOTIFIER);
|
CPUFREQ_POLICY_NOTIFIER);
|
||||||
|
@ -382,8 +382,10 @@ int register_cpu(struct cpu *cpu, int num)
|
|||||||
if (cpu->hotpluggable)
|
if (cpu->hotpluggable)
|
||||||
cpu->dev.groups = hotplugable_cpu_attr_groups;
|
cpu->dev.groups = hotplugable_cpu_attr_groups;
|
||||||
error = device_register(&cpu->dev);
|
error = device_register(&cpu->dev);
|
||||||
if (error)
|
if (error) {
|
||||||
|
put_device(&cpu->dev);
|
||||||
return error;
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
per_cpu(cpu_sys_devices, num) = &cpu->dev;
|
per_cpu(cpu_sys_devices, num) = &cpu->dev;
|
||||||
register_cpu_under_node(num, cpu_to_node(num));
|
register_cpu_under_node(num, cpu_to_node(num));
|
||||||
|
@ -292,8 +292,7 @@ static ssize_t coredump_store(struct device *dev, struct device_attribute *attr,
|
|||||||
const char *buf, size_t count)
|
const char *buf, size_t count)
|
||||||
{
|
{
|
||||||
device_lock(dev);
|
device_lock(dev);
|
||||||
if (dev->driver->coredump)
|
dev->driver->coredump(dev);
|
||||||
dev->driver->coredump(dev);
|
|
||||||
device_unlock(dev);
|
device_unlock(dev);
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
|
7
drivers/base/firmware_loader/Makefile
Normal file
7
drivers/base/firmware_loader/Makefile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
# Makefile for the Linux firmware loader
|
||||||
|
|
||||||
|
obj-y := fallback_table.o
|
||||||
|
obj-$(CONFIG_FW_LOADER) += firmware_class.o
|
||||||
|
firmware_class-objs := main.o
|
||||||
|
firmware_class-$(CONFIG_FW_LOADER_USER_HELPER) += fallback.o
|
675
drivers/base/firmware_loader/fallback.c
Normal file
675
drivers/base/firmware_loader/fallback.c
Normal file
@ -0,0 +1,675 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/kconfig.h>
|
||||||
|
#include <linux/list.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/security.h>
|
||||||
|
#include <linux/highmem.h>
|
||||||
|
#include <linux/umh.h>
|
||||||
|
#include <linux/sysctl.h>
|
||||||
|
#include <linux/vmalloc.h>
|
||||||
|
|
||||||
|
#include "fallback.h"
|
||||||
|
#include "firmware.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* firmware fallback mechanism
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern struct firmware_fallback_config fw_fallback_config;
|
||||||
|
|
||||||
|
/* These getters are vetted to use int properly */
|
||||||
|
static inline int __firmware_loading_timeout(void)
|
||||||
|
{
|
||||||
|
return fw_fallback_config.loading_timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* These setters are vetted to use int properly */
|
||||||
|
static void __fw_fallback_set_timeout(int timeout)
|
||||||
|
{
|
||||||
|
fw_fallback_config.loading_timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* use small loading timeout for caching devices' firmware because all these
|
||||||
|
* firmware images have been loaded successfully at lease once, also system is
|
||||||
|
* ready for completing firmware loading now. The maximum size of firmware in
|
||||||
|
* current distributions is about 2M bytes, so 10 secs should be enough.
|
||||||
|
*/
|
||||||
|
void fw_fallback_set_cache_timeout(void)
|
||||||
|
{
|
||||||
|
fw_fallback_config.old_timeout = __firmware_loading_timeout();
|
||||||
|
__fw_fallback_set_timeout(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Restores the timeout to the value last configured during normal operation */
|
||||||
|
void fw_fallback_set_default_timeout(void)
|
||||||
|
{
|
||||||
|
__fw_fallback_set_timeout(fw_fallback_config.old_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long firmware_loading_timeout(void)
|
||||||
|
{
|
||||||
|
return __firmware_loading_timeout() > 0 ?
|
||||||
|
__firmware_loading_timeout() * HZ : MAX_JIFFY_OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool fw_sysfs_done(struct fw_priv *fw_priv)
|
||||||
|
{
|
||||||
|
return __fw_state_check(fw_priv, FW_STATUS_DONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool fw_sysfs_loading(struct fw_priv *fw_priv)
|
||||||
|
{
|
||||||
|
return __fw_state_check(fw_priv, FW_STATUS_LOADING);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int fw_sysfs_wait_timeout(struct fw_priv *fw_priv, long timeout)
|
||||||
|
{
|
||||||
|
return __fw_state_wait_common(fw_priv, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fw_sysfs {
|
||||||
|
bool nowait;
|
||||||
|
struct device dev;
|
||||||
|
struct fw_priv *fw_priv;
|
||||||
|
struct firmware *fw;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct fw_sysfs *to_fw_sysfs(struct device *dev)
|
||||||
|
{
|
||||||
|
return container_of(dev, struct fw_sysfs, dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fw_load_abort(struct fw_priv *fw_priv)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* There is a small window in which user can write to 'loading'
|
||||||
|
* between loading done and disappearance of 'loading'
|
||||||
|
*/
|
||||||
|
if (fw_sysfs_done(fw_priv))
|
||||||
|
return;
|
||||||
|
|
||||||
|
list_del_init(&fw_priv->pending_list);
|
||||||
|
fw_state_aborted(fw_priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fw_load_abort(struct fw_sysfs *fw_sysfs)
|
||||||
|
{
|
||||||
|
struct fw_priv *fw_priv = fw_sysfs->fw_priv;
|
||||||
|
|
||||||
|
__fw_load_abort(fw_priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LIST_HEAD(pending_fw_head);
|
||||||
|
|
||||||
|
void kill_pending_fw_fallback_reqs(bool only_kill_custom)
|
||||||
|
{
|
||||||
|
struct fw_priv *fw_priv;
|
||||||
|
struct fw_priv *next;
|
||||||
|
|
||||||
|
mutex_lock(&fw_lock);
|
||||||
|
list_for_each_entry_safe(fw_priv, next, &pending_fw_head,
|
||||||
|
pending_list) {
|
||||||
|
if (!fw_priv->need_uevent || !only_kill_custom)
|
||||||
|
__fw_load_abort(fw_priv);
|
||||||
|
}
|
||||||
|
mutex_unlock(&fw_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t timeout_show(struct class *class, struct class_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
return sprintf(buf, "%d\n", __firmware_loading_timeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* firmware_timeout_store - set number of seconds to wait for firmware
|
||||||
|
* @class: device class pointer
|
||||||
|
* @attr: device attribute pointer
|
||||||
|
* @buf: buffer to scan for timeout value
|
||||||
|
* @count: number of bytes in @buf
|
||||||
|
*
|
||||||
|
* Sets the number of seconds to wait for the firmware. Once
|
||||||
|
* this expires an error will be returned to the driver and no
|
||||||
|
* firmware will be provided.
|
||||||
|
*
|
||||||
|
* Note: zero means 'wait forever'.
|
||||||
|
**/
|
||||||
|
static ssize_t timeout_store(struct class *class, struct class_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
int tmp_loading_timeout = simple_strtol(buf, NULL, 10);
|
||||||
|
|
||||||
|
if (tmp_loading_timeout < 0)
|
||||||
|
tmp_loading_timeout = 0;
|
||||||
|
|
||||||
|
__fw_fallback_set_timeout(tmp_loading_timeout);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
static CLASS_ATTR_RW(timeout);
|
||||||
|
|
||||||
|
static struct attribute *firmware_class_attrs[] = {
|
||||||
|
&class_attr_timeout.attr,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
ATTRIBUTE_GROUPS(firmware_class);
|
||||||
|
|
||||||
|
static void fw_dev_release(struct device *dev)
|
||||||
|
{
|
||||||
|
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
||||||
|
|
||||||
|
kfree(fw_sysfs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_firmware_uevent(struct fw_sysfs *fw_sysfs, struct kobj_uevent_env *env)
|
||||||
|
{
|
||||||
|
if (add_uevent_var(env, "FIRMWARE=%s", fw_sysfs->fw_priv->fw_name))
|
||||||
|
return -ENOMEM;
|
||||||
|
if (add_uevent_var(env, "TIMEOUT=%i", __firmware_loading_timeout()))
|
||||||
|
return -ENOMEM;
|
||||||
|
if (add_uevent_var(env, "ASYNC=%d", fw_sysfs->nowait))
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env)
|
||||||
|
{
|
||||||
|
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
mutex_lock(&fw_lock);
|
||||||
|
if (fw_sysfs->fw_priv)
|
||||||
|
err = do_firmware_uevent(fw_sysfs, env);
|
||||||
|
mutex_unlock(&fw_lock);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct class firmware_class = {
|
||||||
|
.name = "firmware",
|
||||||
|
.class_groups = firmware_class_groups,
|
||||||
|
.dev_uevent = firmware_uevent,
|
||||||
|
.dev_release = fw_dev_release,
|
||||||
|
};
|
||||||
|
|
||||||
|
int register_sysfs_loader(void)
|
||||||
|
{
|
||||||
|
return class_register(&firmware_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unregister_sysfs_loader(void)
|
||||||
|
{
|
||||||
|
class_unregister(&firmware_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t firmware_loading_show(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
||||||
|
int loading = 0;
|
||||||
|
|
||||||
|
mutex_lock(&fw_lock);
|
||||||
|
if (fw_sysfs->fw_priv)
|
||||||
|
loading = fw_sysfs_loading(fw_sysfs->fw_priv);
|
||||||
|
mutex_unlock(&fw_lock);
|
||||||
|
|
||||||
|
return sprintf(buf, "%d\n", loading);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Some architectures don't have PAGE_KERNEL_RO */
|
||||||
|
#ifndef PAGE_KERNEL_RO
|
||||||
|
#define PAGE_KERNEL_RO PAGE_KERNEL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* one pages buffer should be mapped/unmapped only once */
|
||||||
|
static int map_fw_priv_pages(struct fw_priv *fw_priv)
|
||||||
|
{
|
||||||
|
if (!fw_priv->is_paged_buf)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
vunmap(fw_priv->data);
|
||||||
|
fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, 0,
|
||||||
|
PAGE_KERNEL_RO);
|
||||||
|
if (!fw_priv->data)
|
||||||
|
return -ENOMEM;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* firmware_loading_store - set value in the 'loading' control file
|
||||||
|
* @dev: device pointer
|
||||||
|
* @attr: device attribute pointer
|
||||||
|
* @buf: buffer to scan for loading control value
|
||||||
|
* @count: number of bytes in @buf
|
||||||
|
*
|
||||||
|
* The relevant values are:
|
||||||
|
*
|
||||||
|
* 1: Start a load, discarding any previous partial load.
|
||||||
|
* 0: Conclude the load and hand the data to the driver code.
|
||||||
|
* -1: Conclude the load with an error and discard any written data.
|
||||||
|
**/
|
||||||
|
static ssize_t firmware_loading_store(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
||||||
|
struct fw_priv *fw_priv;
|
||||||
|
ssize_t written = count;
|
||||||
|
int loading = simple_strtol(buf, NULL, 10);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
mutex_lock(&fw_lock);
|
||||||
|
fw_priv = fw_sysfs->fw_priv;
|
||||||
|
if (fw_state_is_aborted(fw_priv))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
switch (loading) {
|
||||||
|
case 1:
|
||||||
|
/* discarding any previous partial load */
|
||||||
|
if (!fw_sysfs_done(fw_priv)) {
|
||||||
|
for (i = 0; i < fw_priv->nr_pages; i++)
|
||||||
|
__free_page(fw_priv->pages[i]);
|
||||||
|
vfree(fw_priv->pages);
|
||||||
|
fw_priv->pages = NULL;
|
||||||
|
fw_priv->page_array_size = 0;
|
||||||
|
fw_priv->nr_pages = 0;
|
||||||
|
fw_state_start(fw_priv);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
if (fw_sysfs_loading(fw_priv)) {
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Several loading requests may be pending on
|
||||||
|
* one same firmware buf, so let all requests
|
||||||
|
* see the mapped 'buf->data' once the loading
|
||||||
|
* is completed.
|
||||||
|
* */
|
||||||
|
rc = map_fw_priv_pages(fw_priv);
|
||||||
|
if (rc)
|
||||||
|
dev_err(dev, "%s: map pages failed\n",
|
||||||
|
__func__);
|
||||||
|
else
|
||||||
|
rc = security_kernel_post_read_file(NULL,
|
||||||
|
fw_priv->data, fw_priv->size,
|
||||||
|
READING_FIRMWARE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Same logic as fw_load_abort, only the DONE bit
|
||||||
|
* is ignored and we set ABORT only on failure.
|
||||||
|
*/
|
||||||
|
list_del_init(&fw_priv->pending_list);
|
||||||
|
if (rc) {
|
||||||
|
fw_state_aborted(fw_priv);
|
||||||
|
written = rc;
|
||||||
|
} else {
|
||||||
|
fw_state_done(fw_priv);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* fallthrough */
|
||||||
|
default:
|
||||||
|
dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading);
|
||||||
|
/* fallthrough */
|
||||||
|
case -1:
|
||||||
|
fw_load_abort(fw_sysfs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
mutex_unlock(&fw_lock);
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store);
|
||||||
|
|
||||||
|
static void firmware_rw_data(struct fw_priv *fw_priv, char *buffer,
|
||||||
|
loff_t offset, size_t count, bool read)
|
||||||
|
{
|
||||||
|
if (read)
|
||||||
|
memcpy(buffer, fw_priv->data + offset, count);
|
||||||
|
else
|
||||||
|
memcpy(fw_priv->data + offset, buffer, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void firmware_rw(struct fw_priv *fw_priv, char *buffer,
|
||||||
|
loff_t offset, size_t count, bool read)
|
||||||
|
{
|
||||||
|
while (count) {
|
||||||
|
void *page_data;
|
||||||
|
int page_nr = offset >> PAGE_SHIFT;
|
||||||
|
int page_ofs = offset & (PAGE_SIZE-1);
|
||||||
|
int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count);
|
||||||
|
|
||||||
|
page_data = kmap(fw_priv->pages[page_nr]);
|
||||||
|
|
||||||
|
if (read)
|
||||||
|
memcpy(buffer, page_data + page_ofs, page_cnt);
|
||||||
|
else
|
||||||
|
memcpy(page_data + page_ofs, buffer, page_cnt);
|
||||||
|
|
||||||
|
kunmap(fw_priv->pages[page_nr]);
|
||||||
|
buffer += page_cnt;
|
||||||
|
offset += page_cnt;
|
||||||
|
count -= page_cnt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj,
|
||||||
|
struct bin_attribute *bin_attr,
|
||||||
|
char *buffer, loff_t offset, size_t count)
|
||||||
|
{
|
||||||
|
struct device *dev = kobj_to_dev(kobj);
|
||||||
|
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
||||||
|
struct fw_priv *fw_priv;
|
||||||
|
ssize_t ret_count;
|
||||||
|
|
||||||
|
mutex_lock(&fw_lock);
|
||||||
|
fw_priv = fw_sysfs->fw_priv;
|
||||||
|
if (!fw_priv || fw_sysfs_done(fw_priv)) {
|
||||||
|
ret_count = -ENODEV;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (offset > fw_priv->size) {
|
||||||
|
ret_count = 0;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (count > fw_priv->size - offset)
|
||||||
|
count = fw_priv->size - offset;
|
||||||
|
|
||||||
|
ret_count = count;
|
||||||
|
|
||||||
|
if (fw_priv->data)
|
||||||
|
firmware_rw_data(fw_priv, buffer, offset, count, true);
|
||||||
|
else
|
||||||
|
firmware_rw(fw_priv, buffer, offset, count, true);
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&fw_lock);
|
||||||
|
return ret_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fw_realloc_pages(struct fw_sysfs *fw_sysfs, int min_size)
|
||||||
|
{
|
||||||
|
struct fw_priv *fw_priv= fw_sysfs->fw_priv;
|
||||||
|
int pages_needed = PAGE_ALIGN(min_size) >> PAGE_SHIFT;
|
||||||
|
|
||||||
|
/* If the array of pages is too small, grow it... */
|
||||||
|
if (fw_priv->page_array_size < pages_needed) {
|
||||||
|
int new_array_size = max(pages_needed,
|
||||||
|
fw_priv->page_array_size * 2);
|
||||||
|
struct page **new_pages;
|
||||||
|
|
||||||
|
new_pages = vmalloc(new_array_size * sizeof(void *));
|
||||||
|
if (!new_pages) {
|
||||||
|
fw_load_abort(fw_sysfs);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
memcpy(new_pages, fw_priv->pages,
|
||||||
|
fw_priv->page_array_size * sizeof(void *));
|
||||||
|
memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) *
|
||||||
|
(new_array_size - fw_priv->page_array_size));
|
||||||
|
vfree(fw_priv->pages);
|
||||||
|
fw_priv->pages = new_pages;
|
||||||
|
fw_priv->page_array_size = new_array_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (fw_priv->nr_pages < pages_needed) {
|
||||||
|
fw_priv->pages[fw_priv->nr_pages] =
|
||||||
|
alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
|
||||||
|
|
||||||
|
if (!fw_priv->pages[fw_priv->nr_pages]) {
|
||||||
|
fw_load_abort(fw_sysfs);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
fw_priv->nr_pages++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* firmware_data_write - write method for firmware
|
||||||
|
* @filp: open sysfs file
|
||||||
|
* @kobj: kobject for the device
|
||||||
|
* @bin_attr: bin_attr structure
|
||||||
|
* @buffer: buffer being written
|
||||||
|
* @offset: buffer offset for write in total data store area
|
||||||
|
* @count: buffer size
|
||||||
|
*
|
||||||
|
* Data written to the 'data' attribute will be later handed to
|
||||||
|
* the driver as a firmware image.
|
||||||
|
**/
|
||||||
|
static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj,
|
||||||
|
struct bin_attribute *bin_attr,
|
||||||
|
char *buffer, loff_t offset, size_t count)
|
||||||
|
{
|
||||||
|
struct device *dev = kobj_to_dev(kobj);
|
||||||
|
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
||||||
|
struct fw_priv *fw_priv;
|
||||||
|
ssize_t retval;
|
||||||
|
|
||||||
|
if (!capable(CAP_SYS_RAWIO))
|
||||||
|
return -EPERM;
|
||||||
|
|
||||||
|
mutex_lock(&fw_lock);
|
||||||
|
fw_priv = fw_sysfs->fw_priv;
|
||||||
|
if (!fw_priv || fw_sysfs_done(fw_priv)) {
|
||||||
|
retval = -ENODEV;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fw_priv->data) {
|
||||||
|
if (offset + count > fw_priv->allocated_size) {
|
||||||
|
retval = -ENOMEM;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
firmware_rw_data(fw_priv, buffer, offset, count, false);
|
||||||
|
retval = count;
|
||||||
|
} else {
|
||||||
|
retval = fw_realloc_pages(fw_sysfs, offset + count);
|
||||||
|
if (retval)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
retval = count;
|
||||||
|
firmware_rw(fw_priv, buffer, offset, count, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fw_priv->size = max_t(size_t, offset + count, fw_priv->size);
|
||||||
|
out:
|
||||||
|
mutex_unlock(&fw_lock);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct bin_attribute firmware_attr_data = {
|
||||||
|
.attr = { .name = "data", .mode = 0644 },
|
||||||
|
.size = 0,
|
||||||
|
.read = firmware_data_read,
|
||||||
|
.write = firmware_data_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct attribute *fw_dev_attrs[] = {
|
||||||
|
&dev_attr_loading.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct bin_attribute *fw_dev_bin_attrs[] = {
|
||||||
|
&firmware_attr_data,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group fw_dev_attr_group = {
|
||||||
|
.attrs = fw_dev_attrs,
|
||||||
|
.bin_attrs = fw_dev_bin_attrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group *fw_dev_attr_groups[] = {
|
||||||
|
&fw_dev_attr_group,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct fw_sysfs *
|
||||||
|
fw_create_instance(struct firmware *firmware, const char *fw_name,
|
||||||
|
struct device *device, unsigned int opt_flags)
|
||||||
|
{
|
||||||
|
struct fw_sysfs *fw_sysfs;
|
||||||
|
struct device *f_dev;
|
||||||
|
|
||||||
|
fw_sysfs = kzalloc(sizeof(*fw_sysfs), GFP_KERNEL);
|
||||||
|
if (!fw_sysfs) {
|
||||||
|
fw_sysfs = ERR_PTR(-ENOMEM);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
fw_sysfs->nowait = !!(opt_flags & FW_OPT_NOWAIT);
|
||||||
|
fw_sysfs->fw = firmware;
|
||||||
|
f_dev = &fw_sysfs->dev;
|
||||||
|
|
||||||
|
device_initialize(f_dev);
|
||||||
|
dev_set_name(f_dev, "%s", fw_name);
|
||||||
|
f_dev->parent = device;
|
||||||
|
f_dev->class = &firmware_class;
|
||||||
|
f_dev->groups = fw_dev_attr_groups;
|
||||||
|
exit:
|
||||||
|
return fw_sysfs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fw_load_sysfs_fallback - load a firmware via the syfs fallback mechanism
|
||||||
|
* @fw_sysfs: firmware syfs information for the firmware to load
|
||||||
|
* @opt_flags: flags of options, FW_OPT_*
|
||||||
|
* @timeout: timeout to wait for the load
|
||||||
|
*
|
||||||
|
* In charge of constructing a sysfs fallback interface for firmware loading.
|
||||||
|
**/
|
||||||
|
static int fw_load_sysfs_fallback(struct fw_sysfs *fw_sysfs,
|
||||||
|
unsigned int opt_flags, long timeout)
|
||||||
|
{
|
||||||
|
int retval = 0;
|
||||||
|
struct device *f_dev = &fw_sysfs->dev;
|
||||||
|
struct fw_priv *fw_priv = fw_sysfs->fw_priv;
|
||||||
|
|
||||||
|
/* fall back on userspace loading */
|
||||||
|
if (!fw_priv->data)
|
||||||
|
fw_priv->is_paged_buf = true;
|
||||||
|
|
||||||
|
dev_set_uevent_suppress(f_dev, true);
|
||||||
|
|
||||||
|
retval = device_add(f_dev);
|
||||||
|
if (retval) {
|
||||||
|
dev_err(f_dev, "%s: device_register failed\n", __func__);
|
||||||
|
goto err_put_dev;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&fw_lock);
|
||||||
|
list_add(&fw_priv->pending_list, &pending_fw_head);
|
||||||
|
mutex_unlock(&fw_lock);
|
||||||
|
|
||||||
|
if (opt_flags & FW_OPT_UEVENT) {
|
||||||
|
fw_priv->need_uevent = true;
|
||||||
|
dev_set_uevent_suppress(f_dev, false);
|
||||||
|
dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_name);
|
||||||
|
kobject_uevent(&fw_sysfs->dev.kobj, KOBJ_ADD);
|
||||||
|
} else {
|
||||||
|
timeout = MAX_JIFFY_OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
retval = fw_sysfs_wait_timeout(fw_priv, timeout);
|
||||||
|
if (retval < 0) {
|
||||||
|
mutex_lock(&fw_lock);
|
||||||
|
fw_load_abort(fw_sysfs);
|
||||||
|
mutex_unlock(&fw_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fw_state_is_aborted(fw_priv)) {
|
||||||
|
if (retval == -ERESTARTSYS)
|
||||||
|
retval = -EINTR;
|
||||||
|
else
|
||||||
|
retval = -EAGAIN;
|
||||||
|
} else if (fw_priv->is_paged_buf && !fw_priv->data)
|
||||||
|
retval = -ENOMEM;
|
||||||
|
|
||||||
|
device_del(f_dev);
|
||||||
|
err_put_dev:
|
||||||
|
put_device(f_dev);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fw_load_from_user_helper(struct firmware *firmware,
|
||||||
|
const char *name, struct device *device,
|
||||||
|
unsigned int opt_flags)
|
||||||
|
{
|
||||||
|
struct fw_sysfs *fw_sysfs;
|
||||||
|
long timeout;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
timeout = firmware_loading_timeout();
|
||||||
|
if (opt_flags & FW_OPT_NOWAIT) {
|
||||||
|
timeout = usermodehelper_read_lock_wait(timeout);
|
||||||
|
if (!timeout) {
|
||||||
|
dev_dbg(device, "firmware: %s loading timed out\n",
|
||||||
|
name);
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret = usermodehelper_read_trylock();
|
||||||
|
if (WARN_ON(ret)) {
|
||||||
|
dev_err(device, "firmware: %s will not be loaded\n",
|
||||||
|
name);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fw_sysfs = fw_create_instance(firmware, name, device, opt_flags);
|
||||||
|
if (IS_ERR(fw_sysfs)) {
|
||||||
|
ret = PTR_ERR(fw_sysfs);
|
||||||
|
goto out_unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
fw_sysfs->fw_priv = firmware->priv;
|
||||||
|
ret = fw_load_sysfs_fallback(fw_sysfs, opt_flags, timeout);
|
||||||
|
|
||||||
|
if (!ret)
|
||||||
|
ret = assign_fw(firmware, device, opt_flags);
|
||||||
|
|
||||||
|
out_unlock:
|
||||||
|
usermodehelper_read_unlock();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fw_force_sysfs_fallback(unsigned int opt_flags)
|
||||||
|
{
|
||||||
|
if (fw_fallback_config.force_sysfs_fallback)
|
||||||
|
return true;
|
||||||
|
if (!(opt_flags & FW_OPT_USERHELPER))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fw_run_sysfs_fallback(unsigned int opt_flags)
|
||||||
|
{
|
||||||
|
if (fw_fallback_config.ignore_sysfs_fallback) {
|
||||||
|
pr_info_once("Ignoring firmware sysfs fallback due to sysctl knob\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((opt_flags & FW_OPT_NOFALLBACK))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return fw_force_sysfs_fallback(opt_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fw_sysfs_fallback(struct firmware *fw, const char *name,
|
||||||
|
struct device *device,
|
||||||
|
unsigned int opt_flags,
|
||||||
|
int ret)
|
||||||
|
{
|
||||||
|
if (!fw_run_sysfs_fallback(opt_flags))
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
dev_warn(device, "Falling back to user helper\n");
|
||||||
|
return fw_load_from_user_helper(fw, name, device, opt_flags);
|
||||||
|
}
|
67
drivers/base/firmware_loader/fallback.h
Normal file
67
drivers/base/firmware_loader/fallback.h
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
#ifndef __FIRMWARE_FALLBACK_H
|
||||||
|
#define __FIRMWARE_FALLBACK_H
|
||||||
|
|
||||||
|
#include <linux/firmware.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct firmware_fallback_config - firmware fallback configuratioon settings
|
||||||
|
*
|
||||||
|
* Helps describe and fine tune the fallback mechanism.
|
||||||
|
*
|
||||||
|
* @force_sysfs_fallback: force the sysfs fallback mechanism to be used
|
||||||
|
* as if one had enabled CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y.
|
||||||
|
* Useful to help debug a CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y
|
||||||
|
* functionality on a kernel where that config entry has been disabled.
|
||||||
|
* @ignore_sysfs_fallback: force to disable the sysfs fallback mechanism.
|
||||||
|
* This emulates the behaviour as if we had set the kernel
|
||||||
|
* config CONFIG_FW_LOADER_USER_HELPER=n.
|
||||||
|
* @old_timeout: for internal use
|
||||||
|
* @loading_timeout: the timeout to wait for the fallback mechanism before
|
||||||
|
* giving up, in seconds.
|
||||||
|
*/
|
||||||
|
struct firmware_fallback_config {
|
||||||
|
unsigned int force_sysfs_fallback;
|
||||||
|
unsigned int ignore_sysfs_fallback;
|
||||||
|
int old_timeout;
|
||||||
|
int loading_timeout;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
||||||
|
int fw_sysfs_fallback(struct firmware *fw, const char *name,
|
||||||
|
struct device *device,
|
||||||
|
unsigned int opt_flags,
|
||||||
|
int ret);
|
||||||
|
void kill_pending_fw_fallback_reqs(bool only_kill_custom);
|
||||||
|
|
||||||
|
void fw_fallback_set_cache_timeout(void);
|
||||||
|
void fw_fallback_set_default_timeout(void);
|
||||||
|
|
||||||
|
int register_sysfs_loader(void);
|
||||||
|
void unregister_sysfs_loader(void);
|
||||||
|
#else /* CONFIG_FW_LOADER_USER_HELPER */
|
||||||
|
static inline int fw_sysfs_fallback(struct firmware *fw, const char *name,
|
||||||
|
struct device *device,
|
||||||
|
unsigned int opt_flags,
|
||||||
|
int ret)
|
||||||
|
{
|
||||||
|
/* Keep carrying over the same error */
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void kill_pending_fw_fallback_reqs(bool only_kill_custom) { }
|
||||||
|
static inline void fw_fallback_set_cache_timeout(void) { }
|
||||||
|
static inline void fw_fallback_set_default_timeout(void) { }
|
||||||
|
|
||||||
|
static inline int register_sysfs_loader(void)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void unregister_sysfs_loader(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_FW_LOADER_USER_HELPER */
|
||||||
|
|
||||||
|
#endif /* __FIRMWARE_FALLBACK_H */
|
55
drivers/base/firmware_loader/fallback_table.c
Normal file
55
drivers/base/firmware_loader/fallback_table.c
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/kconfig.h>
|
||||||
|
#include <linux/list.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/security.h>
|
||||||
|
#include <linux/highmem.h>
|
||||||
|
#include <linux/umh.h>
|
||||||
|
#include <linux/sysctl.h>
|
||||||
|
|
||||||
|
#include "fallback.h"
|
||||||
|
#include "firmware.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* firmware fallback configuration table
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Module or buit-in */
|
||||||
|
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
||||||
|
|
||||||
|
static unsigned int zero;
|
||||||
|
static unsigned int one = 1;
|
||||||
|
|
||||||
|
struct firmware_fallback_config fw_fallback_config = {
|
||||||
|
.force_sysfs_fallback = IS_ENABLED(CONFIG_FW_LOADER_USER_HELPER_FALLBACK),
|
||||||
|
.loading_timeout = 60,
|
||||||
|
.old_timeout = 60,
|
||||||
|
};
|
||||||
|
EXPORT_SYMBOL_GPL(fw_fallback_config);
|
||||||
|
|
||||||
|
struct ctl_table firmware_config_table[] = {
|
||||||
|
{
|
||||||
|
.procname = "force_sysfs_fallback",
|
||||||
|
.data = &fw_fallback_config.force_sysfs_fallback,
|
||||||
|
.maxlen = sizeof(unsigned int),
|
||||||
|
.mode = 0644,
|
||||||
|
.proc_handler = proc_douintvec_minmax,
|
||||||
|
.extra1 = &zero,
|
||||||
|
.extra2 = &one,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.procname = "ignore_sysfs_fallback",
|
||||||
|
.data = &fw_fallback_config.ignore_sysfs_fallback,
|
||||||
|
.maxlen = sizeof(unsigned int),
|
||||||
|
.mode = 0644,
|
||||||
|
.proc_handler = proc_douintvec_minmax,
|
||||||
|
.extra1 = &zero,
|
||||||
|
.extra2 = &one,
|
||||||
|
},
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
EXPORT_SYMBOL_GPL(firmware_config_table);
|
||||||
|
|
||||||
|
#endif
|
115
drivers/base/firmware_loader/firmware.h
Normal file
115
drivers/base/firmware_loader/firmware.h
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
#ifndef __FIRMWARE_LOADER_H
|
||||||
|
#define __FIRMWARE_LOADER_H
|
||||||
|
|
||||||
|
#include <linux/firmware.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/kref.h>
|
||||||
|
#include <linux/list.h>
|
||||||
|
#include <linux/completion.h>
|
||||||
|
|
||||||
|
#include <generated/utsrelease.h>
|
||||||
|
|
||||||
|
/* firmware behavior options */
|
||||||
|
#define FW_OPT_UEVENT (1U << 0)
|
||||||
|
#define FW_OPT_NOWAIT (1U << 1)
|
||||||
|
#define FW_OPT_USERHELPER (1U << 2)
|
||||||
|
#define FW_OPT_NO_WARN (1U << 3)
|
||||||
|
#define FW_OPT_NOCACHE (1U << 4)
|
||||||
|
#define FW_OPT_NOFALLBACK (1U << 5)
|
||||||
|
|
||||||
|
enum fw_status {
|
||||||
|
FW_STATUS_UNKNOWN,
|
||||||
|
FW_STATUS_LOADING,
|
||||||
|
FW_STATUS_DONE,
|
||||||
|
FW_STATUS_ABORTED,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Concurrent request_firmware() for the same firmware need to be
|
||||||
|
* serialized. struct fw_state is simple state machine which hold the
|
||||||
|
* state of the firmware loading.
|
||||||
|
*/
|
||||||
|
struct fw_state {
|
||||||
|
struct completion completion;
|
||||||
|
enum fw_status status;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fw_priv {
|
||||||
|
struct kref ref;
|
||||||
|
struct list_head list;
|
||||||
|
struct firmware_cache *fwc;
|
||||||
|
struct fw_state fw_st;
|
||||||
|
void *data;
|
||||||
|
size_t size;
|
||||||
|
size_t allocated_size;
|
||||||
|
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
||||||
|
bool is_paged_buf;
|
||||||
|
bool need_uevent;
|
||||||
|
struct page **pages;
|
||||||
|
int nr_pages;
|
||||||
|
int page_array_size;
|
||||||
|
struct list_head pending_list;
|
||||||
|
#endif
|
||||||
|
const char *fw_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct mutex fw_lock;
|
||||||
|
|
||||||
|
static inline bool __fw_state_check(struct fw_priv *fw_priv,
|
||||||
|
enum fw_status status)
|
||||||
|
{
|
||||||
|
struct fw_state *fw_st = &fw_priv->fw_st;
|
||||||
|
|
||||||
|
return fw_st->status == status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int __fw_state_wait_common(struct fw_priv *fw_priv, long timeout)
|
||||||
|
{
|
||||||
|
struct fw_state *fw_st = &fw_priv->fw_st;
|
||||||
|
long ret;
|
||||||
|
|
||||||
|
ret = wait_for_completion_killable_timeout(&fw_st->completion, timeout);
|
||||||
|
if (ret != 0 && fw_st->status == FW_STATUS_ABORTED)
|
||||||
|
return -ENOENT;
|
||||||
|
if (!ret)
|
||||||
|
return -ETIMEDOUT;
|
||||||
|
|
||||||
|
return ret < 0 ? ret : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void __fw_state_set(struct fw_priv *fw_priv,
|
||||||
|
enum fw_status status)
|
||||||
|
{
|
||||||
|
struct fw_state *fw_st = &fw_priv->fw_st;
|
||||||
|
|
||||||
|
WRITE_ONCE(fw_st->status, status);
|
||||||
|
|
||||||
|
if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED)
|
||||||
|
complete_all(&fw_st->completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void fw_state_aborted(struct fw_priv *fw_priv)
|
||||||
|
{
|
||||||
|
__fw_state_set(fw_priv, FW_STATUS_ABORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool fw_state_is_aborted(struct fw_priv *fw_priv)
|
||||||
|
{
|
||||||
|
return __fw_state_check(fw_priv, FW_STATUS_ABORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void fw_state_start(struct fw_priv *fw_priv)
|
||||||
|
{
|
||||||
|
__fw_state_set(fw_priv, FW_STATUS_LOADING);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void fw_state_done(struct fw_priv *fw_priv)
|
||||||
|
{
|
||||||
|
__fw_state_set(fw_priv, FW_STATUS_DONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int assign_fw(struct firmware *fw, struct device *device,
|
||||||
|
unsigned int opt_flags);
|
||||||
|
|
||||||
|
#endif /* __FIRMWARE_LOADER_H */
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
/*
|
/*
|
||||||
* firmware_class.c - Multi purpose firmware loading support
|
* main.c - Multi purpose firmware loading support
|
||||||
*
|
*
|
||||||
* Copyright (c) 2003 Manuel Estrada Sainz
|
* Copyright (c) 2003 Manuel Estrada Sainz
|
||||||
*
|
*
|
||||||
@ -36,37 +36,14 @@
|
|||||||
|
|
||||||
#include <generated/utsrelease.h>
|
#include <generated/utsrelease.h>
|
||||||
|
|
||||||
#include "base.h"
|
#include "../base.h"
|
||||||
|
#include "firmware.h"
|
||||||
|
#include "fallback.h"
|
||||||
|
|
||||||
MODULE_AUTHOR("Manuel Estrada Sainz");
|
MODULE_AUTHOR("Manuel Estrada Sainz");
|
||||||
MODULE_DESCRIPTION("Multi purpose firmware loading support");
|
MODULE_DESCRIPTION("Multi purpose firmware loading support");
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
|
|
||||||
enum fw_status {
|
|
||||||
FW_STATUS_UNKNOWN,
|
|
||||||
FW_STATUS_LOADING,
|
|
||||||
FW_STATUS_DONE,
|
|
||||||
FW_STATUS_ABORTED,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Concurrent request_firmware() for the same firmware need to be
|
|
||||||
* serialized. struct fw_state is simple state machine which hold the
|
|
||||||
* state of the firmware loading.
|
|
||||||
*/
|
|
||||||
struct fw_state {
|
|
||||||
struct completion completion;
|
|
||||||
enum fw_status status;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* firmware behavior options */
|
|
||||||
#define FW_OPT_UEVENT (1U << 0)
|
|
||||||
#define FW_OPT_NOWAIT (1U << 1)
|
|
||||||
#define FW_OPT_USERHELPER (1U << 2)
|
|
||||||
#define FW_OPT_NO_WARN (1U << 3)
|
|
||||||
#define FW_OPT_NOCACHE (1U << 4)
|
|
||||||
#define FW_OPT_NOFALLBACK (1U << 5)
|
|
||||||
|
|
||||||
struct firmware_cache {
|
struct firmware_cache {
|
||||||
/* firmware_buf instance will be added into the below list */
|
/* firmware_buf instance will be added into the below list */
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
@ -89,25 +66,6 @@ struct firmware_cache {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct fw_priv {
|
|
||||||
struct kref ref;
|
|
||||||
struct list_head list;
|
|
||||||
struct firmware_cache *fwc;
|
|
||||||
struct fw_state fw_st;
|
|
||||||
void *data;
|
|
||||||
size_t size;
|
|
||||||
size_t allocated_size;
|
|
||||||
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
|
||||||
bool is_paged_buf;
|
|
||||||
bool need_uevent;
|
|
||||||
struct page **pages;
|
|
||||||
int nr_pages;
|
|
||||||
int page_array_size;
|
|
||||||
struct list_head pending_list;
|
|
||||||
#endif
|
|
||||||
const char *fw_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct fw_cache_entry {
|
struct fw_cache_entry {
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
const char *name;
|
const char *name;
|
||||||
@ -128,7 +86,7 @@ static inline struct fw_priv *to_fw_priv(struct kref *ref)
|
|||||||
|
|
||||||
/* fw_lock could be moved to 'struct fw_sysfs' but since it is just
|
/* fw_lock could be moved to 'struct fw_sysfs' but since it is just
|
||||||
* guarding for corner cases a global lock should be OK */
|
* guarding for corner cases a global lock should be OK */
|
||||||
static DEFINE_MUTEX(fw_lock);
|
DEFINE_MUTEX(fw_lock);
|
||||||
|
|
||||||
static struct firmware_cache fw_cache;
|
static struct firmware_cache fw_cache;
|
||||||
|
|
||||||
@ -191,13 +149,6 @@ static inline bool fw_is_builtin_firmware(const struct firmware *fw)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int loading_timeout = 60; /* In seconds */
|
|
||||||
|
|
||||||
static inline long firmware_loading_timeout(void)
|
|
||||||
{
|
|
||||||
return loading_timeout > 0 ? loading_timeout * HZ : MAX_JIFFY_OFFSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fw_state_init(struct fw_priv *fw_priv)
|
static void fw_state_init(struct fw_priv *fw_priv)
|
||||||
{
|
{
|
||||||
struct fw_state *fw_st = &fw_priv->fw_st;
|
struct fw_state *fw_st = &fw_priv->fw_st;
|
||||||
@ -206,83 +157,11 @@ static void fw_state_init(struct fw_priv *fw_priv)
|
|||||||
fw_st->status = FW_STATUS_UNKNOWN;
|
fw_st->status = FW_STATUS_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __fw_state_wait_common(struct fw_priv *fw_priv, long timeout)
|
|
||||||
{
|
|
||||||
struct fw_state *fw_st = &fw_priv->fw_st;
|
|
||||||
long ret;
|
|
||||||
|
|
||||||
ret = wait_for_completion_killable_timeout(&fw_st->completion, timeout);
|
|
||||||
if (ret != 0 && fw_st->status == FW_STATUS_ABORTED)
|
|
||||||
return -ENOENT;
|
|
||||||
if (!ret)
|
|
||||||
return -ETIMEDOUT;
|
|
||||||
|
|
||||||
return ret < 0 ? ret : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __fw_state_set(struct fw_priv *fw_priv,
|
|
||||||
enum fw_status status)
|
|
||||||
{
|
|
||||||
struct fw_state *fw_st = &fw_priv->fw_st;
|
|
||||||
|
|
||||||
WRITE_ONCE(fw_st->status, status);
|
|
||||||
|
|
||||||
if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED)
|
|
||||||
complete_all(&fw_st->completion);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void fw_state_start(struct fw_priv *fw_priv)
|
|
||||||
{
|
|
||||||
__fw_state_set(fw_priv, FW_STATUS_LOADING);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void fw_state_done(struct fw_priv *fw_priv)
|
|
||||||
{
|
|
||||||
__fw_state_set(fw_priv, FW_STATUS_DONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void fw_state_aborted(struct fw_priv *fw_priv)
|
|
||||||
{
|
|
||||||
__fw_state_set(fw_priv, FW_STATUS_ABORTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int fw_state_wait(struct fw_priv *fw_priv)
|
static inline int fw_state_wait(struct fw_priv *fw_priv)
|
||||||
{
|
{
|
||||||
return __fw_state_wait_common(fw_priv, MAX_SCHEDULE_TIMEOUT);
|
return __fw_state_wait_common(fw_priv, MAX_SCHEDULE_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool __fw_state_check(struct fw_priv *fw_priv,
|
|
||||||
enum fw_status status)
|
|
||||||
{
|
|
||||||
struct fw_state *fw_st = &fw_priv->fw_st;
|
|
||||||
|
|
||||||
return fw_st->status == status;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool fw_state_is_aborted(struct fw_priv *fw_priv)
|
|
||||||
{
|
|
||||||
return __fw_state_check(fw_priv, FW_STATUS_ABORTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
|
||||||
|
|
||||||
static inline bool fw_sysfs_done(struct fw_priv *fw_priv)
|
|
||||||
{
|
|
||||||
return __fw_state_check(fw_priv, FW_STATUS_DONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool fw_sysfs_loading(struct fw_priv *fw_priv)
|
|
||||||
{
|
|
||||||
return __fw_state_check(fw_priv, FW_STATUS_LOADING);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int fw_sysfs_wait_timeout(struct fw_priv *fw_priv, long timeout)
|
|
||||||
{
|
|
||||||
return __fw_state_wait_common(fw_priv, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* CONFIG_FW_LOADER_USER_HELPER */
|
|
||||||
|
|
||||||
static int fw_cache_piggyback_on_request(const char *name);
|
static int fw_cache_piggyback_on_request(const char *name);
|
||||||
|
|
||||||
static struct fw_priv *__allocate_fw_priv(const char *fw_name,
|
static struct fw_priv *__allocate_fw_priv(const char *fw_name,
|
||||||
@ -517,14 +396,24 @@ static struct fw_name_devm *fw_find_devm_name(struct device *dev,
|
|||||||
return fwn;
|
return fwn;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* add firmware name into devres list */
|
static bool fw_cache_is_setup(struct device *dev, const char *name)
|
||||||
static int fw_add_devm_name(struct device *dev, const char *name)
|
|
||||||
{
|
{
|
||||||
struct fw_name_devm *fwn;
|
struct fw_name_devm *fwn;
|
||||||
|
|
||||||
fwn = fw_find_devm_name(dev, name);
|
fwn = fw_find_devm_name(dev, name);
|
||||||
if (fwn)
|
if (fwn)
|
||||||
return 1;
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add firmware name into devres list */
|
||||||
|
static int fw_add_devm_name(struct device *dev, const char *name)
|
||||||
|
{
|
||||||
|
struct fw_name_devm *fwn;
|
||||||
|
|
||||||
|
if (fw_cache_is_setup(dev, name))
|
||||||
|
return 0;
|
||||||
|
|
||||||
fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm),
|
fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm),
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
@ -542,16 +431,22 @@ static int fw_add_devm_name(struct device *dev, const char *name)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
static bool fw_cache_is_setup(struct device *dev, const char *name)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static int fw_add_devm_name(struct device *dev, const char *name)
|
static int fw_add_devm_name(struct device *dev, const char *name)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int assign_fw(struct firmware *fw, struct device *device,
|
int assign_fw(struct firmware *fw, struct device *device,
|
||||||
unsigned int opt_flags)
|
unsigned int opt_flags)
|
||||||
{
|
{
|
||||||
struct fw_priv *fw_priv = fw->priv;
|
struct fw_priv *fw_priv = fw->priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
mutex_lock(&fw_lock);
|
mutex_lock(&fw_lock);
|
||||||
if (!fw_priv->size || fw_state_is_aborted(fw_priv)) {
|
if (!fw_priv->size || fw_state_is_aborted(fw_priv)) {
|
||||||
@ -568,8 +463,13 @@ static int assign_fw(struct firmware *fw, struct device *device,
|
|||||||
*/
|
*/
|
||||||
/* don't cache firmware handled without uevent */
|
/* don't cache firmware handled without uevent */
|
||||||
if (device && (opt_flags & FW_OPT_UEVENT) &&
|
if (device && (opt_flags & FW_OPT_UEVENT) &&
|
||||||
!(opt_flags & FW_OPT_NOCACHE))
|
!(opt_flags & FW_OPT_NOCACHE)) {
|
||||||
fw_add_devm_name(device, fw_priv->fw_name);
|
ret = fw_add_devm_name(device, fw_priv->fw_name);
|
||||||
|
if (ret) {
|
||||||
|
mutex_unlock(&fw_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* After caching firmware image is started, let it piggyback
|
* After caching firmware image is started, let it piggyback
|
||||||
@ -587,626 +487,6 @@ static int assign_fw(struct firmware *fw, struct device *device,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* user-mode helper code
|
|
||||||
*/
|
|
||||||
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
|
||||||
struct fw_sysfs {
|
|
||||||
bool nowait;
|
|
||||||
struct device dev;
|
|
||||||
struct fw_priv *fw_priv;
|
|
||||||
struct firmware *fw;
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct fw_sysfs *to_fw_sysfs(struct device *dev)
|
|
||||||
{
|
|
||||||
return container_of(dev, struct fw_sysfs, dev);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __fw_load_abort(struct fw_priv *fw_priv)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* There is a small window in which user can write to 'loading'
|
|
||||||
* between loading done and disappearance of 'loading'
|
|
||||||
*/
|
|
||||||
if (fw_sysfs_done(fw_priv))
|
|
||||||
return;
|
|
||||||
|
|
||||||
list_del_init(&fw_priv->pending_list);
|
|
||||||
fw_state_aborted(fw_priv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fw_load_abort(struct fw_sysfs *fw_sysfs)
|
|
||||||
{
|
|
||||||
struct fw_priv *fw_priv = fw_sysfs->fw_priv;
|
|
||||||
|
|
||||||
__fw_load_abort(fw_priv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static LIST_HEAD(pending_fw_head);
|
|
||||||
|
|
||||||
static void kill_pending_fw_fallback_reqs(bool only_kill_custom)
|
|
||||||
{
|
|
||||||
struct fw_priv *fw_priv;
|
|
||||||
struct fw_priv *next;
|
|
||||||
|
|
||||||
mutex_lock(&fw_lock);
|
|
||||||
list_for_each_entry_safe(fw_priv, next, &pending_fw_head,
|
|
||||||
pending_list) {
|
|
||||||
if (!fw_priv->need_uevent || !only_kill_custom)
|
|
||||||
__fw_load_abort(fw_priv);
|
|
||||||
}
|
|
||||||
mutex_unlock(&fw_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t timeout_show(struct class *class, struct class_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
return sprintf(buf, "%d\n", loading_timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* firmware_timeout_store - set number of seconds to wait for firmware
|
|
||||||
* @class: device class pointer
|
|
||||||
* @attr: device attribute pointer
|
|
||||||
* @buf: buffer to scan for timeout value
|
|
||||||
* @count: number of bytes in @buf
|
|
||||||
*
|
|
||||||
* Sets the number of seconds to wait for the firmware. Once
|
|
||||||
* this expires an error will be returned to the driver and no
|
|
||||||
* firmware will be provided.
|
|
||||||
*
|
|
||||||
* Note: zero means 'wait forever'.
|
|
||||||
**/
|
|
||||||
static ssize_t timeout_store(struct class *class, struct class_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
loading_timeout = simple_strtol(buf, NULL, 10);
|
|
||||||
if (loading_timeout < 0)
|
|
||||||
loading_timeout = 0;
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
static CLASS_ATTR_RW(timeout);
|
|
||||||
|
|
||||||
static struct attribute *firmware_class_attrs[] = {
|
|
||||||
&class_attr_timeout.attr,
|
|
||||||
NULL,
|
|
||||||
};
|
|
||||||
ATTRIBUTE_GROUPS(firmware_class);
|
|
||||||
|
|
||||||
static void fw_dev_release(struct device *dev)
|
|
||||||
{
|
|
||||||
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
|
||||||
|
|
||||||
kfree(fw_sysfs);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int do_firmware_uevent(struct fw_sysfs *fw_sysfs, struct kobj_uevent_env *env)
|
|
||||||
{
|
|
||||||
if (add_uevent_var(env, "FIRMWARE=%s", fw_sysfs->fw_priv->fw_name))
|
|
||||||
return -ENOMEM;
|
|
||||||
if (add_uevent_var(env, "TIMEOUT=%i", loading_timeout))
|
|
||||||
return -ENOMEM;
|
|
||||||
if (add_uevent_var(env, "ASYNC=%d", fw_sysfs->nowait))
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
||||||
{
|
|
||||||
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
|
||||||
int err = 0;
|
|
||||||
|
|
||||||
mutex_lock(&fw_lock);
|
|
||||||
if (fw_sysfs->fw_priv)
|
|
||||||
err = do_firmware_uevent(fw_sysfs, env);
|
|
||||||
mutex_unlock(&fw_lock);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct class firmware_class = {
|
|
||||||
.name = "firmware",
|
|
||||||
.class_groups = firmware_class_groups,
|
|
||||||
.dev_uevent = firmware_uevent,
|
|
||||||
.dev_release = fw_dev_release,
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline int register_sysfs_loader(void)
|
|
||||||
{
|
|
||||||
return class_register(&firmware_class);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void unregister_sysfs_loader(void)
|
|
||||||
{
|
|
||||||
class_unregister(&firmware_class);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t firmware_loading_show(struct device *dev,
|
|
||||||
struct device_attribute *attr, char *buf)
|
|
||||||
{
|
|
||||||
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
|
||||||
int loading = 0;
|
|
||||||
|
|
||||||
mutex_lock(&fw_lock);
|
|
||||||
if (fw_sysfs->fw_priv)
|
|
||||||
loading = fw_sysfs_loading(fw_sysfs->fw_priv);
|
|
||||||
mutex_unlock(&fw_lock);
|
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", loading);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Some architectures don't have PAGE_KERNEL_RO */
|
|
||||||
#ifndef PAGE_KERNEL_RO
|
|
||||||
#define PAGE_KERNEL_RO PAGE_KERNEL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* one pages buffer should be mapped/unmapped only once */
|
|
||||||
static int map_fw_priv_pages(struct fw_priv *fw_priv)
|
|
||||||
{
|
|
||||||
if (!fw_priv->is_paged_buf)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
vunmap(fw_priv->data);
|
|
||||||
fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, 0,
|
|
||||||
PAGE_KERNEL_RO);
|
|
||||||
if (!fw_priv->data)
|
|
||||||
return -ENOMEM;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* firmware_loading_store - set value in the 'loading' control file
|
|
||||||
* @dev: device pointer
|
|
||||||
* @attr: device attribute pointer
|
|
||||||
* @buf: buffer to scan for loading control value
|
|
||||||
* @count: number of bytes in @buf
|
|
||||||
*
|
|
||||||
* The relevant values are:
|
|
||||||
*
|
|
||||||
* 1: Start a load, discarding any previous partial load.
|
|
||||||
* 0: Conclude the load and hand the data to the driver code.
|
|
||||||
* -1: Conclude the load with an error and discard any written data.
|
|
||||||
**/
|
|
||||||
static ssize_t firmware_loading_store(struct device *dev,
|
|
||||||
struct device_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
|
||||||
struct fw_priv *fw_priv;
|
|
||||||
ssize_t written = count;
|
|
||||||
int loading = simple_strtol(buf, NULL, 10);
|
|
||||||
int i;
|
|
||||||
|
|
||||||
mutex_lock(&fw_lock);
|
|
||||||
fw_priv = fw_sysfs->fw_priv;
|
|
||||||
if (fw_state_is_aborted(fw_priv))
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
switch (loading) {
|
|
||||||
case 1:
|
|
||||||
/* discarding any previous partial load */
|
|
||||||
if (!fw_sysfs_done(fw_priv)) {
|
|
||||||
for (i = 0; i < fw_priv->nr_pages; i++)
|
|
||||||
__free_page(fw_priv->pages[i]);
|
|
||||||
vfree(fw_priv->pages);
|
|
||||||
fw_priv->pages = NULL;
|
|
||||||
fw_priv->page_array_size = 0;
|
|
||||||
fw_priv->nr_pages = 0;
|
|
||||||
fw_state_start(fw_priv);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
if (fw_sysfs_loading(fw_priv)) {
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Several loading requests may be pending on
|
|
||||||
* one same firmware buf, so let all requests
|
|
||||||
* see the mapped 'buf->data' once the loading
|
|
||||||
* is completed.
|
|
||||||
* */
|
|
||||||
rc = map_fw_priv_pages(fw_priv);
|
|
||||||
if (rc)
|
|
||||||
dev_err(dev, "%s: map pages failed\n",
|
|
||||||
__func__);
|
|
||||||
else
|
|
||||||
rc = security_kernel_post_read_file(NULL,
|
|
||||||
fw_priv->data, fw_priv->size,
|
|
||||||
READING_FIRMWARE);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Same logic as fw_load_abort, only the DONE bit
|
|
||||||
* is ignored and we set ABORT only on failure.
|
|
||||||
*/
|
|
||||||
list_del_init(&fw_priv->pending_list);
|
|
||||||
if (rc) {
|
|
||||||
fw_state_aborted(fw_priv);
|
|
||||||
written = rc;
|
|
||||||
} else {
|
|
||||||
fw_state_done(fw_priv);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
/* fallthrough */
|
|
||||||
default:
|
|
||||||
dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading);
|
|
||||||
/* fallthrough */
|
|
||||||
case -1:
|
|
||||||
fw_load_abort(fw_sysfs);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
out:
|
|
||||||
mutex_unlock(&fw_lock);
|
|
||||||
return written;
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store);
|
|
||||||
|
|
||||||
static void firmware_rw_data(struct fw_priv *fw_priv, char *buffer,
|
|
||||||
loff_t offset, size_t count, bool read)
|
|
||||||
{
|
|
||||||
if (read)
|
|
||||||
memcpy(buffer, fw_priv->data + offset, count);
|
|
||||||
else
|
|
||||||
memcpy(fw_priv->data + offset, buffer, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void firmware_rw(struct fw_priv *fw_priv, char *buffer,
|
|
||||||
loff_t offset, size_t count, bool read)
|
|
||||||
{
|
|
||||||
while (count) {
|
|
||||||
void *page_data;
|
|
||||||
int page_nr = offset >> PAGE_SHIFT;
|
|
||||||
int page_ofs = offset & (PAGE_SIZE-1);
|
|
||||||
int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count);
|
|
||||||
|
|
||||||
page_data = kmap(fw_priv->pages[page_nr]);
|
|
||||||
|
|
||||||
if (read)
|
|
||||||
memcpy(buffer, page_data + page_ofs, page_cnt);
|
|
||||||
else
|
|
||||||
memcpy(page_data + page_ofs, buffer, page_cnt);
|
|
||||||
|
|
||||||
kunmap(fw_priv->pages[page_nr]);
|
|
||||||
buffer += page_cnt;
|
|
||||||
offset += page_cnt;
|
|
||||||
count -= page_cnt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj,
|
|
||||||
struct bin_attribute *bin_attr,
|
|
||||||
char *buffer, loff_t offset, size_t count)
|
|
||||||
{
|
|
||||||
struct device *dev = kobj_to_dev(kobj);
|
|
||||||
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
|
||||||
struct fw_priv *fw_priv;
|
|
||||||
ssize_t ret_count;
|
|
||||||
|
|
||||||
mutex_lock(&fw_lock);
|
|
||||||
fw_priv = fw_sysfs->fw_priv;
|
|
||||||
if (!fw_priv || fw_sysfs_done(fw_priv)) {
|
|
||||||
ret_count = -ENODEV;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (offset > fw_priv->size) {
|
|
||||||
ret_count = 0;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (count > fw_priv->size - offset)
|
|
||||||
count = fw_priv->size - offset;
|
|
||||||
|
|
||||||
ret_count = count;
|
|
||||||
|
|
||||||
if (fw_priv->data)
|
|
||||||
firmware_rw_data(fw_priv, buffer, offset, count, true);
|
|
||||||
else
|
|
||||||
firmware_rw(fw_priv, buffer, offset, count, true);
|
|
||||||
|
|
||||||
out:
|
|
||||||
mutex_unlock(&fw_lock);
|
|
||||||
return ret_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int fw_realloc_pages(struct fw_sysfs *fw_sysfs, int min_size)
|
|
||||||
{
|
|
||||||
struct fw_priv *fw_priv= fw_sysfs->fw_priv;
|
|
||||||
int pages_needed = PAGE_ALIGN(min_size) >> PAGE_SHIFT;
|
|
||||||
|
|
||||||
/* If the array of pages is too small, grow it... */
|
|
||||||
if (fw_priv->page_array_size < pages_needed) {
|
|
||||||
int new_array_size = max(pages_needed,
|
|
||||||
fw_priv->page_array_size * 2);
|
|
||||||
struct page **new_pages;
|
|
||||||
|
|
||||||
new_pages = vmalloc(new_array_size * sizeof(void *));
|
|
||||||
if (!new_pages) {
|
|
||||||
fw_load_abort(fw_sysfs);
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
memcpy(new_pages, fw_priv->pages,
|
|
||||||
fw_priv->page_array_size * sizeof(void *));
|
|
||||||
memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) *
|
|
||||||
(new_array_size - fw_priv->page_array_size));
|
|
||||||
vfree(fw_priv->pages);
|
|
||||||
fw_priv->pages = new_pages;
|
|
||||||
fw_priv->page_array_size = new_array_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (fw_priv->nr_pages < pages_needed) {
|
|
||||||
fw_priv->pages[fw_priv->nr_pages] =
|
|
||||||
alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
|
|
||||||
|
|
||||||
if (!fw_priv->pages[fw_priv->nr_pages]) {
|
|
||||||
fw_load_abort(fw_sysfs);
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
fw_priv->nr_pages++;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* firmware_data_write - write method for firmware
|
|
||||||
* @filp: open sysfs file
|
|
||||||
* @kobj: kobject for the device
|
|
||||||
* @bin_attr: bin_attr structure
|
|
||||||
* @buffer: buffer being written
|
|
||||||
* @offset: buffer offset for write in total data store area
|
|
||||||
* @count: buffer size
|
|
||||||
*
|
|
||||||
* Data written to the 'data' attribute will be later handed to
|
|
||||||
* the driver as a firmware image.
|
|
||||||
**/
|
|
||||||
static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj,
|
|
||||||
struct bin_attribute *bin_attr,
|
|
||||||
char *buffer, loff_t offset, size_t count)
|
|
||||||
{
|
|
||||||
struct device *dev = kobj_to_dev(kobj);
|
|
||||||
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
|
||||||
struct fw_priv *fw_priv;
|
|
||||||
ssize_t retval;
|
|
||||||
|
|
||||||
if (!capable(CAP_SYS_RAWIO))
|
|
||||||
return -EPERM;
|
|
||||||
|
|
||||||
mutex_lock(&fw_lock);
|
|
||||||
fw_priv = fw_sysfs->fw_priv;
|
|
||||||
if (!fw_priv || fw_sysfs_done(fw_priv)) {
|
|
||||||
retval = -ENODEV;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fw_priv->data) {
|
|
||||||
if (offset + count > fw_priv->allocated_size) {
|
|
||||||
retval = -ENOMEM;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
firmware_rw_data(fw_priv, buffer, offset, count, false);
|
|
||||||
retval = count;
|
|
||||||
} else {
|
|
||||||
retval = fw_realloc_pages(fw_sysfs, offset + count);
|
|
||||||
if (retval)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
retval = count;
|
|
||||||
firmware_rw(fw_priv, buffer, offset, count, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fw_priv->size = max_t(size_t, offset + count, fw_priv->size);
|
|
||||||
out:
|
|
||||||
mutex_unlock(&fw_lock);
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct bin_attribute firmware_attr_data = {
|
|
||||||
.attr = { .name = "data", .mode = 0644 },
|
|
||||||
.size = 0,
|
|
||||||
.read = firmware_data_read,
|
|
||||||
.write = firmware_data_write,
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct attribute *fw_dev_attrs[] = {
|
|
||||||
&dev_attr_loading.attr,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct bin_attribute *fw_dev_bin_attrs[] = {
|
|
||||||
&firmware_attr_data,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct attribute_group fw_dev_attr_group = {
|
|
||||||
.attrs = fw_dev_attrs,
|
|
||||||
.bin_attrs = fw_dev_bin_attrs,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct attribute_group *fw_dev_attr_groups[] = {
|
|
||||||
&fw_dev_attr_group,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct fw_sysfs *
|
|
||||||
fw_create_instance(struct firmware *firmware, const char *fw_name,
|
|
||||||
struct device *device, unsigned int opt_flags)
|
|
||||||
{
|
|
||||||
struct fw_sysfs *fw_sysfs;
|
|
||||||
struct device *f_dev;
|
|
||||||
|
|
||||||
fw_sysfs = kzalloc(sizeof(*fw_sysfs), GFP_KERNEL);
|
|
||||||
if (!fw_sysfs) {
|
|
||||||
fw_sysfs = ERR_PTR(-ENOMEM);
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
fw_sysfs->nowait = !!(opt_flags & FW_OPT_NOWAIT);
|
|
||||||
fw_sysfs->fw = firmware;
|
|
||||||
f_dev = &fw_sysfs->dev;
|
|
||||||
|
|
||||||
device_initialize(f_dev);
|
|
||||||
dev_set_name(f_dev, "%s", fw_name);
|
|
||||||
f_dev->parent = device;
|
|
||||||
f_dev->class = &firmware_class;
|
|
||||||
f_dev->groups = fw_dev_attr_groups;
|
|
||||||
exit:
|
|
||||||
return fw_sysfs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* load a firmware via user helper */
|
|
||||||
static int _request_firmware_load(struct fw_sysfs *fw_sysfs,
|
|
||||||
unsigned int opt_flags, long timeout)
|
|
||||||
{
|
|
||||||
int retval = 0;
|
|
||||||
struct device *f_dev = &fw_sysfs->dev;
|
|
||||||
struct fw_priv *fw_priv = fw_sysfs->fw_priv;
|
|
||||||
|
|
||||||
/* fall back on userspace loading */
|
|
||||||
if (!fw_priv->data)
|
|
||||||
fw_priv->is_paged_buf = true;
|
|
||||||
|
|
||||||
dev_set_uevent_suppress(f_dev, true);
|
|
||||||
|
|
||||||
retval = device_add(f_dev);
|
|
||||||
if (retval) {
|
|
||||||
dev_err(f_dev, "%s: device_register failed\n", __func__);
|
|
||||||
goto err_put_dev;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex_lock(&fw_lock);
|
|
||||||
list_add(&fw_priv->pending_list, &pending_fw_head);
|
|
||||||
mutex_unlock(&fw_lock);
|
|
||||||
|
|
||||||
if (opt_flags & FW_OPT_UEVENT) {
|
|
||||||
fw_priv->need_uevent = true;
|
|
||||||
dev_set_uevent_suppress(f_dev, false);
|
|
||||||
dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_name);
|
|
||||||
kobject_uevent(&fw_sysfs->dev.kobj, KOBJ_ADD);
|
|
||||||
} else {
|
|
||||||
timeout = MAX_JIFFY_OFFSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
retval = fw_sysfs_wait_timeout(fw_priv, timeout);
|
|
||||||
if (retval < 0) {
|
|
||||||
mutex_lock(&fw_lock);
|
|
||||||
fw_load_abort(fw_sysfs);
|
|
||||||
mutex_unlock(&fw_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fw_state_is_aborted(fw_priv)) {
|
|
||||||
if (retval == -ERESTARTSYS)
|
|
||||||
retval = -EINTR;
|
|
||||||
else
|
|
||||||
retval = -EAGAIN;
|
|
||||||
} else if (fw_priv->is_paged_buf && !fw_priv->data)
|
|
||||||
retval = -ENOMEM;
|
|
||||||
|
|
||||||
device_del(f_dev);
|
|
||||||
err_put_dev:
|
|
||||||
put_device(f_dev);
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int fw_load_from_user_helper(struct firmware *firmware,
|
|
||||||
const char *name, struct device *device,
|
|
||||||
unsigned int opt_flags)
|
|
||||||
{
|
|
||||||
struct fw_sysfs *fw_sysfs;
|
|
||||||
long timeout;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
timeout = firmware_loading_timeout();
|
|
||||||
if (opt_flags & FW_OPT_NOWAIT) {
|
|
||||||
timeout = usermodehelper_read_lock_wait(timeout);
|
|
||||||
if (!timeout) {
|
|
||||||
dev_dbg(device, "firmware: %s loading timed out\n",
|
|
||||||
name);
|
|
||||||
return -EBUSY;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ret = usermodehelper_read_trylock();
|
|
||||||
if (WARN_ON(ret)) {
|
|
||||||
dev_err(device, "firmware: %s will not be loaded\n",
|
|
||||||
name);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fw_sysfs = fw_create_instance(firmware, name, device, opt_flags);
|
|
||||||
if (IS_ERR(fw_sysfs)) {
|
|
||||||
ret = PTR_ERR(fw_sysfs);
|
|
||||||
goto out_unlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
fw_sysfs->fw_priv = firmware->priv;
|
|
||||||
ret = _request_firmware_load(fw_sysfs, opt_flags, timeout);
|
|
||||||
|
|
||||||
if (!ret)
|
|
||||||
ret = assign_fw(firmware, device, opt_flags);
|
|
||||||
|
|
||||||
out_unlock:
|
|
||||||
usermodehelper_read_unlock();
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONFIG_FW_LOADER_USER_HELPER_FALLBACK
|
|
||||||
static bool fw_force_sysfs_fallback(unsigned int opt_flags)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static bool fw_force_sysfs_fallback(unsigned int opt_flags)
|
|
||||||
{
|
|
||||||
if (!(opt_flags & FW_OPT_USERHELPER))
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static bool fw_run_sysfs_fallback(unsigned int opt_flags)
|
|
||||||
{
|
|
||||||
if ((opt_flags & FW_OPT_NOFALLBACK))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return fw_force_sysfs_fallback(opt_flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int fw_sysfs_fallback(struct firmware *fw, const char *name,
|
|
||||||
struct device *device,
|
|
||||||
unsigned int opt_flags,
|
|
||||||
int ret)
|
|
||||||
{
|
|
||||||
if (!fw_run_sysfs_fallback(opt_flags))
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
dev_warn(device, "Falling back to user helper\n");
|
|
||||||
return fw_load_from_user_helper(fw, name, device, opt_flags);
|
|
||||||
}
|
|
||||||
#else /* CONFIG_FW_LOADER_USER_HELPER */
|
|
||||||
static int fw_sysfs_fallback(struct firmware *fw, const char *name,
|
|
||||||
struct device *device,
|
|
||||||
unsigned int opt_flags,
|
|
||||||
int ret)
|
|
||||||
{
|
|
||||||
/* Keep carrying over the same error */
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void kill_pending_fw_fallback_reqs(bool only_kill_custom) { }
|
|
||||||
|
|
||||||
static inline int register_sysfs_loader(void)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void unregister_sysfs_loader(void)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* CONFIG_FW_LOADER_USER_HELPER */
|
|
||||||
|
|
||||||
/* prepare firmware and firmware_buf structs;
|
/* prepare firmware and firmware_buf structs;
|
||||||
* return 0 if a firmware is already assigned, 1 if need to load one,
|
* return 0 if a firmware is already assigned, 1 if need to load one,
|
||||||
* or a negative error code
|
* or a negative error code
|
||||||
@ -1376,6 +656,30 @@ int request_firmware_direct(const struct firmware **firmware_p,
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(request_firmware_direct);
|
EXPORT_SYMBOL_GPL(request_firmware_direct);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* There are some devices with an optimization that enables the device to not
|
||||||
|
* require loading firmware on system reboot. This optimization may still
|
||||||
|
* require the firmware present on resume from suspend. This routine can be
|
||||||
|
* used to ensure the firmware is present on resume from suspend in these
|
||||||
|
* situations. This helper is not compatible with drivers which use
|
||||||
|
* request_firmware_into_buf() or request_firmware_nowait() with no uevent set.
|
||||||
|
**/
|
||||||
|
int firmware_request_cache(struct device *device, const char *name)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&fw_lock);
|
||||||
|
ret = fw_add_devm_name(device, name);
|
||||||
|
mutex_unlock(&fw_lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(firmware_request_cache);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* request_firmware_into_buf - load firmware into a previously allocated buffer
|
* request_firmware_into_buf - load firmware into a previously allocated buffer
|
||||||
* @firmware_p: pointer to firmware image
|
* @firmware_p: pointer to firmware image
|
||||||
@ -1397,6 +701,9 @@ request_firmware_into_buf(const struct firmware **firmware_p, const char *name,
|
|||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (fw_cache_is_setup(device, name))
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
__module_get(THIS_MODULE);
|
__module_get(THIS_MODULE);
|
||||||
ret = _request_firmware(firmware_p, name, device, buf, size,
|
ret = _request_firmware(firmware_p, name, device, buf, size,
|
||||||
FW_OPT_UEVENT | FW_OPT_NOCACHE);
|
FW_OPT_UEVENT | FW_OPT_NOCACHE);
|
||||||
@ -1494,6 +801,12 @@ request_firmware_nowait(
|
|||||||
fw_work->opt_flags = FW_OPT_NOWAIT |
|
fw_work->opt_flags = FW_OPT_NOWAIT |
|
||||||
(uevent ? FW_OPT_UEVENT : FW_OPT_USERHELPER);
|
(uevent ? FW_OPT_UEVENT : FW_OPT_USERHELPER);
|
||||||
|
|
||||||
|
if (!uevent && fw_cache_is_setup(device, name)) {
|
||||||
|
kfree_const(fw_work->name);
|
||||||
|
kfree(fw_work);
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
if (!try_module_get(module)) {
|
if (!try_module_get(module)) {
|
||||||
kfree_const(fw_work->name);
|
kfree_const(fw_work->name);
|
||||||
kfree(fw_work);
|
kfree(fw_work);
|
||||||
@ -1741,7 +1054,6 @@ static void __device_uncache_fw_images(void)
|
|||||||
static void device_cache_fw_images(void)
|
static void device_cache_fw_images(void)
|
||||||
{
|
{
|
||||||
struct firmware_cache *fwc = &fw_cache;
|
struct firmware_cache *fwc = &fw_cache;
|
||||||
int old_timeout;
|
|
||||||
DEFINE_WAIT(wait);
|
DEFINE_WAIT(wait);
|
||||||
|
|
||||||
pr_debug("%s\n", __func__);
|
pr_debug("%s\n", __func__);
|
||||||
@ -1749,16 +1061,7 @@ static void device_cache_fw_images(void)
|
|||||||
/* cancel uncache work */
|
/* cancel uncache work */
|
||||||
cancel_delayed_work_sync(&fwc->work);
|
cancel_delayed_work_sync(&fwc->work);
|
||||||
|
|
||||||
/*
|
fw_fallback_set_cache_timeout();
|
||||||
* use small loading timeout for caching devices' firmware
|
|
||||||
* because all these firmware images have been loaded
|
|
||||||
* successfully at lease once, also system is ready for
|
|
||||||
* completing firmware loading now. The maximum size of
|
|
||||||
* firmware in current distributions is about 2M bytes,
|
|
||||||
* so 10 secs should be enough.
|
|
||||||
*/
|
|
||||||
old_timeout = loading_timeout;
|
|
||||||
loading_timeout = 10;
|
|
||||||
|
|
||||||
mutex_lock(&fw_lock);
|
mutex_lock(&fw_lock);
|
||||||
fwc->state = FW_LOADER_START_CACHE;
|
fwc->state = FW_LOADER_START_CACHE;
|
||||||
@ -1768,7 +1071,7 @@ static void device_cache_fw_images(void)
|
|||||||
/* wait for completion of caching firmware for all devices */
|
/* wait for completion of caching firmware for all devices */
|
||||||
async_synchronize_full_domain(&fw_cache_domain);
|
async_synchronize_full_domain(&fw_cache_domain);
|
||||||
|
|
||||||
loading_timeout = old_timeout;
|
fw_fallback_set_default_timeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -315,7 +315,9 @@ static int register_node(struct node *node, int num)
|
|||||||
node->dev.groups = node_dev_groups;
|
node->dev.groups = node_dev_groups;
|
||||||
error = device_register(&node->dev);
|
error = device_register(&node->dev);
|
||||||
|
|
||||||
if (!error){
|
if (error)
|
||||||
|
put_device(&node->dev);
|
||||||
|
else {
|
||||||
hugetlb_register_node(node);
|
hugetlb_register_node(node);
|
||||||
|
|
||||||
compaction_register_node(node);
|
compaction_register_node(node);
|
||||||
|
@ -1153,8 +1153,10 @@ int __init platform_bus_init(void)
|
|||||||
early_platform_cleanup();
|
early_platform_cleanup();
|
||||||
|
|
||||||
error = device_register(&platform_bus);
|
error = device_register(&platform_bus);
|
||||||
if (error)
|
if (error) {
|
||||||
|
put_device(&platform_bus);
|
||||||
return error;
|
return error;
|
||||||
|
}
|
||||||
error = bus_register(&platform_bus_type);
|
error = bus_register(&platform_bus_type);
|
||||||
if (error)
|
if (error)
|
||||||
device_unregister(&platform_bus);
|
device_unregister(&platform_bus);
|
||||||
|
@ -150,6 +150,8 @@ struct soc_device *soc_device_register(struct soc_device_attribute *soc_dev_attr
|
|||||||
|
|
||||||
out3:
|
out3:
|
||||||
ida_simple_remove(&soc_ida, soc_dev->soc_dev_num);
|
ida_simple_remove(&soc_ida, soc_dev->soc_dev_num);
|
||||||
|
put_device(&soc_dev->dev);
|
||||||
|
soc_dev = NULL;
|
||||||
out2:
|
out2:
|
||||||
kfree(soc_dev);
|
kfree(soc_dev);
|
||||||
out1:
|
out1:
|
||||||
|
@ -420,7 +420,7 @@ static int mt7601u_load_firmware(struct mt7601u_dev *dev)
|
|||||||
MT_USB_DMA_CFG_TX_BULK_EN));
|
MT_USB_DMA_CFG_TX_BULK_EN));
|
||||||
|
|
||||||
if (firmware_running(dev))
|
if (firmware_running(dev))
|
||||||
return 0;
|
return firmware_request_cache(dev->dev, MT7601U_FIRMWARE);
|
||||||
|
|
||||||
ret = request_firmware(&fw, MT7601U_FIRMWARE, dev->dev);
|
ret = request_firmware(&fw, MT7601U_FIRMWARE, dev->dev);
|
||||||
if (ret)
|
if (ret)
|
||||||
|
@ -256,6 +256,7 @@ enum probe_type {
|
|||||||
* automatically.
|
* automatically.
|
||||||
* @pm: Power management operations of the device which matched
|
* @pm: Power management operations of the device which matched
|
||||||
* this driver.
|
* this driver.
|
||||||
|
* @coredump: Called through sysfs to initiate a device coredump.
|
||||||
* @p: Driver core's private data, no one other than the driver
|
* @p: Driver core's private data, no one other than the driver
|
||||||
* core can touch this.
|
* core can touch this.
|
||||||
*
|
*
|
||||||
|
@ -85,4 +85,7 @@ static inline int request_firmware_into_buf(const struct firmware **firmware_p,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
int firmware_request_cache(struct device *device, const char *name);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -253,6 +253,10 @@ extern struct ctl_table random_table[];
|
|||||||
extern struct ctl_table epoll_table[];
|
extern struct ctl_table epoll_table[];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
||||||
|
extern struct ctl_table firmware_config_table[];
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_ARCH_PICK_MMAP_LAYOUT
|
#ifdef HAVE_ARCH_PICK_MMAP_LAYOUT
|
||||||
int sysctl_legacy_va_layout;
|
int sysctl_legacy_va_layout;
|
||||||
#endif
|
#endif
|
||||||
@ -748,6 +752,13 @@ static struct ctl_table kern_table[] = {
|
|||||||
.mode = 0555,
|
.mode = 0555,
|
||||||
.child = usermodehelper_table,
|
.child = usermodehelper_table,
|
||||||
},
|
},
|
||||||
|
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
||||||
|
{
|
||||||
|
.procname = "firmware_config",
|
||||||
|
.mode = 0555,
|
||||||
|
.child = firmware_config_table,
|
||||||
|
},
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
.procname = "overflowuid",
|
.procname = "overflowuid",
|
||||||
.data = &overflowuid,
|
.data = &overflowuid,
|
||||||
|
@ -204,8 +204,9 @@ static int kobject_add_internal(struct kobject *kobj)
|
|||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
|
|
||||||
if (!kobj->name || !kobj->name[0]) {
|
if (!kobj->name || !kobj->name[0]) {
|
||||||
WARN(1, "kobject: (%p): attempted to be registered with empty "
|
WARN(1,
|
||||||
"name!\n", kobj);
|
"kobject: (%p): attempted to be registered with empty name!\n",
|
||||||
|
kobj);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,9 +233,8 @@ static int kobject_add_internal(struct kobject *kobj)
|
|||||||
|
|
||||||
/* be noisy on error issues */
|
/* be noisy on error issues */
|
||||||
if (error == -EEXIST)
|
if (error == -EEXIST)
|
||||||
WARN(1, "%s failed for %s with "
|
WARN(1,
|
||||||
"-EEXIST, don't try to register things with "
|
"%s failed for %s with -EEXIST, don't try to register things with the same name in the same directory.\n",
|
||||||
"the same name in the same directory.\n",
|
|
||||||
__func__, kobject_name(kobj));
|
__func__, kobject_name(kobj));
|
||||||
else
|
else
|
||||||
WARN(1, "%s failed for %s (error: %d parent: %s)\n",
|
WARN(1, "%s failed for %s (error: %d parent: %s)\n",
|
||||||
@ -334,8 +334,8 @@ void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
|
|||||||
}
|
}
|
||||||
if (kobj->state_initialized) {
|
if (kobj->state_initialized) {
|
||||||
/* do not error out as sometimes we can recover */
|
/* do not error out as sometimes we can recover */
|
||||||
printk(KERN_ERR "kobject (%p): tried to init an initialized "
|
pr_err("kobject (%p): tried to init an initialized object, something is seriously wrong.\n",
|
||||||
"object, something is seriously wrong.\n", kobj);
|
kobj);
|
||||||
dump_stack();
|
dump_stack();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,7 +344,7 @@ void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
|
pr_err("kobject (%p): %s\n", kobj, err_str);
|
||||||
dump_stack();
|
dump_stack();
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(kobject_init);
|
EXPORT_SYMBOL(kobject_init);
|
||||||
@ -357,7 +357,7 @@ static __printf(3, 0) int kobject_add_varg(struct kobject *kobj,
|
|||||||
|
|
||||||
retval = kobject_set_name_vargs(kobj, fmt, vargs);
|
retval = kobject_set_name_vargs(kobj, fmt, vargs);
|
||||||
if (retval) {
|
if (retval) {
|
||||||
printk(KERN_ERR "kobject: can not set name properly!\n");
|
pr_err("kobject: can not set name properly!\n");
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
kobj->parent = parent;
|
kobj->parent = parent;
|
||||||
@ -399,8 +399,7 @@ int kobject_add(struct kobject *kobj, struct kobject *parent,
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
if (!kobj->state_initialized) {
|
if (!kobj->state_initialized) {
|
||||||
printk(KERN_ERR "kobject '%s' (%p): tried to add an "
|
pr_err("kobject '%s' (%p): tried to add an uninitialized object, something is seriously wrong.\n",
|
||||||
"uninitialized object, something is seriously wrong.\n",
|
|
||||||
kobject_name(kobj), kobj);
|
kobject_name(kobj), kobj);
|
||||||
dump_stack();
|
dump_stack();
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
@ -590,9 +589,9 @@ struct kobject *kobject_get(struct kobject *kobj)
|
|||||||
{
|
{
|
||||||
if (kobj) {
|
if (kobj) {
|
||||||
if (!kobj->state_initialized)
|
if (!kobj->state_initialized)
|
||||||
WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
|
WARN(1, KERN_WARNING
|
||||||
"initialized, yet kobject_get() is being "
|
"kobject: '%s' (%p): is not initialized, yet kobject_get() is being called.\n",
|
||||||
"called.\n", kobject_name(kobj), kobj);
|
kobject_name(kobj), kobj);
|
||||||
kref_get(&kobj->kref);
|
kref_get(&kobj->kref);
|
||||||
}
|
}
|
||||||
return kobj;
|
return kobj;
|
||||||
@ -622,8 +621,7 @@ static void kobject_cleanup(struct kobject *kobj)
|
|||||||
kobject_name(kobj), kobj, __func__, kobj->parent);
|
kobject_name(kobj), kobj, __func__, kobj->parent);
|
||||||
|
|
||||||
if (t && !t->release)
|
if (t && !t->release)
|
||||||
pr_debug("kobject: '%s' (%p): does not have a release() "
|
pr_debug("kobject: '%s' (%p): does not have a release() function, it is broken and must be fixed.\n",
|
||||||
"function, it is broken and must be fixed.\n",
|
|
||||||
kobject_name(kobj), kobj);
|
kobject_name(kobj), kobj);
|
||||||
|
|
||||||
/* send "remove" if the caller did not do it but sent "add" */
|
/* send "remove" if the caller did not do it but sent "add" */
|
||||||
@ -686,9 +684,9 @@ void kobject_put(struct kobject *kobj)
|
|||||||
{
|
{
|
||||||
if (kobj) {
|
if (kobj) {
|
||||||
if (!kobj->state_initialized)
|
if (!kobj->state_initialized)
|
||||||
WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
|
WARN(1, KERN_WARNING
|
||||||
"initialized, yet kobject_put() is being "
|
"kobject: '%s' (%p): is not initialized, yet kobject_put() is being called.\n",
|
||||||
"called.\n", kobject_name(kobj), kobj);
|
kobject_name(kobj), kobj);
|
||||||
kref_put(&kobj->kref, kobject_release);
|
kref_put(&kobj->kref, kobject_release);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -752,8 +750,7 @@ struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
|
|||||||
|
|
||||||
retval = kobject_add(kobj, parent, "%s", name);
|
retval = kobject_add(kobj, parent, "%s", name);
|
||||||
if (retval) {
|
if (retval) {
|
||||||
printk(KERN_WARNING "%s: kobject_add error: %d\n",
|
pr_warn("%s: kobject_add error: %d\n", __func__, retval);
|
||||||
__func__, retval);
|
|
||||||
kobject_put(kobj);
|
kobject_put(kobj);
|
||||||
kobj = NULL;
|
kobj = NULL;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
|
# No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
|
||||||
all:
|
all:
|
||||||
|
|
||||||
TEST_PROGS := fw_filesystem.sh fw_fallback.sh
|
TEST_PROGS := fw_run_tests.sh
|
||||||
|
|
||||||
include ../lib.mk
|
include ../lib.mk
|
||||||
|
|
||||||
|
@ -1 +1,5 @@
|
|||||||
CONFIG_TEST_FIRMWARE=y
|
CONFIG_TEST_FIRMWARE=y
|
||||||
|
CONFIG_FW_LOADER=y
|
||||||
|
CONFIG_FW_LOADER_USER_HELPER=y
|
||||||
|
CONFIG_IKCONFIG=y
|
||||||
|
CONFIG_IKCONFIG_PROC=y
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
# SPDX-License-Identifier: GPL-2.0
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
# This validates that the kernel will fall back to using the fallback mechanism
|
# This validates that the kernel will fall back to using the fallback mechanism
|
||||||
# to load firmware it can't find on disk itself. We must request a firmware
|
# to load firmware it can't find on disk itself. We must request a firmware
|
||||||
@ -6,31 +6,17 @@
|
|||||||
# won't find so that we can do the load ourself manually.
|
# won't find so that we can do the load ourself manually.
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
modprobe test_firmware
|
TEST_REQS_FW_SYSFS_FALLBACK="yes"
|
||||||
|
TEST_REQS_FW_SET_CUSTOM_PATH="no"
|
||||||
|
TEST_DIR=$(dirname $0)
|
||||||
|
source $TEST_DIR/fw_lib.sh
|
||||||
|
|
||||||
DIR=/sys/devices/virtual/misc/test_firmware
|
check_mods
|
||||||
|
check_setup
|
||||||
|
verify_reqs
|
||||||
|
setup_tmp_file
|
||||||
|
|
||||||
# CONFIG_FW_LOADER_USER_HELPER has a sysfs class under /sys/class/firmware/
|
trap "test_finish" EXIT
|
||||||
# These days no one enables CONFIG_FW_LOADER_USER_HELPER so check for that
|
|
||||||
# as an indicator for CONFIG_FW_LOADER_USER_HELPER.
|
|
||||||
HAS_FW_LOADER_USER_HELPER=$(if [ -d /sys/class/firmware/ ]; then echo yes; else echo no; fi)
|
|
||||||
|
|
||||||
if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
|
|
||||||
OLD_TIMEOUT=$(cat /sys/class/firmware/timeout)
|
|
||||||
else
|
|
||||||
echo "usermode helper disabled so ignoring test"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
FWPATH=$(mktemp -d)
|
|
||||||
FW="$FWPATH/test-firmware.bin"
|
|
||||||
|
|
||||||
test_finish()
|
|
||||||
{
|
|
||||||
echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout
|
|
||||||
rm -f "$FW"
|
|
||||||
rmdir "$FWPATH"
|
|
||||||
}
|
|
||||||
|
|
||||||
load_fw()
|
load_fw()
|
||||||
{
|
{
|
||||||
@ -169,12 +155,6 @@ load_fw_fallback_with_child()
|
|||||||
return $RET
|
return $RET
|
||||||
}
|
}
|
||||||
|
|
||||||
trap "test_finish" EXIT
|
|
||||||
|
|
||||||
# This is an unlikely real-world firmware content. :)
|
|
||||||
echo "ABCD0123" >"$FW"
|
|
||||||
NAME=$(basename "$FW")
|
|
||||||
|
|
||||||
test_syfs_timeout()
|
test_syfs_timeout()
|
||||||
{
|
{
|
||||||
DEVPATH="$DIR"/"nope-$NAME"/loading
|
DEVPATH="$DIR"/"nope-$NAME"/loading
|
||||||
@ -258,8 +238,10 @@ run_sysfs_main_tests()
|
|||||||
|
|
||||||
run_sysfs_custom_load_tests()
|
run_sysfs_custom_load_tests()
|
||||||
{
|
{
|
||||||
if load_fw_custom "$NAME" "$FW" ; then
|
RANDOM_FILE_PATH=$(setup_random_file)
|
||||||
if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
|
RANDOM_FILE="$(basename $RANDOM_FILE_PATH)"
|
||||||
|
if load_fw_custom "$RANDOM_FILE" "$RANDOM_FILE_PATH" ; then
|
||||||
|
if ! diff -q "$RANDOM_FILE_PATH" /dev/test_firmware >/dev/null ; then
|
||||||
echo "$0: firmware was not loaded" >&2
|
echo "$0: firmware was not loaded" >&2
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
@ -267,8 +249,10 @@ run_sysfs_custom_load_tests()
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if load_fw_custom "$NAME" "$FW" ; then
|
RANDOM_FILE_PATH=$(setup_random_file)
|
||||||
if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
|
RANDOM_FILE="$(basename $RANDOM_FILE_PATH)"
|
||||||
|
if load_fw_custom "$RANDOM_FILE" "$RANDOM_FILE_PATH" ; then
|
||||||
|
if ! diff -q "$RANDOM_FILE_PATH" /dev/test_firmware >/dev/null ; then
|
||||||
echo "$0: firmware was not loaded" >&2
|
echo "$0: firmware was not loaded" >&2
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
@ -276,8 +260,12 @@ run_sysfs_custom_load_tests()
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if load_fw_custom_cancel "nope-$NAME" "$FW" ; then
|
RANDOM_FILE_REAL="$RANDOM_FILE_PATH"
|
||||||
if diff -q "$FW" /dev/test_firmware >/dev/null ; then
|
FAKE_RANDOM_FILE_PATH=$(setup_random_file_fake)
|
||||||
|
FAKE_RANDOM_FILE="$(basename $FAKE_RANDOM_FILE_PATH)"
|
||||||
|
|
||||||
|
if load_fw_custom_cancel "$FAKE_RANDOM_FILE" "$RANDOM_FILE_REAL" ; then
|
||||||
|
if diff -q "$RANDOM_FILE_PATH" /dev/test_firmware >/dev/null ; then
|
||||||
echo "$0: firmware was expected to be cancelled" >&2
|
echo "$0: firmware was expected to be cancelled" >&2
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
@ -286,7 +274,10 @@ run_sysfs_custom_load_tests()
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
run_sysfs_main_tests
|
if [ "$HAS_FW_LOADER_USER_HELPER_FALLBACK" = "yes" ]; then
|
||||||
|
run_sysfs_main_tests
|
||||||
|
fi
|
||||||
|
|
||||||
run_sysfs_custom_load_tests
|
run_sysfs_custom_load_tests
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
# SPDX-License-Identifier: GPL-2.0
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
# This validates that the kernel will load firmware out of its list of
|
# This validates that the kernel will load firmware out of its list of
|
||||||
# firmware locations on disk. Since the user helper does similar work,
|
# firmware locations on disk. Since the user helper does similar work,
|
||||||
@ -6,52 +6,15 @@
|
|||||||
# know so we can be sure we're not accidentally testing the user helper.
|
# know so we can be sure we're not accidentally testing the user helper.
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DIR=/sys/devices/virtual/misc/test_firmware
|
TEST_REQS_FW_SYSFS_FALLBACK="no"
|
||||||
|
TEST_REQS_FW_SET_CUSTOM_PATH="yes"
|
||||||
TEST_DIR=$(dirname $0)
|
TEST_DIR=$(dirname $0)
|
||||||
|
source $TEST_DIR/fw_lib.sh
|
||||||
|
|
||||||
test_modprobe()
|
check_mods
|
||||||
{
|
check_setup
|
||||||
if [ ! -d $DIR ]; then
|
verify_reqs
|
||||||
echo "$0: $DIR not present"
|
setup_tmp_file
|
||||||
echo "You must have the following enabled in your kernel:"
|
|
||||||
cat $TEST_DIR/config
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
trap "test_modprobe" EXIT
|
|
||||||
|
|
||||||
if [ ! -d $DIR ]; then
|
|
||||||
modprobe test_firmware
|
|
||||||
fi
|
|
||||||
|
|
||||||
# CONFIG_FW_LOADER_USER_HELPER has a sysfs class under /sys/class/firmware/
|
|
||||||
# These days most distros enable CONFIG_FW_LOADER_USER_HELPER but disable
|
|
||||||
# CONFIG_FW_LOADER_USER_HELPER_FALLBACK. We use /sys/class/firmware/ as an
|
|
||||||
# indicator for CONFIG_FW_LOADER_USER_HELPER.
|
|
||||||
HAS_FW_LOADER_USER_HELPER=$(if [ -d /sys/class/firmware/ ]; then echo yes; else echo no; fi)
|
|
||||||
|
|
||||||
if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
|
|
||||||
OLD_TIMEOUT=$(cat /sys/class/firmware/timeout)
|
|
||||||
fi
|
|
||||||
|
|
||||||
OLD_FWPATH=$(cat /sys/module/firmware_class/parameters/path)
|
|
||||||
|
|
||||||
FWPATH=$(mktemp -d)
|
|
||||||
FW="$FWPATH/test-firmware.bin"
|
|
||||||
|
|
||||||
test_finish()
|
|
||||||
{
|
|
||||||
if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
|
|
||||||
echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout
|
|
||||||
fi
|
|
||||||
if [ "$OLD_FWPATH" = "" ]; then
|
|
||||||
OLD_FWPATH=" "
|
|
||||||
fi
|
|
||||||
echo -n "$OLD_FWPATH" >/sys/module/firmware_class/parameters/path
|
|
||||||
rm -f "$FW"
|
|
||||||
rmdir "$FWPATH"
|
|
||||||
}
|
|
||||||
|
|
||||||
trap "test_finish" EXIT
|
trap "test_finish" EXIT
|
||||||
|
|
||||||
@ -60,14 +23,6 @@ if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
|
|||||||
echo 1 >/sys/class/firmware/timeout
|
echo 1 >/sys/class/firmware/timeout
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set the kernel search path.
|
|
||||||
echo -n "$FWPATH" >/sys/module/firmware_class/parameters/path
|
|
||||||
|
|
||||||
# This is an unlikely real-world firmware content. :)
|
|
||||||
echo "ABCD0123" >"$FW"
|
|
||||||
|
|
||||||
NAME=$(basename "$FW")
|
|
||||||
|
|
||||||
if printf '\000' >"$DIR"/trigger_request 2> /dev/null; then
|
if printf '\000' >"$DIR"/trigger_request 2> /dev/null; then
|
||||||
echo "$0: empty filename should not succeed" >&2
|
echo "$0: empty filename should not succeed" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@ -275,10 +230,13 @@ test_wait_and_cancel_custom_load()
|
|||||||
test_request_firmware_nowait_custom_nofile()
|
test_request_firmware_nowait_custom_nofile()
|
||||||
{
|
{
|
||||||
echo -n "Batched request_firmware_nowait(uevent=false) nofile try #$1: "
|
echo -n "Batched request_firmware_nowait(uevent=false) nofile try #$1: "
|
||||||
|
config_reset
|
||||||
config_unset_uevent
|
config_unset_uevent
|
||||||
config_set_name nope-test-firmware.bin
|
RANDOM_FILE_PATH=$(setup_random_file_fake)
|
||||||
|
RANDOM_FILE="$(basename $RANDOM_FILE_PATH)"
|
||||||
|
config_set_name $RANDOM_FILE
|
||||||
config_trigger_async &
|
config_trigger_async &
|
||||||
test_wait_and_cancel_custom_load nope-test-firmware.bin
|
test_wait_and_cancel_custom_load $RANDOM_FILE
|
||||||
wait
|
wait
|
||||||
release_all_firmware
|
release_all_firmware
|
||||||
echo "OK"
|
echo "OK"
|
||||||
@ -316,7 +274,11 @@ test_request_firmware_nowait_uevent()
|
|||||||
test_request_firmware_nowait_custom()
|
test_request_firmware_nowait_custom()
|
||||||
{
|
{
|
||||||
echo -n "Batched request_firmware_nowait(uevent=false) try #$1: "
|
echo -n "Batched request_firmware_nowait(uevent=false) try #$1: "
|
||||||
|
config_reset
|
||||||
config_unset_uevent
|
config_unset_uevent
|
||||||
|
RANDOM_FILE_PATH=$(setup_random_file)
|
||||||
|
RANDOM_FILE="$(basename $RANDOM_FILE_PATH)"
|
||||||
|
config_set_name $RANDOM_FILE
|
||||||
config_trigger_async
|
config_trigger_async
|
||||||
release_all_firmware
|
release_all_firmware
|
||||||
echo "OK"
|
echo "OK"
|
||||||
|
194
tools/testing/selftests/firmware/fw_lib.sh
Executable file
194
tools/testing/selftests/firmware/fw_lib.sh
Executable file
@ -0,0 +1,194 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
# Library of helpers for test scripts.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DIR=/sys/devices/virtual/misc/test_firmware
|
||||||
|
|
||||||
|
PROC_CONFIG="/proc/config.gz"
|
||||||
|
TEST_DIR=$(dirname $0)
|
||||||
|
|
||||||
|
print_reqs_exit()
|
||||||
|
{
|
||||||
|
echo "You must have the following enabled in your kernel:" >&2
|
||||||
|
cat $TEST_DIR/config >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test_modprobe()
|
||||||
|
{
|
||||||
|
if [ ! -d $DIR ]; then
|
||||||
|
print_reqs_exit
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_mods()
|
||||||
|
{
|
||||||
|
trap "test_modprobe" EXIT
|
||||||
|
if [ ! -d $DIR ]; then
|
||||||
|
modprobe test_firmware
|
||||||
|
fi
|
||||||
|
if [ ! -f $PROC_CONFIG ]; then
|
||||||
|
if modprobe configs 2>/dev/null; then
|
||||||
|
echo "Loaded configs module"
|
||||||
|
if [ ! -f $PROC_CONFIG ]; then
|
||||||
|
echo "You must have the following enabled in your kernel:" >&2
|
||||||
|
cat $TEST_DIR/config >&2
|
||||||
|
echo "Resorting to old heuristics" >&2
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Failed to load configs module, using old heuristics" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_setup()
|
||||||
|
{
|
||||||
|
HAS_FW_LOADER_USER_HELPER="$(kconfig_has CONFIG_FW_LOADER_USER_HELPER=y)"
|
||||||
|
HAS_FW_LOADER_USER_HELPER_FALLBACK="$(kconfig_has CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y)"
|
||||||
|
PROC_FW_IGNORE_SYSFS_FALLBACK="0"
|
||||||
|
PROC_FW_FORCE_SYSFS_FALLBACK="0"
|
||||||
|
|
||||||
|
if [ -z $PROC_SYS_DIR ]; then
|
||||||
|
PROC_SYS_DIR="/proc/sys/kernel"
|
||||||
|
fi
|
||||||
|
|
||||||
|
FW_PROC="${PROC_SYS_DIR}/firmware_config"
|
||||||
|
FW_FORCE_SYSFS_FALLBACK="$FW_PROC/force_sysfs_fallback"
|
||||||
|
FW_IGNORE_SYSFS_FALLBACK="$FW_PROC/ignore_sysfs_fallback"
|
||||||
|
|
||||||
|
if [ -f $FW_FORCE_SYSFS_FALLBACK ]; then
|
||||||
|
PROC_FW_FORCE_SYSFS_FALLBACK="$(cat $FW_FORCE_SYSFS_FALLBACK)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f $FW_IGNORE_SYSFS_FALLBACK ]; then
|
||||||
|
PROC_FW_IGNORE_SYSFS_FALLBACK="$(cat $FW_IGNORE_SYSFS_FALLBACK)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$PROC_FW_FORCE_SYSFS_FALLBACK" = "1" ]; then
|
||||||
|
HAS_FW_LOADER_USER_HELPER="yes"
|
||||||
|
HAS_FW_LOADER_USER_HELPER_FALLBACK="yes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$PROC_FW_IGNORE_SYSFS_FALLBACK" = "1" ]; then
|
||||||
|
HAS_FW_LOADER_USER_HELPER_FALLBACK="no"
|
||||||
|
HAS_FW_LOADER_USER_HELPER="no"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
|
||||||
|
OLD_TIMEOUT="$(cat /sys/class/firmware/timeout)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
OLD_FWPATH="$(cat /sys/module/firmware_class/parameters/path)"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_reqs()
|
||||||
|
{
|
||||||
|
if [ "$TEST_REQS_FW_SYSFS_FALLBACK" = "yes" ]; then
|
||||||
|
if [ ! "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
|
||||||
|
echo "usermode helper disabled so ignoring test"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_tmp_file()
|
||||||
|
{
|
||||||
|
FWPATH=$(mktemp -d)
|
||||||
|
FW="$FWPATH/test-firmware.bin"
|
||||||
|
echo "ABCD0123" >"$FW"
|
||||||
|
NAME=$(basename "$FW")
|
||||||
|
if [ "$TEST_REQS_FW_SET_CUSTOM_PATH" = "yes" ]; then
|
||||||
|
echo -n "$FWPATH" >/sys/module/firmware_class/parameters/path
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__setup_random_file()
|
||||||
|
{
|
||||||
|
RANDOM_FILE_PATH="$(mktemp -p $FWPATH)"
|
||||||
|
# mktemp says dry-run -n is unsafe, so...
|
||||||
|
if [[ "$1" = "fake" ]]; then
|
||||||
|
rm -rf $RANDOM_FILE_PATH
|
||||||
|
sync
|
||||||
|
else
|
||||||
|
echo "ABCD0123" >"$RANDOM_FILE_PATH"
|
||||||
|
fi
|
||||||
|
echo $RANDOM_FILE_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_random_file()
|
||||||
|
{
|
||||||
|
echo $(__setup_random_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_random_file_fake()
|
||||||
|
{
|
||||||
|
echo $(__setup_random_file fake)
|
||||||
|
}
|
||||||
|
|
||||||
|
proc_set_force_sysfs_fallback()
|
||||||
|
{
|
||||||
|
if [ -f $FW_FORCE_SYSFS_FALLBACK ]; then
|
||||||
|
echo -n $1 > $FW_FORCE_SYSFS_FALLBACK
|
||||||
|
check_setup
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
proc_set_ignore_sysfs_fallback()
|
||||||
|
{
|
||||||
|
if [ -f $FW_IGNORE_SYSFS_FALLBACK ]; then
|
||||||
|
echo -n $1 > $FW_IGNORE_SYSFS_FALLBACK
|
||||||
|
check_setup
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
proc_restore_defaults()
|
||||||
|
{
|
||||||
|
proc_set_force_sysfs_fallback 0
|
||||||
|
proc_set_ignore_sysfs_fallback 0
|
||||||
|
}
|
||||||
|
|
||||||
|
test_finish()
|
||||||
|
{
|
||||||
|
if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
|
||||||
|
echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout
|
||||||
|
fi
|
||||||
|
if [ "$OLD_FWPATH" = "" ]; then
|
||||||
|
OLD_FWPATH=" "
|
||||||
|
fi
|
||||||
|
if [ "$TEST_REQS_FW_SET_CUSTOM_PATH" = "yes" ]; then
|
||||||
|
echo -n "$OLD_FWPATH" >/sys/module/firmware_class/parameters/path
|
||||||
|
fi
|
||||||
|
if [ -f $FW ]; then
|
||||||
|
rm -f "$FW"
|
||||||
|
fi
|
||||||
|
if [ -d $FWPATH ]; then
|
||||||
|
rm -rf "$FWPATH"
|
||||||
|
fi
|
||||||
|
proc_restore_defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
kconfig_has()
|
||||||
|
{
|
||||||
|
if [ -f $PROC_CONFIG ]; then
|
||||||
|
if zgrep -q $1 $PROC_CONFIG 2>/dev/null; then
|
||||||
|
echo "yes"
|
||||||
|
else
|
||||||
|
echo "no"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# We currently don't have easy heuristics to infer this
|
||||||
|
# so best we can do is just try to use the kernel assuming
|
||||||
|
# you had enabled it. This matches the old behaviour.
|
||||||
|
if [ "$1" = "CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y" ]; then
|
||||||
|
echo "yes"
|
||||||
|
elif [ "$1" = "CONFIG_FW_LOADER_USER_HELPER=y" ]; then
|
||||||
|
if [ -d /sys/class/firmware/ ]; then
|
||||||
|
echo yes
|
||||||
|
else
|
||||||
|
echo no
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
70
tools/testing/selftests/firmware/fw_run_tests.sh
Executable file
70
tools/testing/selftests/firmware/fw_run_tests.sh
Executable file
@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
# This runs all known tests across all known possible configurations we could
|
||||||
|
# emulate in one run.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TEST_DIR=$(dirname $0)
|
||||||
|
source $TEST_DIR/fw_lib.sh
|
||||||
|
|
||||||
|
export HAS_FW_LOADER_USER_HELPER=""
|
||||||
|
export HAS_FW_LOADER_USER_HELPER_FALLBACK=""
|
||||||
|
|
||||||
|
run_tests()
|
||||||
|
{
|
||||||
|
proc_set_force_sysfs_fallback $1
|
||||||
|
proc_set_ignore_sysfs_fallback $2
|
||||||
|
$TEST_DIR/fw_filesystem.sh
|
||||||
|
|
||||||
|
proc_set_force_sysfs_fallback $1
|
||||||
|
proc_set_ignore_sysfs_fallback $2
|
||||||
|
$TEST_DIR/fw_fallback.sh
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test_config_0001()
|
||||||
|
{
|
||||||
|
echo "-----------------------------------------------------"
|
||||||
|
echo "Running kernel configuration test 1 -- rare"
|
||||||
|
echo "Emulates:"
|
||||||
|
echo "CONFIG_FW_LOADER=y"
|
||||||
|
echo "CONFIG_FW_LOADER_USER_HELPER=n"
|
||||||
|
echo "CONFIG_FW_LOADER_USER_HELPER_FALLBACK=n"
|
||||||
|
run_tests 0 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test_config_0002()
|
||||||
|
{
|
||||||
|
echo "-----------------------------------------------------"
|
||||||
|
echo "Running kernel configuration test 2 -- distro"
|
||||||
|
echo "Emulates:"
|
||||||
|
echo "CONFIG_FW_LOADER=y"
|
||||||
|
echo "CONFIG_FW_LOADER_USER_HELPER=y"
|
||||||
|
echo "CONFIG_FW_LOADER_USER_HELPER_FALLBACK=n"
|
||||||
|
proc_set_ignore_sysfs_fallback 0
|
||||||
|
run_tests 0 0
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test_config_0003()
|
||||||
|
{
|
||||||
|
echo "-----------------------------------------------------"
|
||||||
|
echo "Running kernel configuration test 3 -- android"
|
||||||
|
echo "Emulates:"
|
||||||
|
echo "CONFIG_FW_LOADER=y"
|
||||||
|
echo "CONFIG_FW_LOADER_USER_HELPER=y"
|
||||||
|
echo "CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y"
|
||||||
|
run_tests 1 0
|
||||||
|
}
|
||||||
|
|
||||||
|
check_mods
|
||||||
|
check_setup
|
||||||
|
|
||||||
|
if [ -f $FW_FORCE_SYSFS_FALLBACK ]; then
|
||||||
|
run_test_config_0001
|
||||||
|
run_test_config_0002
|
||||||
|
run_test_config_0003
|
||||||
|
else
|
||||||
|
echo "Running basic kernel configuration, working with your config"
|
||||||
|
run_test
|
||||||
|
fi
|
Loading…
Reference in New Issue
Block a user