1
0
Fork 0

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
  ...
hifive-unleashed-5.1
Linus Torvalds 2018-04-04 19:41:45 -07:00
commit 38047d5c26
27 changed files with 1370 additions and 894 deletions

View File

@ -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
relying on kobject uevents. The addition of the device into the device
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().
The kernel's kobject uevent mechanism is implemented in lib/kobject_uevent.c,

View File

@ -44,6 +44,20 @@ request_firmware_nowait
.. kernel-doc:: drivers/base/firmware_class.c
: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
========================================

View File

@ -5524,7 +5524,7 @@ M: Luis R. Rodriguez <mcgrof@kernel.org>
L: linux-kernel@vger.kernel.org
S: Maintained
F: Documentation/firmware_class/
F: drivers/base/firmware*.c
F: drivers/base/firmware_loader/
F: include/linux/firmware.h
FLASH ADAPTER DRIVER (IBM Flash Adapter 900GB Full Height PCI Flash Card)

View File

@ -13,7 +13,7 @@ obj-y += power/
obj-$(CONFIG_HAS_DMA) += dma-mapping.o
obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.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_MEMORY_HOTPLUG_SPARSE) += memory.o
ifeq ($(CONFIG_SYSFS),y)

View File

@ -169,11 +169,11 @@ bool __init topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu)
}
#ifdef CONFIG_CPU_FREQ
static cpumask_var_t cpus_to_visit __initdata;
static void __init parsing_done_workfn(struct work_struct *work);
static __initdata DECLARE_WORK(parsing_done_work, parsing_done_workfn);
static cpumask_var_t cpus_to_visit;
static void parsing_done_workfn(struct work_struct *work);
static DECLARE_WORK(parsing_done_work, parsing_done_workfn);
static int __init
static int
init_cpu_capacity_callback(struct notifier_block *nb,
unsigned long val,
void *data)
@ -209,7 +209,7 @@ init_cpu_capacity_callback(struct notifier_block *nb,
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,
};
@ -242,7 +242,7 @@ static int __init register_cpufreq_notifier(void)
}
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_POLICY_NOTIFIER);

View File

@ -382,8 +382,10 @@ int register_cpu(struct cpu *cpu, int num)
if (cpu->hotpluggable)
cpu->dev.groups = hotplugable_cpu_attr_groups;
error = device_register(&cpu->dev);
if (error)
if (error) {
put_device(&cpu->dev);
return error;
}
per_cpu(cpu_sys_devices, num) = &cpu->dev;
register_cpu_under_node(num, cpu_to_node(num));

View File

@ -292,8 +292,7 @@ static ssize_t coredump_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
device_lock(dev);
if (dev->driver->coredump)
dev->driver->coredump(dev);
dev->driver->coredump(dev);
device_unlock(dev);
return count;

View 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

View 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);
}

View 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 */

View 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

View 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 */

View File

@ -1,6 +1,6 @@
// 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
*
@ -36,37 +36,14 @@
#include <generated/utsrelease.h>
#include "base.h"
#include "../base.h"
#include "firmware.h"
#include "fallback.h"
MODULE_AUTHOR("Manuel Estrada Sainz");
MODULE_DESCRIPTION("Multi purpose firmware loading support");
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 {
/* firmware_buf instance will be added into the below list */
spinlock_t lock;
@ -89,25 +66,6 @@ struct firmware_cache {
#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 list_head list;
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
* 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;
@ -191,13 +149,6 @@ static inline bool fw_is_builtin_firmware(const struct firmware *fw)
}
#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)
{
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;
}
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)
{
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 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;
}
/* add firmware name into devres list */
static int fw_add_devm_name(struct device *dev, const char *name)
static bool fw_cache_is_setup(struct device *dev, const char *name)
{
struct fw_name_devm *fwn;
fwn = fw_find_devm_name(dev, name);
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),
GFP_KERNEL);
@ -542,16 +431,22 @@ static int fw_add_devm_name(struct device *dev, const char *name)
return 0;
}
#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)
{
return 0;
}
#endif
static int assign_fw(struct firmware *fw, struct device *device,
unsigned int opt_flags)
int assign_fw(struct firmware *fw, struct device *device,
unsigned int opt_flags)
{
struct fw_priv *fw_priv = fw->priv;
int ret;
mutex_lock(&fw_lock);
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 */
if (device && (opt_flags & FW_OPT_UEVENT) &&
!(opt_flags & FW_OPT_NOCACHE))
fw_add_devm_name(device, fw_priv->fw_name);
!(opt_flags & FW_OPT_NOCACHE)) {
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
@ -587,626 +487,6 @@ static int assign_fw(struct firmware *fw, struct device *device,
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;
* return 0 if a firmware is already assigned, 1 if need to load one,
* or a negative error code
@ -1376,6 +656,30 @@ int request_firmware_direct(const struct firmware **firmware_p,
}
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
* @firmware_p: pointer to firmware image
@ -1397,6 +701,9 @@ request_firmware_into_buf(const struct firmware **firmware_p, const char *name,
{
int ret;
if (fw_cache_is_setup(device, name))
return -EOPNOTSUPP;
__module_get(THIS_MODULE);
ret = _request_firmware(firmware_p, name, device, buf, size,
FW_OPT_UEVENT | FW_OPT_NOCACHE);
@ -1494,6 +801,12 @@ request_firmware_nowait(
fw_work->opt_flags = FW_OPT_NOWAIT |
(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)) {
kfree_const(fw_work->name);
kfree(fw_work);
@ -1741,7 +1054,6 @@ static void __device_uncache_fw_images(void)
static void device_cache_fw_images(void)
{
struct firmware_cache *fwc = &fw_cache;
int old_timeout;
DEFINE_WAIT(wait);
pr_debug("%s\n", __func__);
@ -1749,16 +1061,7 @@ static void device_cache_fw_images(void)
/* cancel uncache work */
cancel_delayed_work_sync(&fwc->work);
/*
* 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;
fw_fallback_set_cache_timeout();
mutex_lock(&fw_lock);
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 */
async_synchronize_full_domain(&fw_cache_domain);
loading_timeout = old_timeout;
fw_fallback_set_default_timeout();
}
/**

View File

@ -315,7 +315,9 @@ static int register_node(struct node *node, int num)
node->dev.groups = node_dev_groups;
error = device_register(&node->dev);
if (!error){
if (error)
put_device(&node->dev);
else {
hugetlb_register_node(node);
compaction_register_node(node);

View File

@ -1153,8 +1153,10 @@ int __init platform_bus_init(void)
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
if (error) {
put_device(&platform_bus);
return error;
}
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);

View File

@ -150,6 +150,8 @@ struct soc_device *soc_device_register(struct soc_device_attribute *soc_dev_attr
out3:
ida_simple_remove(&soc_ida, soc_dev->soc_dev_num);
put_device(&soc_dev->dev);
soc_dev = NULL;
out2:
kfree(soc_dev);
out1:

View File

@ -420,7 +420,7 @@ static int mt7601u_load_firmware(struct mt7601u_dev *dev)
MT_USB_DMA_CFG_TX_BULK_EN));
if (firmware_running(dev))
return 0;
return firmware_request_cache(dev->dev, MT7601U_FIRMWARE);
ret = request_firmware(&fw, MT7601U_FIRMWARE, dev->dev);
if (ret)

View File

@ -256,6 +256,7 @@ enum probe_type {
* automatically.
* @pm: Power management operations of the device which matched
* this driver.
* @coredump: Called through sysfs to initiate a device coredump.
* @p: Driver core's private data, no one other than the driver
* core can touch this.
*

View File

@ -85,4 +85,7 @@ static inline int request_firmware_into_buf(const struct firmware **firmware_p,
}
#endif
int firmware_request_cache(struct device *device, const char *name);
#endif

View File

@ -253,6 +253,10 @@ extern struct ctl_table random_table[];
extern struct ctl_table epoll_table[];
#endif
#ifdef CONFIG_FW_LOADER_USER_HELPER
extern struct ctl_table firmware_config_table[];
#endif
#ifdef HAVE_ARCH_PICK_MMAP_LAYOUT
int sysctl_legacy_va_layout;
#endif
@ -748,6 +752,13 @@ static struct ctl_table kern_table[] = {
.mode = 0555,
.child = usermodehelper_table,
},
#ifdef CONFIG_FW_LOADER_USER_HELPER
{
.procname = "firmware_config",
.mode = 0555,
.child = firmware_config_table,
},
#endif
{
.procname = "overflowuid",
.data = &overflowuid,

View File

@ -204,8 +204,9 @@ static int kobject_add_internal(struct kobject *kobj)
return -ENOENT;
if (!kobj->name || !kobj->name[0]) {
WARN(1, "kobject: (%p): attempted to be registered with empty "
"name!\n", kobj);
WARN(1,
"kobject: (%p): attempted to be registered with empty name!\n",
kobj);
return -EINVAL;
}
@ -232,9 +233,8 @@ static int kobject_add_internal(struct kobject *kobj)
/* be noisy on error issues */
if (error == -EEXIST)
WARN(1, "%s failed for %s with "
"-EEXIST, don't try to register things with "
"the same name in the same directory.\n",
WARN(1,
"%s failed for %s with -EEXIST, don't try to register things with the same name in the same directory.\n",
__func__, kobject_name(kobj));
else
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) {
/* do not error out as sometimes we can recover */
printk(KERN_ERR "kobject (%p): tried to init an initialized "
"object, something is seriously wrong.\n", kobj);
pr_err("kobject (%p): tried to init an initialized object, something is seriously wrong.\n",
kobj);
dump_stack();
}
@ -344,7 +344,7 @@ void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
return;
error:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
pr_err("kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
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);
if (retval) {
printk(KERN_ERR "kobject: can not set name properly!\n");
pr_err("kobject: can not set name properly!\n");
return retval;
}
kobj->parent = parent;
@ -399,8 +399,7 @@ int kobject_add(struct kobject *kobj, struct kobject *parent,
return -EINVAL;
if (!kobj->state_initialized) {
printk(KERN_ERR "kobject '%s' (%p): tried to add an "
"uninitialized object, something is seriously wrong.\n",
pr_err("kobject '%s' (%p): tried to add an uninitialized object, something is seriously wrong.\n",
kobject_name(kobj), kobj);
dump_stack();
return -EINVAL;
@ -590,9 +589,9 @@ struct kobject *kobject_get(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
"initialized, yet kobject_get() is being "
"called.\n", kobject_name(kobj), kobj);
WARN(1, KERN_WARNING
"kobject: '%s' (%p): is not initialized, yet kobject_get() is being called.\n",
kobject_name(kobj), kobj);
kref_get(&kobj->kref);
}
return kobj;
@ -622,8 +621,7 @@ static void kobject_cleanup(struct kobject *kobj)
kobject_name(kobj), kobj, __func__, kobj->parent);
if (t && !t->release)
pr_debug("kobject: '%s' (%p): does not have a release() "
"function, it is broken and must be fixed.\n",
pr_debug("kobject: '%s' (%p): does not have a release() function, it is broken and must be fixed.\n",
kobject_name(kobj), kobj);
/* 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->state_initialized)
WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
"initialized, yet kobject_put() is being "
"called.\n", kobject_name(kobj), kobj);
WARN(1, KERN_WARNING
"kobject: '%s' (%p): is not initialized, yet kobject_put() is being called.\n",
kobject_name(kobj), kobj);
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);
if (retval) {
printk(KERN_WARNING "%s: kobject_add error: %d\n",
__func__, retval);
pr_warn("%s: kobject_add error: %d\n", __func__, retval);
kobject_put(kobj);
kobj = NULL;
}

View File

@ -3,7 +3,7 @@
# No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
all:
TEST_PROGS := fw_filesystem.sh fw_fallback.sh
TEST_PROGS := fw_run_tests.sh
include ../lib.mk

View File

@ -1 +1,5 @@
CONFIG_TEST_FIRMWARE=y
CONFIG_FW_LOADER=y
CONFIG_FW_LOADER_USER_HELPER=y
CONFIG_IKCONFIG=y
CONFIG_IKCONFIG_PROC=y

View File

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# 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
@ -6,31 +6,17 @@
# won't find so that we can do the load ourself manually.
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/
# 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"
}
trap "test_finish" EXIT
load_fw()
{
@ -169,12 +155,6 @@ load_fw_fallback_with_child()
return $RET
}
trap "test_finish" EXIT
# This is an unlikely real-world firmware content. :)
echo "ABCD0123" >"$FW"
NAME=$(basename "$FW")
test_syfs_timeout()
{
DEVPATH="$DIR"/"nope-$NAME"/loading
@ -258,8 +238,10 @@ run_sysfs_main_tests()
run_sysfs_custom_load_tests()
{
if load_fw_custom "$NAME" "$FW" ; then
if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
RANDOM_FILE_PATH=$(setup_random_file)
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
exit 1
else
@ -267,8 +249,10 @@ run_sysfs_custom_load_tests()
fi
fi
if load_fw_custom "$NAME" "$FW" ; then
if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
RANDOM_FILE_PATH=$(setup_random_file)
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
exit 1
else
@ -276,8 +260,12 @@ run_sysfs_custom_load_tests()
fi
fi
if load_fw_custom_cancel "nope-$NAME" "$FW" ; then
if diff -q "$FW" /dev/test_firmware >/dev/null ; then
RANDOM_FILE_REAL="$RANDOM_FILE_PATH"
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
exit 1
else
@ -286,7 +274,10 @@ run_sysfs_custom_load_tests()
fi
}
run_sysfs_main_tests
if [ "$HAS_FW_LOADER_USER_HELPER_FALLBACK" = "yes" ]; then
run_sysfs_main_tests
fi
run_sysfs_custom_load_tests
exit 0

View File

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# This validates that the kernel will load firmware out of its list of
# 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.
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)
source $TEST_DIR/fw_lib.sh
test_modprobe()
{
if [ ! -d $DIR ]; then
echo "$0: $DIR not present"
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"
}
check_mods
check_setup
verify_reqs
setup_tmp_file
trap "test_finish" EXIT
@ -60,14 +23,6 @@ if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
echo 1 >/sys/class/firmware/timeout
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
echo "$0: empty filename should not succeed" >&2
exit 1
@ -275,10 +230,13 @@ test_wait_and_cancel_custom_load()
test_request_firmware_nowait_custom_nofile()
{
echo -n "Batched request_firmware_nowait(uevent=false) nofile try #$1: "
config_reset
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 &
test_wait_and_cancel_custom_load nope-test-firmware.bin
test_wait_and_cancel_custom_load $RANDOM_FILE
wait
release_all_firmware
echo "OK"
@ -316,7 +274,11 @@ test_request_firmware_nowait_uevent()
test_request_firmware_nowait_custom()
{
echo -n "Batched request_firmware_nowait(uevent=false) try #$1: "
config_reset
config_unset_uevent
RANDOM_FILE_PATH=$(setup_random_file)
RANDOM_FILE="$(basename $RANDOM_FILE_PATH)"
config_set_name $RANDOM_FILE
config_trigger_async
release_all_firmware
echo "OK"

View 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
}

View 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