Merge 500a434fc5
("Merge tag 'driver-core-5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core") into android-mainline
Steps on the way to 5.19-rc1 Signed-off-by: Greg Kroah-Hartman <gregkh@google.com> Change-Id: Ie6de144a337f7e16e38738a1487e9885480ddfa2
This commit is contained in:
commit
e2b7083040
77
Documentation/ABI/testing/sysfs-class-firmware
Normal file
77
Documentation/ABI/testing/sysfs-class-firmware
Normal file
@ -0,0 +1,77 @@
|
||||
What: /sys/class/firmware/.../data
|
||||
Date: July 2022
|
||||
KernelVersion: 5.19
|
||||
Contact: Russ Weight <russell.h.weight@intel.com>
|
||||
Description: The data sysfs file is used for firmware-fallback and for
|
||||
firmware uploads. Cat a firmware image to this sysfs file
|
||||
after you echo 1 to the loading sysfs file. When the firmware
|
||||
image write is complete, echo 0 to the loading sysfs file. This
|
||||
sequence will signal the completion of the firmware write and
|
||||
signal the lower-level driver that the firmware data is
|
||||
available.
|
||||
|
||||
What: /sys/class/firmware/.../cancel
|
||||
Date: July 2022
|
||||
KernelVersion: 5.19
|
||||
Contact: Russ Weight <russell.h.weight@intel.com>
|
||||
Description: Write-only. For firmware uploads, write a "1" to this file to
|
||||
request that the transfer of firmware data to the lower-level
|
||||
device be canceled. This request will be rejected (EBUSY) if
|
||||
the update cannot be canceled (e.g. a FLASH write is in
|
||||
progress) or (ENODEV) if there is no firmware update in progress.
|
||||
|
||||
What: /sys/class/firmware/.../error
|
||||
Date: July 2022
|
||||
KernelVersion: 5.19
|
||||
Contact: Russ Weight <russell.h.weight@intel.com>
|
||||
Description: Read-only. Returns a string describing a failed firmware
|
||||
upload. This string will be in the form of <STATUS>:<ERROR>,
|
||||
where <STATUS> will be one of the status strings described
|
||||
for the status sysfs file and <ERROR> will be one of the
|
||||
following: "hw-error", "timeout", "user-abort", "device-busy",
|
||||
"invalid-file-size", "read-write-error", "flash-wearout". The
|
||||
error sysfs file is only meaningful when the current firmware
|
||||
upload status is "idle". If this file is read while a firmware
|
||||
transfer is in progress, then the read will fail with EBUSY.
|
||||
|
||||
What: /sys/class/firmware/.../loading
|
||||
Date: July 2022
|
||||
KernelVersion: 5.19
|
||||
Contact: Russ Weight <russell.h.weight@intel.com>
|
||||
Description: The loading sysfs file is used for both firmware-fallback and
|
||||
for firmware uploads. Echo 1 onto the loading file to indicate
|
||||
you are writing a firmware file to the data sysfs node. Echo
|
||||
-1 onto this file to abort the data write or echo 0 onto this
|
||||
file to indicate that the write is complete. For firmware
|
||||
uploads, the zero value also triggers the transfer of the
|
||||
firmware data to the lower-level device driver.
|
||||
|
||||
What: /sys/class/firmware/.../remaining_size
|
||||
Date: July 2022
|
||||
KernelVersion: 5.19
|
||||
Contact: Russ Weight <russell.h.weight@intel.com>
|
||||
Description: Read-only. For firmware upload, this file contains the size
|
||||
of the firmware data that remains to be transferred to the
|
||||
lower-level device driver. The size value is initialized to
|
||||
the full size of the firmware image that was previously
|
||||
written to the data sysfs file. This value is periodically
|
||||
updated during the "transferring" phase of the firmware
|
||||
upload.
|
||||
Format: "%u".
|
||||
|
||||
What: /sys/class/firmware/.../status
|
||||
Date: July 2022
|
||||
KernelVersion: 5.19
|
||||
Contact: Russ Weight <russell.h.weight@intel.com>
|
||||
Description: Read-only. Returns a string describing the current status of
|
||||
a firmware upload. The string will be one of the following:
|
||||
idle, "receiving", "preparing", "transferring", "programming".
|
||||
|
||||
What: /sys/class/firmware/.../timeout
|
||||
Date: July 2022
|
||||
KernelVersion: 5.19
|
||||
Contact: Russ Weight <russell.h.weight@intel.com>
|
||||
Description: This file supports the timeout mechanism for firmware
|
||||
fallback. This file has no affect on firmware uploads. For
|
||||
more information on timeouts please see the documentation
|
||||
for firmware fallback.
|
42
Documentation/ABI/testing/sysfs-devices-physical_location
Normal file
42
Documentation/ABI/testing/sysfs-devices-physical_location
Normal file
@ -0,0 +1,42 @@
|
||||
What: /sys/devices/.../physical_location
|
||||
Date: March 2022
|
||||
Contact: Won Chung <wonchung@google.com>
|
||||
Description:
|
||||
This directory contains information on physical location of
|
||||
the device connection point with respect to the system's
|
||||
housing.
|
||||
|
||||
What: /sys/devices/.../physical_location/panel
|
||||
Date: March 2022
|
||||
Contact: Won Chung <wonchung@google.com>
|
||||
Description:
|
||||
Describes which panel surface of the system’s housing the
|
||||
device connection point resides on.
|
||||
|
||||
What: /sys/devices/.../physical_location/vertical_position
|
||||
Date: March 2022
|
||||
Contact: Won Chung <wonchung@google.com>
|
||||
Description:
|
||||
Describes vertical position of the device connection point on
|
||||
the panel surface.
|
||||
|
||||
What: /sys/devices/.../physical_location/horizontal_position
|
||||
Date: March 2022
|
||||
Contact: Won Chung <wonchung@google.com>
|
||||
Description:
|
||||
Describes horizontal position of the device connection point on
|
||||
the panel surface.
|
||||
|
||||
What: /sys/devices/.../physical_location/dock
|
||||
Date: March 2022
|
||||
Contact: Won Chung <wonchung@google.com>
|
||||
Description:
|
||||
"Yes" if the device connection point resides in a docking
|
||||
station or a port replicator. "No" otherwise.
|
||||
|
||||
What: /sys/devices/.../physical_location/lid
|
||||
Date: March 2022
|
||||
Contact: Won Chung <wonchung@google.com>
|
||||
Description:
|
||||
"Yes" if the device connection point resides on the lid of
|
||||
laptop system. "No" otherwise.
|
@ -979,8 +979,10 @@
|
||||
[KNL] Debugging option to set a timeout in seconds for
|
||||
deferred probe to give up waiting on dependencies to
|
||||
probe. Only specific dependencies (subsystems or
|
||||
drivers) that have opted in will be ignored. A timeout of 0
|
||||
will timeout at the end of initcalls. This option will also
|
||||
drivers) that have opted in will be ignored. A timeout
|
||||
of 0 will timeout at the end of initcalls. If the time
|
||||
out hasn't expired, it'll be restarted by each
|
||||
successful driver registration. This option will also
|
||||
dump out devices still on the deferred probe list after
|
||||
retrying.
|
||||
|
||||
@ -1101,7 +1103,10 @@
|
||||
driver later using sysfs.
|
||||
|
||||
driver_async_probe= [KNL]
|
||||
List of driver names to be probed asynchronously.
|
||||
List of driver names to be probed asynchronously. *
|
||||
matches with all driver names. If * is specified, the
|
||||
rest of the listed driver names are those that will NOT
|
||||
match the *.
|
||||
Format: <driver_name1>,<driver_name2>...
|
||||
|
||||
drm.edid_firmware=[<connector>:]<file>[,[<connector>:]<file>]
|
||||
|
126
Documentation/driver-api/firmware/fw_upload.rst
Normal file
126
Documentation/driver-api/firmware/fw_upload.rst
Normal file
@ -0,0 +1,126 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
===================
|
||||
Firmware Upload API
|
||||
===================
|
||||
|
||||
A device driver that registers with the firmware loader will expose
|
||||
persistent sysfs nodes to enable users to initiate firmware updates for
|
||||
that device. It is the responsibility of the device driver and/or the
|
||||
device itself to perform any validation on the data received. Firmware
|
||||
upload uses the same *loading* and *data* sysfs files described in the
|
||||
documentation for firmware fallback. It also adds additional sysfs files
|
||||
to provide status on the transfer of the firmware image to the device.
|
||||
|
||||
Register for firmware upload
|
||||
============================
|
||||
|
||||
A device driver registers for firmware upload by calling
|
||||
firmware_upload_register(). Among the parameter list is a name to
|
||||
identify the device under /sys/class/firmware. A user may initiate a
|
||||
firmware upload by echoing a 1 to the *loading* sysfs file for the target
|
||||
device. Next, the user writes the firmware image to the *data* sysfs
|
||||
file. After writing the firmware data, the user echos 0 to the *loading*
|
||||
sysfs file to signal completion. Echoing 0 to *loading* also triggers the
|
||||
transfer of the firmware to the lower-lever device driver in the context
|
||||
of a kernel worker thread.
|
||||
|
||||
To use the firmware upload API, write a driver that implements a set of
|
||||
ops. The probe function calls firmware_upload_register() and the remove
|
||||
function calls firmware_upload_unregister() such as::
|
||||
|
||||
static const struct fw_upload_ops m10bmc_ops = {
|
||||
.prepare = m10bmc_sec_prepare,
|
||||
.write = m10bmc_sec_write,
|
||||
.poll_complete = m10bmc_sec_poll_complete,
|
||||
.cancel = m10bmc_sec_cancel,
|
||||
.cleanup = m10bmc_sec_cleanup,
|
||||
};
|
||||
|
||||
static int m10bmc_sec_probe(struct platform_device *pdev)
|
||||
{
|
||||
const char *fw_name, *truncate;
|
||||
struct m10bmc_sec *sec;
|
||||
struct fw_upload *fwl;
|
||||
unsigned int len;
|
||||
|
||||
sec = devm_kzalloc(&pdev->dev, sizeof(*sec), GFP_KERNEL);
|
||||
if (!sec)
|
||||
return -ENOMEM;
|
||||
|
||||
sec->dev = &pdev->dev;
|
||||
sec->m10bmc = dev_get_drvdata(pdev->dev.parent);
|
||||
dev_set_drvdata(&pdev->dev, sec);
|
||||
|
||||
fw_name = dev_name(sec->dev);
|
||||
truncate = strstr(fw_name, ".auto");
|
||||
len = (truncate) ? truncate - fw_name : strlen(fw_name);
|
||||
sec->fw_name = kmemdup_nul(fw_name, len, GFP_KERNEL);
|
||||
|
||||
fwl = firmware_upload_register(sec->dev, sec->fw_name, &m10bmc_ops, sec);
|
||||
if (IS_ERR(fwl)) {
|
||||
dev_err(sec->dev, "Firmware Upload driver failed to start\n");
|
||||
kfree(sec->fw_name);
|
||||
return PTR_ERR(fwl);
|
||||
}
|
||||
|
||||
sec->fwl = fwl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int m10bmc_sec_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct m10bmc_sec *sec = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
firmware_upload_unregister(sec->fwl);
|
||||
kfree(sec->fw_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
firmware_upload_register
|
||||
------------------------
|
||||
.. kernel-doc:: drivers/base/firmware_loader/sysfs_upload.c
|
||||
:identifiers: firmware_upload_register
|
||||
|
||||
firmware_upload_unregister
|
||||
--------------------------
|
||||
.. kernel-doc:: drivers/base/firmware_loader/sysfs_upload.c
|
||||
:identifiers: firmware_upload_unregister
|
||||
|
||||
Firmware Upload Ops
|
||||
-------------------
|
||||
.. kernel-doc:: include/linux/firmware.h
|
||||
:identifiers: fw_upload_ops
|
||||
|
||||
Firmware Upload Progress Codes
|
||||
------------------------------
|
||||
The following progress codes are used internally by the firmware loader.
|
||||
Corresponding strings are reported through the status sysfs node that
|
||||
is described below and are documented in the ABI documentation.
|
||||
|
||||
.. kernel-doc:: drivers/base/firmware_loader/sysfs_upload.h
|
||||
:identifiers: fw_upload_prog
|
||||
|
||||
Firmware Upload Error Codes
|
||||
---------------------------
|
||||
The following error codes may be returned by the driver ops in case of
|
||||
failure:
|
||||
|
||||
.. kernel-doc:: include/linux/firmware.h
|
||||
:identifiers: fw_upload_err
|
||||
|
||||
Sysfs Attributes
|
||||
================
|
||||
|
||||
In addition to the *loading* and *data* sysfs files, there are additional
|
||||
sysfs files to monitor the status of the data transfer to the target
|
||||
device and to determine the final pass/fail status of the transfer.
|
||||
Depending on the device and the size of the firmware image, a firmware
|
||||
update could take milliseconds or minutes.
|
||||
|
||||
The additional sysfs files are:
|
||||
|
||||
* status - provides an indication of the progress of a firmware update
|
||||
* error - provides error information for a failed firmware update
|
||||
* remaining_size - tracks the data transfer portion of an update
|
||||
* cancel - echo 1 to this file to cancel the update
|
@ -8,6 +8,7 @@ Linux Firmware API
|
||||
core
|
||||
efi/index
|
||||
request_firmware
|
||||
fw_upload
|
||||
other_interfaces
|
||||
|
||||
.. only:: subproject and html
|
||||
|
@ -7727,6 +7727,7 @@ F: include/linux/arm_ffa.h
|
||||
|
||||
FIRMWARE LOADER (request_firmware)
|
||||
M: Luis Chamberlain <mcgrof@kernel.org>
|
||||
M: Russ Weight <russell.h.weight@intel.com>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/firmware_class/
|
||||
|
@ -98,31 +98,11 @@ static ssize_t driver_override_store(struct device *_dev,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct amba_device *dev = to_amba_device(_dev);
|
||||
char *driver_override, *old, *cp;
|
||||
int ret;
|
||||
|
||||
/* We need to keep extra room for a newline */
|
||||
if (count >= (PAGE_SIZE - 1))
|
||||
return -EINVAL;
|
||||
|
||||
driver_override = kstrndup(buf, count, GFP_KERNEL);
|
||||
if (!driver_override)
|
||||
return -ENOMEM;
|
||||
|
||||
cp = strchr(driver_override, '\n');
|
||||
if (cp)
|
||||
*cp = '\0';
|
||||
|
||||
device_lock(_dev);
|
||||
old = dev->driver_override;
|
||||
if (strlen(driver_override)) {
|
||||
dev->driver_override = driver_override;
|
||||
} else {
|
||||
kfree(driver_override);
|
||||
dev->driver_override = NULL;
|
||||
}
|
||||
device_unlock(_dev);
|
||||
|
||||
kfree(old);
|
||||
ret = driver_set_override(_dev, &dev->driver_override, buf, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ obj-$(CONFIG_DEV_COREDUMP) += devcoredump.o
|
||||
obj-$(CONFIG_GENERIC_MSI_IRQ_DOMAIN) += platform-msi.o
|
||||
obj-$(CONFIG_GENERIC_ARCH_TOPOLOGY) += arch_topology.o
|
||||
obj-$(CONFIG_GENERIC_ARCH_NUMA) += arch_numa.o
|
||||
obj-$(CONFIG_ACPI) += physical_location.o
|
||||
|
||||
obj-y += test/
|
||||
|
||||
|
@ -19,6 +19,9 @@
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/thermal_pressure.h>
|
||||
|
||||
static DEFINE_PER_CPU(struct scale_freq_data __rcu *, sft_data);
|
||||
static struct cpumask scale_freq_counters_mask;
|
||||
static bool scale_freq_invariant;
|
||||
@ -195,6 +198,8 @@ void topology_update_thermal_pressure(const struct cpumask *cpus,
|
||||
|
||||
th_pressure = max_capacity - capacity;
|
||||
|
||||
trace_thermal_pressure_update(cpu, th_pressure);
|
||||
|
||||
for_each_cpu(cpu, cpus)
|
||||
WRITE_ONCE(per_cpu(thermal_pressure, cpu), th_pressure);
|
||||
}
|
||||
|
@ -159,6 +159,7 @@ extern char *make_class_name(const char *name, struct kobject *kobj);
|
||||
extern int devres_release_all(struct device *dev);
|
||||
extern void device_block_probing(void);
|
||||
extern void device_unblock_probing(void);
|
||||
extern void deferred_probe_extend_timeout(void);
|
||||
|
||||
/* /sys/devices directory */
|
||||
extern struct kset *devices_kset;
|
||||
|
@ -617,7 +617,7 @@ int bus_add_driver(struct device_driver *drv)
|
||||
if (drv->bus->p->drivers_autoprobe) {
|
||||
error = driver_attach(drv);
|
||||
if (error)
|
||||
goto out_unregister;
|
||||
goto out_del_list;
|
||||
}
|
||||
module_add_driver(drv->owner, drv);
|
||||
|
||||
@ -644,6 +644,8 @@ int bus_add_driver(struct device_driver *drv)
|
||||
|
||||
return 0;
|
||||
|
||||
out_del_list:
|
||||
klist_del(&priv->knode_bus);
|
||||
out_unregister:
|
||||
kobject_put(&priv->kobj);
|
||||
/* drv->p is freed in driver_release() */
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include <linux/dma-map-ops.h> /* for dma_default_coherent */
|
||||
|
||||
#include "base.h"
|
||||
#include "physical_location.h"
|
||||
#include "power/power.h"
|
||||
|
||||
#ifdef CONFIG_SYSFS_DEPRECATED
|
||||
@ -2649,8 +2650,17 @@ static int device_add_attrs(struct device *dev)
|
||||
goto err_remove_dev_waiting_for_supplier;
|
||||
}
|
||||
|
||||
if (dev_add_physical_location(dev)) {
|
||||
error = device_add_group(dev,
|
||||
&dev_attr_physical_location_group);
|
||||
if (error)
|
||||
goto err_remove_dev_removable;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_dev_removable:
|
||||
device_remove_file(dev, &dev_attr_removable);
|
||||
err_remove_dev_waiting_for_supplier:
|
||||
device_remove_file(dev, &dev_attr_waiting_for_supplier);
|
||||
err_remove_dev_online:
|
||||
@ -2672,6 +2682,11 @@ static void device_remove_attrs(struct device *dev)
|
||||
struct class *class = dev->class;
|
||||
const struct device_type *type = dev->type;
|
||||
|
||||
if (dev->physical_location) {
|
||||
device_remove_group(dev, &dev_attr_physical_location_group);
|
||||
kfree(dev->physical_location);
|
||||
}
|
||||
|
||||
device_remove_file(dev, &dev_attr_removable);
|
||||
device_remove_file(dev, &dev_attr_waiting_for_supplier);
|
||||
device_remove_file(dev, &dev_attr_online);
|
||||
|
@ -60,6 +60,7 @@ static bool initcalls_done;
|
||||
/* Save the async probe drivers' name from kernel cmdline */
|
||||
#define ASYNC_DRV_NAMES_MAX_LEN 256
|
||||
static char async_probe_drv_names[ASYNC_DRV_NAMES_MAX_LEN];
|
||||
static bool async_probe_default;
|
||||
|
||||
/*
|
||||
* In some cases, like suspend to RAM or hibernation, It might be reasonable
|
||||
@ -255,7 +256,12 @@ static int deferred_devs_show(struct seq_file *s, void *data)
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(deferred_devs);
|
||||
|
||||
#ifdef CONFIG_MODULES
|
||||
int driver_deferred_probe_timeout = 10;
|
||||
#else
|
||||
int driver_deferred_probe_timeout;
|
||||
#endif
|
||||
|
||||
EXPORT_SYMBOL_GPL(driver_deferred_probe_timeout);
|
||||
static DECLARE_WAIT_QUEUE_HEAD(probe_timeout_waitqueue);
|
||||
|
||||
@ -274,10 +280,10 @@ __setup("deferred_probe_timeout=", deferred_probe_timeout_setup);
|
||||
* @dev: device to check
|
||||
*
|
||||
* Return:
|
||||
* -ENODEV if initcalls have completed and modules are disabled.
|
||||
* -ETIMEDOUT if the deferred probe timeout was set and has expired
|
||||
* and modules are enabled.
|
||||
* -EPROBE_DEFER in other cases.
|
||||
* * -ENODEV if initcalls have completed and modules are disabled.
|
||||
* * -ETIMEDOUT if the deferred probe timeout was set and has expired
|
||||
* and modules are enabled.
|
||||
* * -EPROBE_DEFER in other cases.
|
||||
*
|
||||
* Drivers or subsystems can opt-in to calling this function instead of directly
|
||||
* returning -EPROBE_DEFER.
|
||||
@ -316,6 +322,20 @@ static void deferred_probe_timeout_work_func(struct work_struct *work)
|
||||
}
|
||||
static DECLARE_DELAYED_WORK(deferred_probe_timeout_work, deferred_probe_timeout_work_func);
|
||||
|
||||
void deferred_probe_extend_timeout(void)
|
||||
{
|
||||
/*
|
||||
* If the work hasn't been queued yet or if the work expired, don't
|
||||
* start a new one.
|
||||
*/
|
||||
if (cancel_delayed_work(&deferred_probe_timeout_work)) {
|
||||
schedule_delayed_work(&deferred_probe_timeout_work,
|
||||
driver_deferred_probe_timeout * HZ);
|
||||
pr_debug("Extended deferred probe timeout by %d secs\n",
|
||||
driver_deferred_probe_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* deferred_probe_initcall() - Enable probing of deferred devices
|
||||
*
|
||||
@ -799,7 +819,11 @@ static int driver_probe_device(struct device_driver *drv, struct device *dev)
|
||||
|
||||
static inline bool cmdline_requested_async_probing(const char *drv_name)
|
||||
{
|
||||
return parse_option_str(async_probe_drv_names, drv_name);
|
||||
bool async_drv;
|
||||
|
||||
async_drv = parse_option_str(async_probe_drv_names, drv_name);
|
||||
|
||||
return (async_probe_default != async_drv);
|
||||
}
|
||||
|
||||
/* The option format is "driver_async_probe=drv_name1,drv_name2,..." */
|
||||
@ -809,6 +833,8 @@ static int __init save_async_options(char *buf)
|
||||
pr_warn("Too long list of driver names for 'driver_async_probe'!\n");
|
||||
|
||||
strlcpy(async_probe_drv_names, buf, ASYNC_DRV_NAMES_MAX_LEN);
|
||||
async_probe_default = parse_option_str(async_probe_drv_names, "*");
|
||||
|
||||
return 1;
|
||||
}
|
||||
__setup("driver_async_probe=", save_async_options);
|
||||
@ -943,6 +969,7 @@ static void __device_attach_async_helper(void *_dev, async_cookie_t cookie)
|
||||
static int __device_attach(struct device *dev, bool allow_async)
|
||||
{
|
||||
int ret = 0;
|
||||
bool async = false;
|
||||
|
||||
device_lock(dev);
|
||||
if (dev->p->dead) {
|
||||
@ -981,7 +1008,7 @@ static int __device_attach(struct device *dev, bool allow_async)
|
||||
*/
|
||||
dev_dbg(dev, "scheduling asynchronous probe\n");
|
||||
get_device(dev);
|
||||
async_schedule_dev(__device_attach_async_helper, dev);
|
||||
async = true;
|
||||
} else {
|
||||
pm_request_idle(dev);
|
||||
}
|
||||
@ -991,6 +1018,8 @@ static int __device_attach(struct device *dev, bool allow_async)
|
||||
}
|
||||
out_unlock:
|
||||
device_unlock(dev);
|
||||
if (async)
|
||||
async_schedule_dev(__device_attach_async_helper, dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1084,6 +1113,7 @@ static void __driver_attach_async_helper(void *_dev, async_cookie_t cookie)
|
||||
|
||||
__device_driver_lock(dev, dev->parent);
|
||||
drv = dev->p->async_driver;
|
||||
dev->p->async_driver = NULL;
|
||||
ret = driver_probe_device(drv, dev);
|
||||
__device_driver_unlock(dev, dev->parent);
|
||||
|
||||
@ -1130,7 +1160,7 @@ static int __driver_attach(struct device *dev, void *data)
|
||||
*/
|
||||
dev_dbg(dev, "probing driver %s asynchronously\n", drv->name);
|
||||
device_lock(dev);
|
||||
if (!dev->driver) {
|
||||
if (!dev->driver && !dev->p->async_driver) {
|
||||
get_device(dev);
|
||||
dev->p->async_driver = drv;
|
||||
async_schedule_dev(__driver_attach_async_helper, dev);
|
||||
|
@ -30,6 +30,75 @@ static struct device *next_device(struct klist_iter *i)
|
||||
return dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* driver_set_override() - Helper to set or clear driver override.
|
||||
* @dev: Device to change
|
||||
* @override: Address of string to change (e.g. &device->driver_override);
|
||||
* The contents will be freed and hold newly allocated override.
|
||||
* @s: NUL-terminated string, new driver name to force a match, pass empty
|
||||
* string to clear it ("" or "\n", where the latter is only for sysfs
|
||||
* interface).
|
||||
* @len: length of @s
|
||||
*
|
||||
* Helper to set or clear driver override in a device, intended for the cases
|
||||
* when the driver_override field is allocated by driver/bus code.
|
||||
*
|
||||
* Returns: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int driver_set_override(struct device *dev, const char **override,
|
||||
const char *s, size_t len)
|
||||
{
|
||||
const char *new, *old;
|
||||
char *cp;
|
||||
|
||||
if (!override || !s)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The stored value will be used in sysfs show callback (sysfs_emit()),
|
||||
* which has a length limit of PAGE_SIZE and adds a trailing newline.
|
||||
* Thus we can store one character less to avoid truncation during sysfs
|
||||
* show.
|
||||
*/
|
||||
if (len >= (PAGE_SIZE - 1))
|
||||
return -EINVAL;
|
||||
|
||||
if (!len) {
|
||||
/* Empty string passed - clear override */
|
||||
device_lock(dev);
|
||||
old = *override;
|
||||
*override = NULL;
|
||||
device_unlock(dev);
|
||||
kfree(old);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
cp = strnchr(s, len, '\n');
|
||||
if (cp)
|
||||
len = cp - s;
|
||||
|
||||
new = kstrndup(s, len, GFP_KERNEL);
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
device_lock(dev);
|
||||
old = *override;
|
||||
if (cp != s) {
|
||||
*override = new;
|
||||
} else {
|
||||
/* "\n" passed - clear override */
|
||||
kfree(new);
|
||||
*override = NULL;
|
||||
}
|
||||
device_unlock(dev);
|
||||
|
||||
kfree(old);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(driver_set_override);
|
||||
|
||||
/**
|
||||
* driver_for_each_device - Iterator for devices bound to a driver.
|
||||
* @drv: Driver we're iterating.
|
||||
@ -177,6 +246,7 @@ int driver_register(struct device_driver *drv)
|
||||
return ret;
|
||||
}
|
||||
kobject_uevent(&drv->p->kobj, KOBJ_ADD);
|
||||
deferred_probe_extend_timeout();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -29,6 +29,9 @@ if FW_LOADER
|
||||
config FW_LOADER_PAGED_BUF
|
||||
bool
|
||||
|
||||
config FW_LOADER_SYSFS
|
||||
bool
|
||||
|
||||
config EXTRA_FIRMWARE
|
||||
string "Build named firmware blobs into the kernel binary"
|
||||
help
|
||||
@ -72,6 +75,7 @@ config EXTRA_FIRMWARE_DIR
|
||||
|
||||
config FW_LOADER_USER_HELPER
|
||||
bool "Enable the firmware sysfs fallback mechanism"
|
||||
select FW_LOADER_SYSFS
|
||||
select FW_LOADER_PAGED_BUF
|
||||
help
|
||||
This option enables a sysfs loading facility to enable firmware
|
||||
@ -159,21 +163,33 @@ config FW_LOADER_USER_HELPER_FALLBACK
|
||||
|
||||
config FW_LOADER_COMPRESS
|
||||
bool "Enable compressed firmware support"
|
||||
select FW_LOADER_PAGED_BUF
|
||||
select XZ_DEC
|
||||
help
|
||||
This option enables the support for loading compressed firmware
|
||||
files. The caller of firmware API receives the decompressed file
|
||||
content. The compressed file is loaded as a fallback, only after
|
||||
loading the raw file failed at first.
|
||||
|
||||
Currently only XZ-compressed files are supported, and they have to
|
||||
be compressed with either none or crc32 integrity check type (pass
|
||||
"-C crc32" option to xz command).
|
||||
|
||||
Compressed firmware support does not apply to firmware images
|
||||
that are built into the kernel image (CONFIG_EXTRA_FIRMWARE).
|
||||
|
||||
if FW_LOADER_COMPRESS
|
||||
config FW_LOADER_COMPRESS_XZ
|
||||
bool "Enable XZ-compressed firmware support"
|
||||
select FW_LOADER_PAGED_BUF
|
||||
select XZ_DEC
|
||||
help
|
||||
This option adds the support for XZ-compressed files.
|
||||
The files have to be compressed with either none or crc32
|
||||
integrity check type (pass "-C crc32" option to xz command).
|
||||
|
||||
config FW_LOADER_COMPRESS_ZSTD
|
||||
bool "Enable ZSTD-compressed firmware support"
|
||||
select ZSTD_DECOMPRESS
|
||||
help
|
||||
This option adds the support for ZSTD-compressed files.
|
||||
|
||||
endif # FW_LOADER_COMPRESS
|
||||
|
||||
config FW_CACHE
|
||||
bool "Enable firmware caching during suspend"
|
||||
depends on PM_SLEEP
|
||||
@ -186,5 +202,19 @@ config FW_CACHE
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
config FW_UPLOAD
|
||||
bool "Enable users to initiate firmware updates using sysfs"
|
||||
select FW_LOADER_SYSFS
|
||||
select FW_LOADER_PAGED_BUF
|
||||
help
|
||||
Enabling this option will allow device drivers to expose a persistent
|
||||
sysfs interface that allows firmware updates to be initiated from
|
||||
userspace. For example, FPGA based PCIe cards load firmware and FPGA
|
||||
images from local FLASH when the card boots. The images in FLASH may
|
||||
be updated with new images provided by the user. Enable this device
|
||||
to support cards that rely on user-initiated updates for firmware files.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
endif # FW_LOADER
|
||||
endmenu
|
||||
|
@ -6,5 +6,7 @@ obj-$(CONFIG_FW_LOADER) += firmware_class.o
|
||||
firmware_class-objs := main.o
|
||||
firmware_class-$(CONFIG_FW_LOADER_USER_HELPER) += fallback.o
|
||||
firmware_class-$(CONFIG_EFI_EMBEDDED_FIRMWARE) += fallback_platform.o
|
||||
firmware_class-$(CONFIG_FW_LOADER_SYSFS) += sysfs.o
|
||||
firmware_class-$(CONFIG_FW_UPLOAD) += sysfs_upload.o
|
||||
|
||||
obj-y += builtin/
|
||||
|
@ -3,12 +3,9 @@
|
||||
#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 <linux/module.h>
|
||||
|
||||
#include "fallback.h"
|
||||
@ -18,22 +15,6 @@
|
||||
* firmware fallback mechanism
|
||||
*/
|
||||
|
||||
MODULE_IMPORT_NS(FIRMWARE_LOADER_PRIVATE);
|
||||
|
||||
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
|
||||
@ -58,52 +39,11 @@ static long firmware_loading_timeout(void)
|
||||
__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/aborted and disappearance of 'loading'
|
||||
*/
|
||||
if (fw_state_is_aborted(fw_priv) || fw_sysfs_done(fw_priv))
|
||||
return;
|
||||
|
||||
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)
|
||||
@ -120,376 +60,6 @@ void kill_pending_fw_fallback_reqs(bool only_kill_custom)
|
||||
mutex_unlock(&fw_lock);
|
||||
}
|
||||
|
||||
static ssize_t timeout_show(struct class *class, struct class_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%d\n", __firmware_loading_timeout());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
int ret = class_register(&firmware_class);
|
||||
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
return register_firmware_config_sysctl();
|
||||
}
|
||||
|
||||
void unregister_sysfs_loader(void)
|
||||
{
|
||||
unregister_firmware_config_sysctl();
|
||||
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 sysfs_emit(buf, "%d\n", loading);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
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)) {
|
||||
fw_free_paged_buf(fw_priv);
|
||||
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 = fw_map_paged_buf(fw_priv);
|
||||
if (rc)
|
||||
dev_err(dev, "%s: map pages failed\n",
|
||||
__func__);
|
||||
else
|
||||
rc = security_kernel_post_load_data(fw_priv->data,
|
||||
fw_priv->size,
|
||||
LOADING_FIRMWARE, "blob");
|
||||
|
||||
/*
|
||||
* Same logic as fw_load_abort, only the DONE bit
|
||||
* is ignored and we set ABORT only on failure.
|
||||
*/
|
||||
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)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = fw_grow_paged_buf(fw_sysfs->fw_priv,
|
||||
PAGE_ALIGN(min_size) >> PAGE_SHIFT);
|
||||
if (err)
|
||||
fw_load_abort(fw_sysfs);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, u32 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 sysfs fallback mechanism
|
||||
* @fw_sysfs: firmware sysfs information for the firmware to load
|
||||
|
@ -6,29 +6,7 @@
|
||||
#include <linux/device.h>
|
||||
|
||||
#include "firmware.h"
|
||||
|
||||
/**
|
||||
* struct firmware_fallback_config - firmware fallback configuration 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;
|
||||
};
|
||||
#include "sysfs.h"
|
||||
|
||||
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
||||
int firmware_fallback_sysfs(struct firmware *fw, const char *name,
|
||||
@ -40,19 +18,6 @@ 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);
|
||||
#ifdef CONFIG_SYSCTL
|
||||
extern int register_firmware_config_sysctl(void);
|
||||
extern void unregister_firmware_config_sysctl(void);
|
||||
#else
|
||||
static inline int register_firmware_config_sysctl(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void unregister_firmware_config_sysctl(void) { }
|
||||
#endif /* CONFIG_SYSCTL */
|
||||
|
||||
#else /* CONFIG_FW_LOADER_USER_HELPER */
|
||||
static inline int firmware_fallback_sysfs(struct firmware *fw, const char *name,
|
||||
struct device *device,
|
||||
@ -66,15 +31,6 @@ static inline int firmware_fallback_sysfs(struct firmware *fw, const char *name,
|
||||
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 */
|
||||
|
||||
#ifdef CONFIG_EFI_EMBEDDED_FIRMWARE
|
||||
|
@ -87,6 +87,7 @@ struct fw_priv {
|
||||
};
|
||||
|
||||
extern struct mutex fw_lock;
|
||||
extern struct firmware_cache fw_cache;
|
||||
|
||||
static inline bool __fw_state_check(struct fw_priv *fw_priv,
|
||||
enum fw_status status)
|
||||
@ -149,7 +150,22 @@ static inline void fw_state_done(struct fw_priv *fw_priv)
|
||||
__fw_state_set(fw_priv, FW_STATUS_DONE);
|
||||
}
|
||||
|
||||
static inline bool fw_state_is_done(struct fw_priv *fw_priv)
|
||||
{
|
||||
return __fw_state_check(fw_priv, FW_STATUS_DONE);
|
||||
}
|
||||
|
||||
static inline bool fw_state_is_loading(struct fw_priv *fw_priv)
|
||||
{
|
||||
return __fw_state_check(fw_priv, FW_STATUS_LOADING);
|
||||
}
|
||||
|
||||
int alloc_lookup_fw_priv(const char *fw_name, struct firmware_cache *fwc,
|
||||
struct fw_priv **fw_priv, void *dbuf, size_t size,
|
||||
size_t offset, u32 opt_flags);
|
||||
int assign_fw(struct firmware *fw, struct device *device);
|
||||
void free_fw_priv(struct fw_priv *fw_priv);
|
||||
void fw_state_init(struct fw_priv *fw_priv);
|
||||
|
||||
#ifdef CONFIG_FW_LOADER
|
||||
bool firmware_is_builtin(const struct firmware *fw);
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include <linux/syscore_ops.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/zstd.h>
|
||||
#include <linux/xz.h>
|
||||
|
||||
#include <generated/utsrelease.h>
|
||||
@ -91,9 +92,9 @@ static inline struct fw_priv *to_fw_priv(struct kref *ref)
|
||||
* guarding for corner cases a global lock should be OK */
|
||||
DEFINE_MUTEX(fw_lock);
|
||||
|
||||
static struct firmware_cache fw_cache;
|
||||
struct firmware_cache fw_cache;
|
||||
|
||||
static void fw_state_init(struct fw_priv *fw_priv)
|
||||
void fw_state_init(struct fw_priv *fw_priv)
|
||||
{
|
||||
struct fw_state *fw_st = &fw_priv->fw_st;
|
||||
|
||||
@ -163,13 +164,9 @@ static struct fw_priv *__lookup_fw_priv(const char *fw_name)
|
||||
}
|
||||
|
||||
/* Returns 1 for batching firmware requests with the same name */
|
||||
static int alloc_lookup_fw_priv(const char *fw_name,
|
||||
struct firmware_cache *fwc,
|
||||
struct fw_priv **fw_priv,
|
||||
void *dbuf,
|
||||
size_t size,
|
||||
size_t offset,
|
||||
u32 opt_flags)
|
||||
int alloc_lookup_fw_priv(const char *fw_name, struct firmware_cache *fwc,
|
||||
struct fw_priv **fw_priv, void *dbuf, size_t size,
|
||||
size_t offset, u32 opt_flags)
|
||||
{
|
||||
struct fw_priv *tmp;
|
||||
|
||||
@ -224,7 +221,7 @@ static void __free_fw_priv(struct kref *ref)
|
||||
kfree(fw_priv);
|
||||
}
|
||||
|
||||
static void free_fw_priv(struct fw_priv *fw_priv)
|
||||
void free_fw_priv(struct fw_priv *fw_priv)
|
||||
{
|
||||
struct firmware_cache *fwc = fw_priv->fwc;
|
||||
spin_lock(&fwc->lock);
|
||||
@ -253,6 +250,8 @@ void fw_free_paged_buf(struct fw_priv *fw_priv)
|
||||
fw_priv->pages = NULL;
|
||||
fw_priv->page_array_size = 0;
|
||||
fw_priv->nr_pages = 0;
|
||||
fw_priv->data = NULL;
|
||||
fw_priv->size = 0;
|
||||
}
|
||||
|
||||
int fw_grow_paged_buf(struct fw_priv *fw_priv, int pages_needed)
|
||||
@ -304,10 +303,74 @@ int fw_map_paged_buf(struct fw_priv *fw_priv)
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* ZSTD-compressed firmware support
|
||||
*/
|
||||
#ifdef CONFIG_FW_LOADER_COMPRESS_ZSTD
|
||||
static int fw_decompress_zstd(struct device *dev, struct fw_priv *fw_priv,
|
||||
size_t in_size, const void *in_buffer)
|
||||
{
|
||||
size_t len, out_size, workspace_size;
|
||||
void *workspace, *out_buf;
|
||||
zstd_dctx *ctx;
|
||||
int err;
|
||||
|
||||
if (fw_priv->allocated_size) {
|
||||
out_size = fw_priv->allocated_size;
|
||||
out_buf = fw_priv->data;
|
||||
} else {
|
||||
zstd_frame_header params;
|
||||
|
||||
if (zstd_get_frame_header(¶ms, in_buffer, in_size) ||
|
||||
params.frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN) {
|
||||
dev_dbg(dev, "%s: invalid zstd header\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
out_size = params.frameContentSize;
|
||||
out_buf = vzalloc(out_size);
|
||||
if (!out_buf)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
workspace_size = zstd_dctx_workspace_bound();
|
||||
workspace = kvzalloc(workspace_size, GFP_KERNEL);
|
||||
if (!workspace) {
|
||||
err = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
ctx = zstd_init_dctx(workspace, workspace_size);
|
||||
if (!ctx) {
|
||||
dev_dbg(dev, "%s: failed to initialize context\n", __func__);
|
||||
err = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
len = zstd_decompress_dctx(ctx, out_buf, out_size, in_buffer, in_size);
|
||||
if (zstd_is_error(len)) {
|
||||
dev_dbg(dev, "%s: failed to decompress: %d\n", __func__,
|
||||
zstd_get_error_code(len));
|
||||
err = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!fw_priv->allocated_size)
|
||||
fw_priv->data = out_buf;
|
||||
fw_priv->size = len;
|
||||
err = 0;
|
||||
|
||||
error:
|
||||
kvfree(workspace);
|
||||
if (err && !fw_priv->allocated_size)
|
||||
vfree(out_buf);
|
||||
return err;
|
||||
}
|
||||
#endif /* CONFIG_FW_LOADER_COMPRESS_ZSTD */
|
||||
|
||||
/*
|
||||
* XZ-compressed firmware support
|
||||
*/
|
||||
#ifdef CONFIG_FW_LOADER_COMPRESS
|
||||
#ifdef CONFIG_FW_LOADER_COMPRESS_XZ
|
||||
/* show an error and return the standard error code */
|
||||
static int fw_decompress_xz_error(struct device *dev, enum xz_ret xz_ret)
|
||||
{
|
||||
@ -401,7 +464,7 @@ static int fw_decompress_xz(struct device *dev, struct fw_priv *fw_priv,
|
||||
else
|
||||
return fw_decompress_xz_pages(dev, fw_priv, in_size, in_buffer);
|
||||
}
|
||||
#endif /* CONFIG_FW_LOADER_COMPRESS */
|
||||
#endif /* CONFIG_FW_LOADER_COMPRESS_XZ */
|
||||
|
||||
/* direct firmware loading support */
|
||||
static char fw_path_para[256];
|
||||
@ -771,7 +834,12 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
|
||||
if (!(opt_flags & FW_OPT_PARTIAL))
|
||||
nondirect = true;
|
||||
|
||||
#ifdef CONFIG_FW_LOADER_COMPRESS
|
||||
#ifdef CONFIG_FW_LOADER_COMPRESS_ZSTD
|
||||
if (ret == -ENOENT && nondirect)
|
||||
ret = fw_get_filesystem_firmware(device, fw->priv, ".zst",
|
||||
fw_decompress_zstd);
|
||||
#endif
|
||||
#ifdef CONFIG_FW_LOADER_COMPRESS_XZ
|
||||
if (ret == -ENOENT && nondirect)
|
||||
ret = fw_get_filesystem_firmware(device, fw->priv, ".xz",
|
||||
fw_decompress_xz);
|
||||
|
422
drivers/base/firmware_loader/sysfs.c
Normal file
422
drivers/base/firmware_loader/sysfs.c
Normal file
@ -0,0 +1,422 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "sysfs.h"
|
||||
|
||||
/*
|
||||
* sysfs support for firmware loader
|
||||
*/
|
||||
|
||||
void __fw_load_abort(struct fw_priv *fw_priv)
|
||||
{
|
||||
/*
|
||||
* There is a small window in which user can write to 'loading'
|
||||
* between loading done/aborted and disappearance of 'loading'
|
||||
*/
|
||||
if (fw_state_is_aborted(fw_priv) || fw_state_is_done(fw_priv))
|
||||
return;
|
||||
|
||||
fw_state_aborted(fw_priv);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
||||
static ssize_t timeout_show(struct class *class, struct class_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%d\n", __firmware_loading_timeout());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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;
|
||||
}
|
||||
#endif /* CONFIG_FW_LOADER_USER_HELPER */
|
||||
|
||||
static void fw_dev_release(struct device *dev)
|
||||
{
|
||||
struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
|
||||
|
||||
if (fw_sysfs->fw_upload_priv) {
|
||||
free_fw_priv(fw_sysfs->fw_priv);
|
||||
kfree(fw_sysfs->fw_upload_priv);
|
||||
}
|
||||
kfree(fw_sysfs);
|
||||
}
|
||||
|
||||
static struct class firmware_class = {
|
||||
.name = "firmware",
|
||||
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
||||
.class_groups = firmware_class_groups,
|
||||
.dev_uevent = firmware_uevent,
|
||||
#endif
|
||||
.dev_release = fw_dev_release,
|
||||
};
|
||||
|
||||
int register_sysfs_loader(void)
|
||||
{
|
||||
int ret = class_register(&firmware_class);
|
||||
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
return register_firmware_config_sysctl();
|
||||
}
|
||||
|
||||
void unregister_sysfs_loader(void)
|
||||
{
|
||||
unregister_firmware_config_sysctl();
|
||||
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_state_is_loading(fw_sysfs->fw_priv);
|
||||
mutex_unlock(&fw_lock);
|
||||
|
||||
return sysfs_emit(buf, "%d\n", loading);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
mutex_lock(&fw_lock);
|
||||
fw_priv = fw_sysfs->fw_priv;
|
||||
if (fw_state_is_aborted(fw_priv) || fw_state_is_done(fw_priv))
|
||||
goto out;
|
||||
|
||||
switch (loading) {
|
||||
case 1:
|
||||
/* discarding any previous partial load */
|
||||
fw_free_paged_buf(fw_priv);
|
||||
fw_state_start(fw_priv);
|
||||
break;
|
||||
case 0:
|
||||
if (fw_state_is_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 = fw_map_paged_buf(fw_priv);
|
||||
if (rc)
|
||||
dev_err(dev, "%s: map pages failed\n",
|
||||
__func__);
|
||||
else
|
||||
rc = security_kernel_post_load_data(fw_priv->data,
|
||||
fw_priv->size,
|
||||
LOADING_FIRMWARE,
|
||||
"blob");
|
||||
|
||||
/*
|
||||
* Same logic as fw_load_abort, only the DONE bit
|
||||
* is ignored and we set ABORT only on failure.
|
||||
*/
|
||||
if (rc) {
|
||||
fw_state_aborted(fw_priv);
|
||||
written = rc;
|
||||
} else {
|
||||
fw_state_done(fw_priv);
|
||||
|
||||
/*
|
||||
* If this is a user-initiated firmware upload
|
||||
* then start the upload in a worker thread now.
|
||||
*/
|
||||
rc = fw_upload_start(fw_sysfs);
|
||||
if (rc)
|
||||
written = rc;
|
||||
}
|
||||
break;
|
||||
}
|
||||
fallthrough;
|
||||
default:
|
||||
dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading);
|
||||
fallthrough;
|
||||
case -1:
|
||||
fw_load_abort(fw_sysfs);
|
||||
if (fw_sysfs->fw_upload_priv)
|
||||
fw_state_init(fw_sysfs->fw_priv);
|
||||
|
||||
break;
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&fw_lock);
|
||||
return written;
|
||||
}
|
||||
|
||||
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_state_is_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)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = fw_grow_paged_buf(fw_sysfs->fw_priv,
|
||||
PAGE_ALIGN(min_size) >> PAGE_SHIFT);
|
||||
if (err)
|
||||
fw_load_abort(fw_sysfs);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_state_is_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,
|
||||
#ifdef CONFIG_FW_UPLOAD
|
||||
&dev_attr_cancel.attr,
|
||||
&dev_attr_status.attr,
|
||||
&dev_attr_error.attr,
|
||||
&dev_attr_remaining_size.attr,
|
||||
#endif
|
||||
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,
|
||||
#ifdef CONFIG_FW_UPLOAD
|
||||
.is_visible = fw_upload_is_visible,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const struct attribute_group *fw_dev_attr_groups[] = {
|
||||
&fw_dev_attr_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
struct fw_sysfs *
|
||||
fw_create_instance(struct firmware *firmware, const char *fw_name,
|
||||
struct device *device, u32 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;
|
||||
}
|
117
drivers/base/firmware_loader/sysfs.h
Normal file
117
drivers/base/firmware_loader/sysfs.h
Normal file
@ -0,0 +1,117 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef __FIRMWARE_SYSFS_H
|
||||
#define __FIRMWARE_SYSFS_H
|
||||
|
||||
#include <linux/device.h>
|
||||
|
||||
#include "firmware.h"
|
||||
|
||||
MODULE_IMPORT_NS(FIRMWARE_LOADER_PRIVATE);
|
||||
|
||||
extern struct firmware_fallback_config fw_fallback_config;
|
||||
extern struct device_attribute dev_attr_loading;
|
||||
|
||||
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
||||
/**
|
||||
* struct firmware_fallback_config - firmware fallback configuration 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;
|
||||
};
|
||||
|
||||
/* 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 inline void __fw_fallback_set_timeout(int timeout)
|
||||
{
|
||||
fw_fallback_config.loading_timeout = timeout;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FW_LOADER_SYSFS
|
||||
int register_sysfs_loader(void);
|
||||
void unregister_sysfs_loader(void);
|
||||
#if defined(CONFIG_FW_LOADER_USER_HELPER) && defined(CONFIG_SYSCTL)
|
||||
int register_firmware_config_sysctl(void);
|
||||
void unregister_firmware_config_sysctl(void);
|
||||
#else
|
||||
static inline int register_firmware_config_sysctl(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void unregister_firmware_config_sysctl(void) { }
|
||||
#endif /* CONFIG_FW_LOADER_USER_HELPER && CONFIG_SYSCTL */
|
||||
#else /* CONFIG_FW_LOADER_SYSFS */
|
||||
static inline int register_sysfs_loader(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void unregister_sysfs_loader(void)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_FW_LOADER_SYSFS */
|
||||
|
||||
struct fw_sysfs {
|
||||
bool nowait;
|
||||
struct device dev;
|
||||
struct fw_priv *fw_priv;
|
||||
struct firmware *fw;
|
||||
void *fw_upload_priv;
|
||||
};
|
||||
|
||||
static inline struct fw_sysfs *to_fw_sysfs(struct device *dev)
|
||||
{
|
||||
return container_of(dev, struct fw_sysfs, dev);
|
||||
}
|
||||
|
||||
void __fw_load_abort(struct fw_priv *fw_priv);
|
||||
|
||||
static inline void fw_load_abort(struct fw_sysfs *fw_sysfs)
|
||||
{
|
||||
struct fw_priv *fw_priv = fw_sysfs->fw_priv;
|
||||
|
||||
__fw_load_abort(fw_priv);
|
||||
}
|
||||
|
||||
struct fw_sysfs *
|
||||
fw_create_instance(struct firmware *firmware, const char *fw_name,
|
||||
struct device *device, u32 opt_flags);
|
||||
|
||||
#ifdef CONFIG_FW_UPLOAD
|
||||
extern struct device_attribute dev_attr_status;
|
||||
extern struct device_attribute dev_attr_error;
|
||||
extern struct device_attribute dev_attr_cancel;
|
||||
extern struct device_attribute dev_attr_remaining_size;
|
||||
|
||||
int fw_upload_start(struct fw_sysfs *fw_sysfs);
|
||||
umode_t fw_upload_is_visible(struct kobject *kobj, struct attribute *attr, int n);
|
||||
#else
|
||||
static inline int fw_upload_start(struct fw_sysfs *fw_sysfs)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __FIRMWARE_SYSFS_H */
|
397
drivers/base/firmware_loader/sysfs_upload.c
Normal file
397
drivers/base/firmware_loader/sysfs_upload.c
Normal file
@ -0,0 +1,397 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "sysfs_upload.h"
|
||||
|
||||
/*
|
||||
* Support for user-space to initiate a firmware upload to a device.
|
||||
*/
|
||||
|
||||
static const char * const fw_upload_prog_str[] = {
|
||||
[FW_UPLOAD_PROG_IDLE] = "idle",
|
||||
[FW_UPLOAD_PROG_RECEIVING] = "receiving",
|
||||
[FW_UPLOAD_PROG_PREPARING] = "preparing",
|
||||
[FW_UPLOAD_PROG_TRANSFERRING] = "transferring",
|
||||
[FW_UPLOAD_PROG_PROGRAMMING] = "programming"
|
||||
};
|
||||
|
||||
static const char * const fw_upload_err_str[] = {
|
||||
[FW_UPLOAD_ERR_NONE] = "none",
|
||||
[FW_UPLOAD_ERR_HW_ERROR] = "hw-error",
|
||||
[FW_UPLOAD_ERR_TIMEOUT] = "timeout",
|
||||
[FW_UPLOAD_ERR_CANCELED] = "user-abort",
|
||||
[FW_UPLOAD_ERR_BUSY] = "device-busy",
|
||||
[FW_UPLOAD_ERR_INVALID_SIZE] = "invalid-file-size",
|
||||
[FW_UPLOAD_ERR_RW_ERROR] = "read-write-error",
|
||||
[FW_UPLOAD_ERR_WEAROUT] = "flash-wearout",
|
||||
};
|
||||
|
||||
static const char *fw_upload_progress(struct device *dev,
|
||||
enum fw_upload_prog prog)
|
||||
{
|
||||
const char *status = "unknown-status";
|
||||
|
||||
if (prog < FW_UPLOAD_PROG_MAX)
|
||||
status = fw_upload_prog_str[prog];
|
||||
else
|
||||
dev_err(dev, "Invalid status during secure update: %d\n", prog);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static const char *fw_upload_error(struct device *dev,
|
||||
enum fw_upload_err err_code)
|
||||
{
|
||||
const char *error = "unknown-error";
|
||||
|
||||
if (err_code < FW_UPLOAD_ERR_MAX)
|
||||
error = fw_upload_err_str[err_code];
|
||||
else
|
||||
dev_err(dev, "Invalid error code during secure update: %d\n",
|
||||
err_code);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
status_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct fw_upload_priv *fwlp = to_fw_sysfs(dev)->fw_upload_priv;
|
||||
|
||||
return sysfs_emit(buf, "%s\n", fw_upload_progress(dev, fwlp->progress));
|
||||
}
|
||||
DEVICE_ATTR_RO(status);
|
||||
|
||||
static ssize_t
|
||||
error_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct fw_upload_priv *fwlp = to_fw_sysfs(dev)->fw_upload_priv;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&fwlp->lock);
|
||||
|
||||
if (fwlp->progress != FW_UPLOAD_PROG_IDLE)
|
||||
ret = -EBUSY;
|
||||
else if (!fwlp->err_code)
|
||||
ret = 0;
|
||||
else
|
||||
ret = sysfs_emit(buf, "%s:%s\n",
|
||||
fw_upload_progress(dev, fwlp->err_progress),
|
||||
fw_upload_error(dev, fwlp->err_code));
|
||||
|
||||
mutex_unlock(&fwlp->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
DEVICE_ATTR_RO(error);
|
||||
|
||||
static ssize_t cancel_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct fw_upload_priv *fwlp = to_fw_sysfs(dev)->fw_upload_priv;
|
||||
int ret = count;
|
||||
bool cancel;
|
||||
|
||||
if (kstrtobool(buf, &cancel) || !cancel)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&fwlp->lock);
|
||||
if (fwlp->progress == FW_UPLOAD_PROG_IDLE)
|
||||
ret = -ENODEV;
|
||||
|
||||
fwlp->ops->cancel(fwlp->fw_upload);
|
||||
mutex_unlock(&fwlp->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
DEVICE_ATTR_WO(cancel);
|
||||
|
||||
static ssize_t remaining_size_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct fw_upload_priv *fwlp = to_fw_sysfs(dev)->fw_upload_priv;
|
||||
|
||||
return sysfs_emit(buf, "%u\n", fwlp->remaining_size);
|
||||
}
|
||||
DEVICE_ATTR_RO(remaining_size);
|
||||
|
||||
umode_t
|
||||
fw_upload_is_visible(struct kobject *kobj, struct attribute *attr, int n)
|
||||
{
|
||||
static struct fw_sysfs *fw_sysfs;
|
||||
|
||||
fw_sysfs = to_fw_sysfs(kobj_to_dev(kobj));
|
||||
|
||||
if (fw_sysfs->fw_upload_priv || attr == &dev_attr_loading.attr)
|
||||
return attr->mode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fw_upload_update_progress(struct fw_upload_priv *fwlp,
|
||||
enum fw_upload_prog new_progress)
|
||||
{
|
||||
mutex_lock(&fwlp->lock);
|
||||
fwlp->progress = new_progress;
|
||||
mutex_unlock(&fwlp->lock);
|
||||
}
|
||||
|
||||
static void fw_upload_set_error(struct fw_upload_priv *fwlp,
|
||||
enum fw_upload_err err_code)
|
||||
{
|
||||
mutex_lock(&fwlp->lock);
|
||||
fwlp->err_progress = fwlp->progress;
|
||||
fwlp->err_code = err_code;
|
||||
mutex_unlock(&fwlp->lock);
|
||||
}
|
||||
|
||||
static void fw_upload_prog_complete(struct fw_upload_priv *fwlp)
|
||||
{
|
||||
mutex_lock(&fwlp->lock);
|
||||
fwlp->progress = FW_UPLOAD_PROG_IDLE;
|
||||
mutex_unlock(&fwlp->lock);
|
||||
}
|
||||
|
||||
static void fw_upload_main(struct work_struct *work)
|
||||
{
|
||||
struct fw_upload_priv *fwlp;
|
||||
struct fw_sysfs *fw_sysfs;
|
||||
u32 written = 0, offset = 0;
|
||||
enum fw_upload_err ret;
|
||||
struct device *fw_dev;
|
||||
struct fw_upload *fwl;
|
||||
|
||||
fwlp = container_of(work, struct fw_upload_priv, work);
|
||||
fwl = fwlp->fw_upload;
|
||||
fw_sysfs = (struct fw_sysfs *)fwl->priv;
|
||||
fw_dev = &fw_sysfs->dev;
|
||||
|
||||
fw_upload_update_progress(fwlp, FW_UPLOAD_PROG_PREPARING);
|
||||
ret = fwlp->ops->prepare(fwl, fwlp->data, fwlp->remaining_size);
|
||||
if (ret != FW_UPLOAD_ERR_NONE) {
|
||||
fw_upload_set_error(fwlp, ret);
|
||||
goto putdev_exit;
|
||||
}
|
||||
|
||||
fw_upload_update_progress(fwlp, FW_UPLOAD_PROG_TRANSFERRING);
|
||||
while (fwlp->remaining_size) {
|
||||
ret = fwlp->ops->write(fwl, fwlp->data, offset,
|
||||
fwlp->remaining_size, &written);
|
||||
if (ret != FW_UPLOAD_ERR_NONE || !written) {
|
||||
if (ret == FW_UPLOAD_ERR_NONE) {
|
||||
dev_warn(fw_dev, "write-op wrote zero data\n");
|
||||
ret = FW_UPLOAD_ERR_RW_ERROR;
|
||||
}
|
||||
fw_upload_set_error(fwlp, ret);
|
||||
goto done;
|
||||
}
|
||||
|
||||
fwlp->remaining_size -= written;
|
||||
offset += written;
|
||||
}
|
||||
|
||||
fw_upload_update_progress(fwlp, FW_UPLOAD_PROG_PROGRAMMING);
|
||||
ret = fwlp->ops->poll_complete(fwl);
|
||||
if (ret != FW_UPLOAD_ERR_NONE)
|
||||
fw_upload_set_error(fwlp, ret);
|
||||
|
||||
done:
|
||||
if (fwlp->ops->cleanup)
|
||||
fwlp->ops->cleanup(fwl);
|
||||
|
||||
putdev_exit:
|
||||
put_device(fw_dev->parent);
|
||||
|
||||
/*
|
||||
* Note: fwlp->remaining_size is left unmodified here to provide
|
||||
* additional information on errors. It will be reinitialized when
|
||||
* the next firmeware upload begins.
|
||||
*/
|
||||
mutex_lock(&fw_lock);
|
||||
fw_free_paged_buf(fw_sysfs->fw_priv);
|
||||
fw_state_init(fw_sysfs->fw_priv);
|
||||
mutex_unlock(&fw_lock);
|
||||
fwlp->data = NULL;
|
||||
fw_upload_prog_complete(fwlp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Start a worker thread to upload data to the parent driver.
|
||||
* Must be called with fw_lock held.
|
||||
*/
|
||||
int fw_upload_start(struct fw_sysfs *fw_sysfs)
|
||||
{
|
||||
struct fw_priv *fw_priv = fw_sysfs->fw_priv;
|
||||
struct device *fw_dev = &fw_sysfs->dev;
|
||||
struct fw_upload_priv *fwlp;
|
||||
|
||||
if (!fw_sysfs->fw_upload_priv)
|
||||
return 0;
|
||||
|
||||
if (!fw_priv->size) {
|
||||
fw_free_paged_buf(fw_priv);
|
||||
fw_state_init(fw_sysfs->fw_priv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fwlp = fw_sysfs->fw_upload_priv;
|
||||
mutex_lock(&fwlp->lock);
|
||||
|
||||
/* Do not interfere with an on-going fw_upload */
|
||||
if (fwlp->progress != FW_UPLOAD_PROG_IDLE) {
|
||||
mutex_unlock(&fwlp->lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
get_device(fw_dev->parent); /* released in fw_upload_main */
|
||||
|
||||
fwlp->progress = FW_UPLOAD_PROG_RECEIVING;
|
||||
fwlp->err_code = 0;
|
||||
fwlp->remaining_size = fw_priv->size;
|
||||
fwlp->data = fw_priv->data;
|
||||
|
||||
pr_debug("%s: fw-%s fw_priv=%p data=%p size=%u\n",
|
||||
__func__, fw_priv->fw_name,
|
||||
fw_priv, fw_priv->data,
|
||||
(unsigned int)fw_priv->size);
|
||||
|
||||
queue_work(system_long_wq, &fwlp->work);
|
||||
mutex_unlock(&fwlp->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* firmware_upload_register() - register for the firmware upload sysfs API
|
||||
* @module: kernel module of this device
|
||||
* @parent: parent device instantiating firmware upload
|
||||
* @name: firmware name to be associated with this device
|
||||
* @ops: pointer to structure of firmware upload ops
|
||||
* @dd_handle: pointer to parent driver private data
|
||||
*
|
||||
* @name must be unique among all users of firmware upload. The firmware
|
||||
* sysfs files for this device will be found at /sys/class/firmware/@name.
|
||||
*
|
||||
* Return: struct fw_upload pointer or ERR_PTR()
|
||||
*
|
||||
**/
|
||||
struct fw_upload *
|
||||
firmware_upload_register(struct module *module, struct device *parent,
|
||||
const char *name, const struct fw_upload_ops *ops,
|
||||
void *dd_handle)
|
||||
{
|
||||
u32 opt_flags = FW_OPT_NOCACHE;
|
||||
struct fw_upload *fw_upload;
|
||||
struct fw_upload_priv *fw_upload_priv;
|
||||
struct fw_sysfs *fw_sysfs;
|
||||
struct fw_priv *fw_priv;
|
||||
struct device *fw_dev;
|
||||
int ret;
|
||||
|
||||
if (!name || name[0] == '\0')
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (!ops || !ops->cancel || !ops->prepare ||
|
||||
!ops->write || !ops->poll_complete) {
|
||||
dev_err(parent, "Attempt to register without all required ops\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
if (!try_module_get(module))
|
||||
return ERR_PTR(-EFAULT);
|
||||
|
||||
fw_upload = kzalloc(sizeof(*fw_upload), GFP_KERNEL);
|
||||
if (!fw_upload) {
|
||||
ret = -ENOMEM;
|
||||
goto exit_module_put;
|
||||
}
|
||||
|
||||
fw_upload_priv = kzalloc(sizeof(*fw_upload_priv), GFP_KERNEL);
|
||||
if (!fw_upload_priv) {
|
||||
ret = -ENOMEM;
|
||||
goto free_fw_upload;
|
||||
}
|
||||
|
||||
fw_upload_priv->fw_upload = fw_upload;
|
||||
fw_upload_priv->ops = ops;
|
||||
mutex_init(&fw_upload_priv->lock);
|
||||
fw_upload_priv->module = module;
|
||||
fw_upload_priv->name = name;
|
||||
fw_upload_priv->err_code = 0;
|
||||
fw_upload_priv->progress = FW_UPLOAD_PROG_IDLE;
|
||||
INIT_WORK(&fw_upload_priv->work, fw_upload_main);
|
||||
fw_upload->dd_handle = dd_handle;
|
||||
|
||||
fw_sysfs = fw_create_instance(NULL, name, parent, opt_flags);
|
||||
if (IS_ERR(fw_sysfs)) {
|
||||
ret = PTR_ERR(fw_sysfs);
|
||||
goto free_fw_upload_priv;
|
||||
}
|
||||
fw_upload->priv = fw_sysfs;
|
||||
fw_sysfs->fw_upload_priv = fw_upload_priv;
|
||||
fw_dev = &fw_sysfs->dev;
|
||||
|
||||
ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, NULL, 0, 0,
|
||||
FW_OPT_NOCACHE);
|
||||
if (ret != 0) {
|
||||
if (ret > 0)
|
||||
ret = -EINVAL;
|
||||
goto free_fw_sysfs;
|
||||
}
|
||||
fw_priv->is_paged_buf = true;
|
||||
fw_sysfs->fw_priv = fw_priv;
|
||||
|
||||
ret = device_add(fw_dev);
|
||||
if (ret) {
|
||||
dev_err(fw_dev, "%s: device_register failed\n", __func__);
|
||||
put_device(fw_dev);
|
||||
goto exit_module_put;
|
||||
}
|
||||
|
||||
return fw_upload;
|
||||
|
||||
free_fw_sysfs:
|
||||
kfree(fw_sysfs);
|
||||
|
||||
free_fw_upload_priv:
|
||||
kfree(fw_upload_priv);
|
||||
|
||||
free_fw_upload:
|
||||
kfree(fw_upload);
|
||||
|
||||
exit_module_put:
|
||||
module_put(module);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(firmware_upload_register);
|
||||
|
||||
/**
|
||||
* firmware_upload_unregister() - Unregister firmware upload interface
|
||||
* @fw_upload: pointer to struct fw_upload
|
||||
**/
|
||||
void firmware_upload_unregister(struct fw_upload *fw_upload)
|
||||
{
|
||||
struct fw_sysfs *fw_sysfs = fw_upload->priv;
|
||||
struct fw_upload_priv *fw_upload_priv = fw_sysfs->fw_upload_priv;
|
||||
|
||||
mutex_lock(&fw_upload_priv->lock);
|
||||
if (fw_upload_priv->progress == FW_UPLOAD_PROG_IDLE) {
|
||||
mutex_unlock(&fw_upload_priv->lock);
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
fw_upload_priv->ops->cancel(fw_upload);
|
||||
mutex_unlock(&fw_upload_priv->lock);
|
||||
|
||||
/* Ensure lower-level device-driver is finished */
|
||||
flush_work(&fw_upload_priv->work);
|
||||
|
||||
unregister:
|
||||
device_unregister(&fw_sysfs->dev);
|
||||
module_put(fw_upload_priv->module);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(firmware_upload_unregister);
|
41
drivers/base/firmware_loader/sysfs_upload.h
Normal file
41
drivers/base/firmware_loader/sysfs_upload.h
Normal file
@ -0,0 +1,41 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef __SYSFS_UPLOAD_H
|
||||
#define __SYSFS_UPLOAD_H
|
||||
|
||||
#include <linux/device.h>
|
||||
|
||||
#include "sysfs.h"
|
||||
|
||||
/**
|
||||
* enum fw_upload_prog - firmware upload progress codes
|
||||
* @FW_UPLOAD_PROG_IDLE: there is no firmware upload in progress
|
||||
* @FW_UPLOAD_PROG_RECEIVING: worker thread is receiving firmware data
|
||||
* @FW_UPLOAD_PROG_PREPARING: target device is preparing for firmware upload
|
||||
* @FW_UPLOAD_PROG_TRANSFERRING: data is being copied to the device
|
||||
* @FW_UPLOAD_PROG_PROGRAMMING: device is performing the firmware update
|
||||
* @FW_UPLOAD_PROG_MAX: Maximum progress code marker
|
||||
*/
|
||||
enum fw_upload_prog {
|
||||
FW_UPLOAD_PROG_IDLE,
|
||||
FW_UPLOAD_PROG_RECEIVING,
|
||||
FW_UPLOAD_PROG_PREPARING,
|
||||
FW_UPLOAD_PROG_TRANSFERRING,
|
||||
FW_UPLOAD_PROG_PROGRAMMING,
|
||||
FW_UPLOAD_PROG_MAX
|
||||
};
|
||||
|
||||
struct fw_upload_priv {
|
||||
struct fw_upload *fw_upload;
|
||||
struct module *module;
|
||||
const char *name;
|
||||
const struct fw_upload_ops *ops;
|
||||
struct mutex lock; /* protect data structure contents */
|
||||
struct work_struct work;
|
||||
const u8 *data; /* pointer to update data */
|
||||
u32 remaining_size; /* size remaining to transfer */
|
||||
enum fw_upload_prog progress;
|
||||
enum fw_upload_prog err_progress; /* progress at time of failure */
|
||||
enum fw_upload_err err_code; /* security manager error code */
|
||||
};
|
||||
|
||||
#endif /* __SYSFS_UPLOAD_H */
|
143
drivers/base/physical_location.c
Normal file
143
drivers/base/physical_location.c
Normal file
@ -0,0 +1,143 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Device physical location support
|
||||
*
|
||||
* Author: Won Chung <wonchung@google.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#include "physical_location.h"
|
||||
|
||||
bool dev_add_physical_location(struct device *dev)
|
||||
{
|
||||
struct acpi_pld_info *pld;
|
||||
acpi_status status;
|
||||
|
||||
if (!has_acpi_companion(dev))
|
||||
return false;
|
||||
|
||||
status = acpi_get_physical_device_location(ACPI_HANDLE(dev), &pld);
|
||||
if (ACPI_FAILURE(status))
|
||||
return false;
|
||||
|
||||
dev->physical_location =
|
||||
kzalloc(sizeof(*dev->physical_location), GFP_KERNEL);
|
||||
if (!dev->physical_location)
|
||||
return false;
|
||||
dev->physical_location->panel = pld->panel;
|
||||
dev->physical_location->vertical_position = pld->vertical_position;
|
||||
dev->physical_location->horizontal_position = pld->horizontal_position;
|
||||
dev->physical_location->dock = pld->dock;
|
||||
dev->physical_location->lid = pld->lid;
|
||||
|
||||
ACPI_FREE(pld);
|
||||
return true;
|
||||
}
|
||||
|
||||
static ssize_t panel_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
const char *panel;
|
||||
|
||||
switch (dev->physical_location->panel) {
|
||||
case DEVICE_PANEL_TOP:
|
||||
panel = "top";
|
||||
break;
|
||||
case DEVICE_PANEL_BOTTOM:
|
||||
panel = "bottom";
|
||||
break;
|
||||
case DEVICE_PANEL_LEFT:
|
||||
panel = "left";
|
||||
break;
|
||||
case DEVICE_PANEL_RIGHT:
|
||||
panel = "right";
|
||||
break;
|
||||
case DEVICE_PANEL_FRONT:
|
||||
panel = "front";
|
||||
break;
|
||||
case DEVICE_PANEL_BACK:
|
||||
panel = "back";
|
||||
break;
|
||||
default:
|
||||
panel = "unknown";
|
||||
}
|
||||
return sysfs_emit(buf, "%s\n", panel);
|
||||
}
|
||||
static DEVICE_ATTR_RO(panel);
|
||||
|
||||
static ssize_t vertical_position_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
const char *vertical_position;
|
||||
|
||||
switch (dev->physical_location->vertical_position) {
|
||||
case DEVICE_VERT_POS_UPPER:
|
||||
vertical_position = "upper";
|
||||
break;
|
||||
case DEVICE_VERT_POS_CENTER:
|
||||
vertical_position = "center";
|
||||
break;
|
||||
case DEVICE_VERT_POS_LOWER:
|
||||
vertical_position = "lower";
|
||||
break;
|
||||
default:
|
||||
vertical_position = "unknown";
|
||||
}
|
||||
return sysfs_emit(buf, "%s\n", vertical_position);
|
||||
}
|
||||
static DEVICE_ATTR_RO(vertical_position);
|
||||
|
||||
static ssize_t horizontal_position_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
const char *horizontal_position;
|
||||
|
||||
switch (dev->physical_location->horizontal_position) {
|
||||
case DEVICE_HORI_POS_LEFT:
|
||||
horizontal_position = "left";
|
||||
break;
|
||||
case DEVICE_HORI_POS_CENTER:
|
||||
horizontal_position = "center";
|
||||
break;
|
||||
case DEVICE_HORI_POS_RIGHT:
|
||||
horizontal_position = "right";
|
||||
break;
|
||||
default:
|
||||
horizontal_position = "unknown";
|
||||
}
|
||||
return sysfs_emit(buf, "%s\n", horizontal_position);
|
||||
}
|
||||
static DEVICE_ATTR_RO(horizontal_position);
|
||||
|
||||
static ssize_t dock_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%s\n",
|
||||
dev->physical_location->dock ? "yes" : "no");
|
||||
}
|
||||
static DEVICE_ATTR_RO(dock);
|
||||
|
||||
static ssize_t lid_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%s\n",
|
||||
dev->physical_location->lid ? "yes" : "no");
|
||||
}
|
||||
static DEVICE_ATTR_RO(lid);
|
||||
|
||||
static struct attribute *dev_attr_physical_location[] = {
|
||||
&dev_attr_panel.attr,
|
||||
&dev_attr_vertical_position.attr,
|
||||
&dev_attr_horizontal_position.attr,
|
||||
&dev_attr_dock.attr,
|
||||
&dev_attr_lid.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
const struct attribute_group dev_attr_physical_location_group = {
|
||||
.name = "physical_location",
|
||||
.attrs = dev_attr_physical_location,
|
||||
};
|
||||
|
16
drivers/base/physical_location.h
Normal file
16
drivers/base/physical_location.h
Normal file
@ -0,0 +1,16 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Device physical location support
|
||||
*
|
||||
* Author: Won Chung <wonchung@google.com>
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
extern bool dev_add_physical_location(struct device *dev);
|
||||
extern const struct attribute_group dev_attr_physical_location_group;
|
||||
#else
|
||||
static inline bool dev_add_physical_location(struct device *dev) { return false; };
|
||||
static const struct attribute_group dev_attr_physical_location_group = {};
|
||||
#endif
|
@ -233,7 +233,8 @@ int platform_get_irq_optional(struct platform_device *dev, unsigned int num)
|
||||
out_not_found:
|
||||
ret = -ENXIO;
|
||||
out:
|
||||
WARN(ret == 0, "0 is an invalid IRQ number\n");
|
||||
if (WARN(!ret, "0 is an invalid IRQ number\n"))
|
||||
return -EINVAL;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_get_irq_optional);
|
||||
@ -448,7 +449,8 @@ static int __platform_get_irq_byname(struct platform_device *dev,
|
||||
|
||||
r = platform_get_resource_byname(dev, IORESOURCE_IRQ, name);
|
||||
if (r) {
|
||||
WARN(r->start == 0, "0 is an invalid IRQ number\n");
|
||||
if (WARN(!r->start, "0 is an invalid IRQ number\n"))
|
||||
return -EINVAL;
|
||||
return r->start;
|
||||
}
|
||||
|
||||
@ -1277,31 +1279,11 @@ static ssize_t driver_override_store(struct device *dev,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
char *driver_override, *old, *cp;
|
||||
int ret;
|
||||
|
||||
/* We need to keep extra room for a newline */
|
||||
if (count >= (PAGE_SIZE - 1))
|
||||
return -EINVAL;
|
||||
|
||||
driver_override = kstrndup(buf, count, GFP_KERNEL);
|
||||
if (!driver_override)
|
||||
return -ENOMEM;
|
||||
|
||||
cp = strchr(driver_override, '\n');
|
||||
if (cp)
|
||||
*cp = '\0';
|
||||
|
||||
device_lock(dev);
|
||||
old = pdev->driver_override;
|
||||
if (strlen(driver_override)) {
|
||||
pdev->driver_override = driver_override;
|
||||
} else {
|
||||
kfree(driver_override);
|
||||
pdev->driver_override = NULL;
|
||||
}
|
||||
device_unlock(dev);
|
||||
|
||||
kfree(old);
|
||||
ret = driver_set_override(dev, &pdev->driver_override, buf, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
@ -185,31 +185,14 @@ static ssize_t driver_override_store(struct device *dev,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev);
|
||||
char *driver_override, *old = mc_dev->driver_override;
|
||||
char *cp;
|
||||
int ret;
|
||||
|
||||
if (WARN_ON(dev->bus != &fsl_mc_bus_type))
|
||||
return -EINVAL;
|
||||
|
||||
if (count >= (PAGE_SIZE - 1))
|
||||
return -EINVAL;
|
||||
|
||||
driver_override = kstrndup(buf, count, GFP_KERNEL);
|
||||
if (!driver_override)
|
||||
return -ENOMEM;
|
||||
|
||||
cp = strchr(driver_override, '\n');
|
||||
if (cp)
|
||||
*cp = '\0';
|
||||
|
||||
if (strlen(driver_override)) {
|
||||
mc_dev->driver_override = driver_override;
|
||||
} else {
|
||||
kfree(driver_override);
|
||||
mc_dev->driver_override = NULL;
|
||||
}
|
||||
|
||||
kfree(old);
|
||||
ret = driver_set_override(dev, &mc_dev->driver_override, buf, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
@ -683,7 +683,12 @@ struct clk_hw *imx_clk_scu_alloc_dev(const char *name,
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
pdev->driver_override = "imx-scu-clk";
|
||||
ret = driver_set_override(&pdev->dev, &pdev->driver_override,
|
||||
"imx-scu-clk", strlen("imx-scu-clk"));
|
||||
if (ret) {
|
||||
platform_device_put(pdev);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
ret = imx_clk_scu_attach_pd(&pdev->dev, rsrc_id);
|
||||
if (ret)
|
||||
|
@ -575,31 +575,11 @@ static ssize_t driver_override_store(struct device *dev,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct hv_device *hv_dev = device_to_hv_device(dev);
|
||||
char *driver_override, *old, *cp;
|
||||
int ret;
|
||||
|
||||
/* We need to keep extra room for a newline */
|
||||
if (count >= (PAGE_SIZE - 1))
|
||||
return -EINVAL;
|
||||
|
||||
driver_override = kstrndup(buf, count, GFP_KERNEL);
|
||||
if (!driver_override)
|
||||
return -ENOMEM;
|
||||
|
||||
cp = strchr(driver_override, '\n');
|
||||
if (cp)
|
||||
*cp = '\0';
|
||||
|
||||
device_lock(dev);
|
||||
old = hv_dev->driver_override;
|
||||
if (strlen(driver_override)) {
|
||||
hv_dev->driver_override = driver_override;
|
||||
} else {
|
||||
kfree(driver_override);
|
||||
hv_dev->driver_override = NULL;
|
||||
}
|
||||
device_unlock(dev);
|
||||
|
||||
kfree(old);
|
||||
ret = driver_set_override(dev, &hv_dev->driver_override, buf, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
@ -567,31 +567,11 @@ static ssize_t driver_override_store(struct device *dev,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
char *driver_override, *old, *cp;
|
||||
int ret;
|
||||
|
||||
/* We need to keep extra room for a newline */
|
||||
if (count >= (PAGE_SIZE - 1))
|
||||
return -EINVAL;
|
||||
|
||||
driver_override = kstrndup(buf, count, GFP_KERNEL);
|
||||
if (!driver_override)
|
||||
return -ENOMEM;
|
||||
|
||||
cp = strchr(driver_override, '\n');
|
||||
if (cp)
|
||||
*cp = '\0';
|
||||
|
||||
device_lock(dev);
|
||||
old = pdev->driver_override;
|
||||
if (strlen(driver_override)) {
|
||||
pdev->driver_override = driver_override;
|
||||
} else {
|
||||
kfree(driver_override);
|
||||
pdev->driver_override = NULL;
|
||||
}
|
||||
device_unlock(dev);
|
||||
|
||||
kfree(old);
|
||||
ret = driver_set_override(dev, &pdev->driver_override, buf, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
@ -400,7 +400,8 @@ field##_store(struct device *dev, struct device_attribute *attr, \
|
||||
const char *buf, size_t sz) \
|
||||
{ \
|
||||
struct rpmsg_device *rpdev = to_rpmsg_device(dev); \
|
||||
char *new, *old; \
|
||||
const char *old; \
|
||||
char *new; \
|
||||
\
|
||||
new = kstrndup(buf, sz, GFP_KERNEL); \
|
||||
if (!new) \
|
||||
@ -592,24 +593,51 @@ static struct bus_type rpmsg_bus = {
|
||||
.remove = rpmsg_dev_remove,
|
||||
};
|
||||
|
||||
int rpmsg_register_device(struct rpmsg_device *rpdev)
|
||||
/*
|
||||
* A helper for registering rpmsg device with driver override and name.
|
||||
* Drivers should not be using it, but instead rpmsg_register_device().
|
||||
*/
|
||||
int rpmsg_register_device_override(struct rpmsg_device *rpdev,
|
||||
const char *driver_override)
|
||||
{
|
||||
struct device *dev = &rpdev->dev;
|
||||
int ret;
|
||||
|
||||
dev_set_name(&rpdev->dev, "%s.%s.%d.%d", dev_name(dev->parent),
|
||||
if (driver_override)
|
||||
strcpy(rpdev->id.name, driver_override);
|
||||
|
||||
dev_set_name(dev, "%s.%s.%d.%d", dev_name(dev->parent),
|
||||
rpdev->id.name, rpdev->src, rpdev->dst);
|
||||
|
||||
rpdev->dev.bus = &rpmsg_bus;
|
||||
dev->bus = &rpmsg_bus;
|
||||
|
||||
ret = device_register(&rpdev->dev);
|
||||
device_initialize(dev);
|
||||
if (driver_override) {
|
||||
ret = driver_set_override(dev, &rpdev->driver_override,
|
||||
driver_override,
|
||||
strlen(driver_override));
|
||||
if (ret) {
|
||||
dev_err(dev, "device_set_override failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = device_add(dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "device_register failed: %d\n", ret);
|
||||
put_device(&rpdev->dev);
|
||||
dev_err(dev, "device_add failed: %d\n", ret);
|
||||
kfree(rpdev->driver_override);
|
||||
rpdev->driver_override = NULL;
|
||||
put_device(dev);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(rpmsg_register_device_override);
|
||||
|
||||
int rpmsg_register_device(struct rpmsg_device *rpdev)
|
||||
{
|
||||
return rpmsg_register_device_override(rpdev, NULL);
|
||||
}
|
||||
EXPORT_SYMBOL(rpmsg_register_device);
|
||||
|
||||
/*
|
||||
|
@ -94,10 +94,7 @@ int rpmsg_release_channel(struct rpmsg_device *rpdev,
|
||||
*/
|
||||
static inline int rpmsg_ctrldev_register_device(struct rpmsg_device *rpdev)
|
||||
{
|
||||
strcpy(rpdev->id.name, "rpmsg_ctrl");
|
||||
rpdev->driver_override = "rpmsg_ctrl";
|
||||
|
||||
return rpmsg_register_device(rpdev);
|
||||
return rpmsg_register_device_override(rpdev, "rpmsg_ctrl");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -20,12 +20,10 @@
|
||||
*/
|
||||
int rpmsg_ns_register_device(struct rpmsg_device *rpdev)
|
||||
{
|
||||
strcpy(rpdev->id.name, "rpmsg_ns");
|
||||
rpdev->driver_override = "rpmsg_ns";
|
||||
rpdev->src = RPMSG_NS_ADDR;
|
||||
rpdev->dst = RPMSG_NS_ADDR;
|
||||
|
||||
return rpmsg_register_device(rpdev);
|
||||
return rpmsg_register_device_override(rpdev, "rpmsg_ns");
|
||||
}
|
||||
EXPORT_SYMBOL(rpmsg_ns_register_device);
|
||||
|
||||
|
@ -103,7 +103,11 @@ struct subchannel {
|
||||
struct work_struct todo_work;
|
||||
struct schib_config config;
|
||||
u64 dma_mask;
|
||||
char *driver_override; /* Driver name to force a match */
|
||||
/*
|
||||
* Driver name to force a match. Do not set directly, because core
|
||||
* frees it. Use driver_set_override() to set or clear it.
|
||||
*/
|
||||
const char *driver_override;
|
||||
} __attribute__ ((aligned(8)));
|
||||
|
||||
DECLARE_PER_CPU_ALIGNED(struct irb, cio_irb);
|
||||
|
@ -338,31 +338,11 @@ static ssize_t driver_override_store(struct device *dev,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct subchannel *sch = to_subchannel(dev);
|
||||
char *driver_override, *old, *cp;
|
||||
int ret;
|
||||
|
||||
/* We need to keep extra room for a newline */
|
||||
if (count >= (PAGE_SIZE - 1))
|
||||
return -EINVAL;
|
||||
|
||||
driver_override = kstrndup(buf, count, GFP_KERNEL);
|
||||
if (!driver_override)
|
||||
return -ENOMEM;
|
||||
|
||||
cp = strchr(driver_override, '\n');
|
||||
if (cp)
|
||||
*cp = '\0';
|
||||
|
||||
device_lock(dev);
|
||||
old = sch->driver_override;
|
||||
if (strlen(driver_override)) {
|
||||
sch->driver_override = driver_override;
|
||||
} else {
|
||||
kfree(driver_override);
|
||||
sch->driver_override = NULL;
|
||||
}
|
||||
device_unlock(dev);
|
||||
|
||||
kfree(old);
|
||||
ret = driver_set_override(dev, &sch->driver_override, buf, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
@ -1434,6 +1434,7 @@ static int of_qcom_slim_ngd_register(struct device *parent,
|
||||
const struct of_device_id *match;
|
||||
struct device_node *node;
|
||||
u32 id;
|
||||
int ret;
|
||||
|
||||
match = of_match_node(qcom_slim_ngd_dt_match, parent->of_node);
|
||||
data = match->data;
|
||||
@ -1455,7 +1456,17 @@ static int of_qcom_slim_ngd_register(struct device *parent,
|
||||
}
|
||||
ngd->id = id;
|
||||
ngd->pdev->dev.parent = parent;
|
||||
ngd->pdev->driver_override = QCOM_SLIM_NGD_DRV_NAME;
|
||||
|
||||
ret = driver_set_override(&ngd->pdev->dev,
|
||||
&ngd->pdev->driver_override,
|
||||
QCOM_SLIM_NGD_DRV_NAME,
|
||||
strlen(QCOM_SLIM_NGD_DRV_NAME));
|
||||
if (ret) {
|
||||
platform_device_put(ngd->pdev);
|
||||
kfree(ngd);
|
||||
of_node_put(node);
|
||||
return ret;
|
||||
}
|
||||
ngd->pdev->dev.of_node = node;
|
||||
ctrl->ngd = ngd;
|
||||
|
||||
|
@ -71,29 +71,11 @@ static ssize_t driver_override_store(struct device *dev,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
const char *end = memchr(buf, '\n', count);
|
||||
const size_t len = end ? end - buf : count;
|
||||
const char *driver_override, *old;
|
||||
int ret;
|
||||
|
||||
/* We need to keep extra room for a newline when displaying value */
|
||||
if (len >= (PAGE_SIZE - 1))
|
||||
return -EINVAL;
|
||||
|
||||
driver_override = kstrndup(buf, len, GFP_KERNEL);
|
||||
if (!driver_override)
|
||||
return -ENOMEM;
|
||||
|
||||
device_lock(dev);
|
||||
old = spi->driver_override;
|
||||
if (len) {
|
||||
spi->driver_override = driver_override;
|
||||
} else {
|
||||
/* Empty string, disable driver override */
|
||||
spi->driver_override = NULL;
|
||||
kfree(driver_override);
|
||||
}
|
||||
device_unlock(dev);
|
||||
kfree(old);
|
||||
ret = driver_set_override(dev, &spi->driver_override, buf, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
@ -77,32 +77,11 @@ static ssize_t driver_override_store(struct device *dev,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct vdpa_device *vdev = dev_to_vdpa(dev);
|
||||
const char *driver_override, *old;
|
||||
char *cp;
|
||||
int ret;
|
||||
|
||||
/* We need to keep extra room for a newline */
|
||||
if (count >= (PAGE_SIZE - 1))
|
||||
return -EINVAL;
|
||||
|
||||
driver_override = kstrndup(buf, count, GFP_KERNEL);
|
||||
if (!driver_override)
|
||||
return -ENOMEM;
|
||||
|
||||
cp = strchr(driver_override, '\n');
|
||||
if (cp)
|
||||
*cp = '\0';
|
||||
|
||||
device_lock(dev);
|
||||
old = vdev->driver_override;
|
||||
if (strlen(driver_override)) {
|
||||
vdev->driver_override = driver_override;
|
||||
} else {
|
||||
kfree(driver_override);
|
||||
vdev->driver_override = NULL;
|
||||
}
|
||||
device_unlock(dev);
|
||||
|
||||
kfree(old);
|
||||
ret = driver_set_override(dev, &vdev->driver_override, buf, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
@ -18,7 +18,15 @@
|
||||
#include "kernfs-internal.h"
|
||||
|
||||
static DEFINE_SPINLOCK(kernfs_rename_lock); /* kn->parent and ->name */
|
||||
static char kernfs_pr_cont_buf[PATH_MAX]; /* protected by rename_lock */
|
||||
/*
|
||||
* Don't use rename_lock to piggy back on pr_cont_buf. We don't want to
|
||||
* call pr_cont() while holding rename_lock. Because sometimes pr_cont()
|
||||
* will perform wakeups when releasing console_sem. Holding rename_lock
|
||||
* will introduce deadlock if the scheduler reads the kernfs_name in the
|
||||
* wakeup path.
|
||||
*/
|
||||
static DEFINE_SPINLOCK(kernfs_pr_cont_lock);
|
||||
static char kernfs_pr_cont_buf[PATH_MAX]; /* protected by pr_cont_lock */
|
||||
static DEFINE_SPINLOCK(kernfs_idr_lock); /* root->ino_idr */
|
||||
|
||||
#define rb_to_kn(X) rb_entry((X), struct kernfs_node, rb)
|
||||
@ -229,12 +237,12 @@ void pr_cont_kernfs_name(struct kernfs_node *kn)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&kernfs_rename_lock, flags);
|
||||
spin_lock_irqsave(&kernfs_pr_cont_lock, flags);
|
||||
|
||||
kernfs_name_locked(kn, kernfs_pr_cont_buf, sizeof(kernfs_pr_cont_buf));
|
||||
kernfs_name(kn, kernfs_pr_cont_buf, sizeof(kernfs_pr_cont_buf));
|
||||
pr_cont("%s", kernfs_pr_cont_buf);
|
||||
|
||||
spin_unlock_irqrestore(&kernfs_rename_lock, flags);
|
||||
spin_unlock_irqrestore(&kernfs_pr_cont_lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -248,10 +256,10 @@ void pr_cont_kernfs_path(struct kernfs_node *kn)
|
||||
unsigned long flags;
|
||||
int sz;
|
||||
|
||||
spin_lock_irqsave(&kernfs_rename_lock, flags);
|
||||
spin_lock_irqsave(&kernfs_pr_cont_lock, flags);
|
||||
|
||||
sz = kernfs_path_from_node_locked(kn, NULL, kernfs_pr_cont_buf,
|
||||
sizeof(kernfs_pr_cont_buf));
|
||||
sz = kernfs_path_from_node(kn, NULL, kernfs_pr_cont_buf,
|
||||
sizeof(kernfs_pr_cont_buf));
|
||||
if (sz < 0) {
|
||||
pr_cont("(error)");
|
||||
goto out;
|
||||
@ -265,7 +273,7 @@ void pr_cont_kernfs_path(struct kernfs_node *kn)
|
||||
pr_cont("%s", kernfs_pr_cont_buf);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&kernfs_rename_lock, flags);
|
||||
spin_unlock_irqrestore(&kernfs_pr_cont_lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -823,13 +831,12 @@ static struct kernfs_node *kernfs_walk_ns(struct kernfs_node *parent,
|
||||
|
||||
lockdep_assert_held_read(&kernfs_root(parent)->kernfs_rwsem);
|
||||
|
||||
/* grab kernfs_rename_lock to piggy back on kernfs_pr_cont_buf */
|
||||
spin_lock_irq(&kernfs_rename_lock);
|
||||
spin_lock_irq(&kernfs_pr_cont_lock);
|
||||
|
||||
len = strlcpy(kernfs_pr_cont_buf, path, sizeof(kernfs_pr_cont_buf));
|
||||
|
||||
if (len >= sizeof(kernfs_pr_cont_buf)) {
|
||||
spin_unlock_irq(&kernfs_rename_lock);
|
||||
spin_unlock_irq(&kernfs_pr_cont_lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -841,7 +848,7 @@ static struct kernfs_node *kernfs_walk_ns(struct kernfs_node *parent,
|
||||
parent = kernfs_find_ns(parent, name, ns);
|
||||
}
|
||||
|
||||
spin_unlock_irq(&kernfs_rename_lock);
|
||||
spin_unlock_irq(&kernfs_pr_cont_lock);
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
@ -33,7 +33,6 @@ static DEFINE_SPINLOCK(kernfs_open_node_lock);
|
||||
static DEFINE_MUTEX(kernfs_open_file_mutex);
|
||||
|
||||
struct kernfs_open_node {
|
||||
atomic_t refcnt;
|
||||
atomic_t event;
|
||||
wait_queue_head_t poll;
|
||||
struct list_head files; /* goes through kernfs_open_file.list */
|
||||
@ -530,10 +529,8 @@ static int kernfs_get_open_node(struct kernfs_node *kn,
|
||||
}
|
||||
|
||||
on = kn->attr.open;
|
||||
if (on) {
|
||||
atomic_inc(&on->refcnt);
|
||||
if (on)
|
||||
list_add_tail(&of->list, &on->files);
|
||||
}
|
||||
|
||||
spin_unlock_irq(&kernfs_open_node_lock);
|
||||
mutex_unlock(&kernfs_open_file_mutex);
|
||||
@ -548,7 +545,6 @@ static int kernfs_get_open_node(struct kernfs_node *kn,
|
||||
if (!new_on)
|
||||
return -ENOMEM;
|
||||
|
||||
atomic_set(&new_on->refcnt, 0);
|
||||
atomic_set(&new_on->event, 1);
|
||||
init_waitqueue_head(&new_on->poll);
|
||||
INIT_LIST_HEAD(&new_on->files);
|
||||
@ -556,17 +552,19 @@ static int kernfs_get_open_node(struct kernfs_node *kn,
|
||||
}
|
||||
|
||||
/**
|
||||
* kernfs_put_open_node - put kernfs_open_node
|
||||
* @kn: target kernfs_nodet
|
||||
* kernfs_unlink_open_file - Unlink @of from @kn.
|
||||
*
|
||||
* @kn: target kernfs_node
|
||||
* @of: associated kernfs_open_file
|
||||
*
|
||||
* Put @kn->attr.open and unlink @of from the files list. If
|
||||
* reference count reaches zero, disassociate and free it.
|
||||
* Unlink @of from list of @kn's associated open files. If list of
|
||||
* associated open files becomes empty, disassociate and free
|
||||
* kernfs_open_node.
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
*/
|
||||
static void kernfs_put_open_node(struct kernfs_node *kn,
|
||||
static void kernfs_unlink_open_file(struct kernfs_node *kn,
|
||||
struct kernfs_open_file *of)
|
||||
{
|
||||
struct kernfs_open_node *on = kn->attr.open;
|
||||
@ -578,7 +576,7 @@ static void kernfs_put_open_node(struct kernfs_node *kn,
|
||||
if (of)
|
||||
list_del(&of->list);
|
||||
|
||||
if (atomic_dec_and_test(&on->refcnt))
|
||||
if (list_empty(&on->files))
|
||||
kn->attr.open = NULL;
|
||||
else
|
||||
on = NULL;
|
||||
@ -706,7 +704,7 @@ static int kernfs_fop_open(struct inode *inode, struct file *file)
|
||||
return 0;
|
||||
|
||||
err_put_node:
|
||||
kernfs_put_open_node(kn, of);
|
||||
kernfs_unlink_open_file(kn, of);
|
||||
err_seq_release:
|
||||
seq_release(inode, file);
|
||||
err_free:
|
||||
@ -752,7 +750,7 @@ static int kernfs_fop_release(struct inode *inode, struct file *filp)
|
||||
mutex_unlock(&kernfs_open_file_mutex);
|
||||
}
|
||||
|
||||
kernfs_put_open_node(kn, of);
|
||||
kernfs_unlink_open_file(kn, of);
|
||||
seq_release(inode, filp);
|
||||
kfree(of->prealloc_buf);
|
||||
kfree(of);
|
||||
@ -768,15 +766,24 @@ void kernfs_drain_open_files(struct kernfs_node *kn)
|
||||
if (!(kn->flags & (KERNFS_HAS_MMAP | KERNFS_HAS_RELEASE)))
|
||||
return;
|
||||
|
||||
spin_lock_irq(&kernfs_open_node_lock);
|
||||
on = kn->attr.open;
|
||||
if (on)
|
||||
atomic_inc(&on->refcnt);
|
||||
spin_unlock_irq(&kernfs_open_node_lock);
|
||||
if (!on)
|
||||
/*
|
||||
* lockless opportunistic check is safe below because no one is adding to
|
||||
* ->attr.open at this point of time. This check allows early bail out
|
||||
* if ->attr.open is already NULL. kernfs_unlink_open_file makes
|
||||
* ->attr.open NULL only while holding kernfs_open_file_mutex so below
|
||||
* check under kernfs_open_file_mutex will ensure bailing out if
|
||||
* ->attr.open became NULL while waiting for the mutex.
|
||||
*/
|
||||
if (!kn->attr.open)
|
||||
return;
|
||||
|
||||
mutex_lock(&kernfs_open_file_mutex);
|
||||
if (!kn->attr.open) {
|
||||
mutex_unlock(&kernfs_open_file_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
on = kn->attr.open;
|
||||
|
||||
list_for_each_entry(of, &on->files, list) {
|
||||
struct inode *inode = file_inode(of->file);
|
||||
@ -789,8 +796,6 @@ void kernfs_drain_open_files(struct kernfs_node *kn)
|
||||
}
|
||||
|
||||
mutex_unlock(&kernfs_open_file_mutex);
|
||||
|
||||
kernfs_put_open_node(kn, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -70,7 +70,11 @@ struct amba_device {
|
||||
unsigned int cid;
|
||||
struct amba_cs_uci_id uci;
|
||||
unsigned int irq[AMBA_NR_IRQS];
|
||||
char *driver_override;
|
||||
/*
|
||||
* Driver name to force a match. Do not set directly, because core
|
||||
* frees it. Use driver_set_override() to set or clear it.
|
||||
*/
|
||||
const char *driver_override;
|
||||
};
|
||||
|
||||
struct amba_driver {
|
||||
|
@ -386,6 +386,75 @@ struct dev_msi_info {
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* enum device_physical_location_panel - Describes which panel surface of the
|
||||
* system's housing the device connection point resides on.
|
||||
* @DEVICE_PANEL_TOP: Device connection point is on the top panel.
|
||||
* @DEVICE_PANEL_BOTTOM: Device connection point is on the bottom panel.
|
||||
* @DEVICE_PANEL_LEFT: Device connection point is on the left panel.
|
||||
* @DEVICE_PANEL_RIGHT: Device connection point is on the right panel.
|
||||
* @DEVICE_PANEL_FRONT: Device connection point is on the front panel.
|
||||
* @DEVICE_PANEL_BACK: Device connection point is on the back panel.
|
||||
* @DEVICE_PANEL_UNKNOWN: The panel with device connection point is unknown.
|
||||
*/
|
||||
enum device_physical_location_panel {
|
||||
DEVICE_PANEL_TOP,
|
||||
DEVICE_PANEL_BOTTOM,
|
||||
DEVICE_PANEL_LEFT,
|
||||
DEVICE_PANEL_RIGHT,
|
||||
DEVICE_PANEL_FRONT,
|
||||
DEVICE_PANEL_BACK,
|
||||
DEVICE_PANEL_UNKNOWN,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum device_physical_location_vertical_position - Describes vertical
|
||||
* position of the device connection point on the panel surface.
|
||||
* @DEVICE_VERT_POS_UPPER: Device connection point is at upper part of panel.
|
||||
* @DEVICE_VERT_POS_CENTER: Device connection point is at center part of panel.
|
||||
* @DEVICE_VERT_POS_LOWER: Device connection point is at lower part of panel.
|
||||
*/
|
||||
enum device_physical_location_vertical_position {
|
||||
DEVICE_VERT_POS_UPPER,
|
||||
DEVICE_VERT_POS_CENTER,
|
||||
DEVICE_VERT_POS_LOWER,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum device_physical_location_horizontal_position - Describes horizontal
|
||||
* position of the device connection point on the panel surface.
|
||||
* @DEVICE_HORI_POS_LEFT: Device connection point is at left part of panel.
|
||||
* @DEVICE_HORI_POS_CENTER: Device connection point is at center part of panel.
|
||||
* @DEVICE_HORI_POS_RIGHT: Device connection point is at right part of panel.
|
||||
*/
|
||||
enum device_physical_location_horizontal_position {
|
||||
DEVICE_HORI_POS_LEFT,
|
||||
DEVICE_HORI_POS_CENTER,
|
||||
DEVICE_HORI_POS_RIGHT,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct device_physical_location - Device data related to physical location
|
||||
* of the device connection point.
|
||||
* @panel: Panel surface of the system's housing that the device connection
|
||||
* point resides on.
|
||||
* @vertical_position: Vertical position of the device connection point within
|
||||
* the panel.
|
||||
* @horizontal_position: Horizontal position of the device connection point
|
||||
* within the panel.
|
||||
* @dock: Set if the device connection point resides in a docking station or
|
||||
* port replicator.
|
||||
* @lid: Set if this device connection point resides on the lid of laptop
|
||||
* system.
|
||||
*/
|
||||
struct device_physical_location {
|
||||
enum device_physical_location_panel panel;
|
||||
enum device_physical_location_vertical_position vertical_position;
|
||||
enum device_physical_location_horizontal_position horizontal_position;
|
||||
bool dock;
|
||||
bool lid;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct device - The basic device structure
|
||||
* @parent: The device's "parent" device, the device to which it is attached.
|
||||
@ -451,6 +520,8 @@ struct dev_msi_info {
|
||||
* device (i.e. the bus driver that discovered the device).
|
||||
* @iommu_group: IOMMU group the device belongs to.
|
||||
* @iommu: Per device generic IOMMU runtime data
|
||||
* @physical_location: Describes physical location of the device connection
|
||||
* point in the system housing.
|
||||
* @removable: Whether the device can be removed from the system. This
|
||||
* should be set by the subsystem / bus driver that discovered
|
||||
* the device.
|
||||
@ -562,6 +633,8 @@ struct device {
|
||||
struct iommu_group *iommu_group;
|
||||
struct dev_iommu *iommu;
|
||||
|
||||
struct device_physical_location *physical_location;
|
||||
|
||||
enum device_removable removable;
|
||||
|
||||
bool offline_disabled:1;
|
||||
|
@ -151,6 +151,8 @@ extern int __must_check driver_create_file(struct device_driver *driver,
|
||||
extern void driver_remove_file(struct device_driver *driver,
|
||||
const struct driver_attribute *attr);
|
||||
|
||||
int driver_set_override(struct device *dev, const char **override,
|
||||
const char *s, size_t len);
|
||||
extern int __must_check driver_for_each_device(struct device_driver *drv,
|
||||
struct device *start,
|
||||
void *data,
|
||||
|
@ -2,6 +2,8 @@
|
||||
#ifndef _LINUX_EXPORT_H
|
||||
#define _LINUX_EXPORT_H
|
||||
|
||||
#include <linux/stringify.h>
|
||||
|
||||
/*
|
||||
* Export symbols from the kernel to modules. Forked from module.h
|
||||
* to reduce the amount of pointless cruft we feed to gcc when only
|
||||
@ -140,7 +142,6 @@ struct kernel_symbol {
|
||||
#endif /* CONFIG_MODULES */
|
||||
|
||||
#ifdef DEFAULT_SYMBOL_NAMESPACE
|
||||
#include <linux/stringify.h>
|
||||
#define _EXPORT_SYMBOL(sym, sec) __EXPORT_SYMBOL(sym, sec, __stringify(DEFAULT_SYMBOL_NAMESPACE))
|
||||
#else
|
||||
#define _EXPORT_SYMBOL(sym, sec) __EXPORT_SYMBOL(sym, sec, "")
|
||||
@ -148,8 +149,8 @@ struct kernel_symbol {
|
||||
|
||||
#define EXPORT_SYMBOL(sym) _EXPORT_SYMBOL(sym, "")
|
||||
#define EXPORT_SYMBOL_GPL(sym) _EXPORT_SYMBOL(sym, "_gpl")
|
||||
#define EXPORT_SYMBOL_NS(sym, ns) __EXPORT_SYMBOL(sym, "", #ns)
|
||||
#define EXPORT_SYMBOL_NS_GPL(sym, ns) __EXPORT_SYMBOL(sym, "_gpl", #ns)
|
||||
#define EXPORT_SYMBOL_NS(sym, ns) __EXPORT_SYMBOL(sym, "", __stringify(ns))
|
||||
#define EXPORT_SYMBOL_NS_GPL(sym, ns) __EXPORT_SYMBOL(sym, "_gpl", __stringify(ns))
|
||||
|
||||
#endif /* !__ASSEMBLY__ */
|
||||
|
||||
|
@ -17,6 +17,64 @@ struct firmware {
|
||||
void *priv;
|
||||
};
|
||||
|
||||
/**
|
||||
* enum fw_upload_err - firmware upload error codes
|
||||
* @FW_UPLOAD_ERR_NONE: returned to indicate success
|
||||
* @FW_UPLOAD_ERR_HW_ERROR: error signalled by hardware, see kernel log
|
||||
* @FW_UPLOAD_ERR_TIMEOUT: SW timed out on handshake with HW/firmware
|
||||
* @FW_UPLOAD_ERR_CANCELED: upload was cancelled by the user
|
||||
* @FW_UPLOAD_ERR_BUSY: there is an upload operation already in progress
|
||||
* @FW_UPLOAD_ERR_INVALID_SIZE: invalid firmware image size
|
||||
* @FW_UPLOAD_ERR_RW_ERROR: read or write to HW failed, see kernel log
|
||||
* @FW_UPLOAD_ERR_WEAROUT: FLASH device is approaching wear-out, wait & retry
|
||||
* @FW_UPLOAD_ERR_MAX: Maximum error code marker
|
||||
*/
|
||||
enum fw_upload_err {
|
||||
FW_UPLOAD_ERR_NONE,
|
||||
FW_UPLOAD_ERR_HW_ERROR,
|
||||
FW_UPLOAD_ERR_TIMEOUT,
|
||||
FW_UPLOAD_ERR_CANCELED,
|
||||
FW_UPLOAD_ERR_BUSY,
|
||||
FW_UPLOAD_ERR_INVALID_SIZE,
|
||||
FW_UPLOAD_ERR_RW_ERROR,
|
||||
FW_UPLOAD_ERR_WEAROUT,
|
||||
FW_UPLOAD_ERR_MAX
|
||||
};
|
||||
|
||||
struct fw_upload {
|
||||
void *dd_handle; /* reference to parent driver */
|
||||
void *priv; /* firmware loader private fields */
|
||||
};
|
||||
|
||||
/**
|
||||
* struct fw_upload_ops - device specific operations to support firmware upload
|
||||
* @prepare: Required: Prepare secure update
|
||||
* @write: Required: The write() op receives the remaining
|
||||
* size to be written and must return the actual
|
||||
* size written or a negative error code. The write()
|
||||
* op will be called repeatedly until all data is
|
||||
* written.
|
||||
* @poll_complete: Required: Check for the completion of the
|
||||
* HW authentication/programming process.
|
||||
* @cancel: Required: Request cancellation of update. This op
|
||||
* is called from the context of a different kernel
|
||||
* thread, so race conditions need to be considered.
|
||||
* @cleanup: Optional: Complements the prepare()
|
||||
* function and is called at the completion
|
||||
* of the update, on success or failure, if the
|
||||
* prepare function succeeded.
|
||||
*/
|
||||
struct fw_upload_ops {
|
||||
enum fw_upload_err (*prepare)(struct fw_upload *fw_upload,
|
||||
const u8 *data, u32 size);
|
||||
enum fw_upload_err (*write)(struct fw_upload *fw_upload,
|
||||
const u8 *data, u32 offset,
|
||||
u32 size, u32 *written);
|
||||
enum fw_upload_err (*poll_complete)(struct fw_upload *fw_upload);
|
||||
void (*cancel)(struct fw_upload *fw_upload);
|
||||
void (*cleanup)(struct fw_upload *fw_upload);
|
||||
};
|
||||
|
||||
struct module;
|
||||
struct device;
|
||||
|
||||
@ -112,6 +170,30 @@ static inline int request_partial_firmware_into_buf
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FW_UPLOAD
|
||||
|
||||
struct fw_upload *
|
||||
firmware_upload_register(struct module *module, struct device *parent,
|
||||
const char *name, const struct fw_upload_ops *ops,
|
||||
void *dd_handle);
|
||||
void firmware_upload_unregister(struct fw_upload *fw_upload);
|
||||
|
||||
#else
|
||||
|
||||
static inline struct fw_upload *
|
||||
firmware_upload_register(struct module *module, struct device *parent,
|
||||
const char *name, const struct fw_upload_ops *ops,
|
||||
void *dd_handle)
|
||||
{
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
static inline void firmware_upload_unregister(struct fw_upload *fw_upload)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int firmware_request_cache(struct device *device, const char *name);
|
||||
|
||||
#endif
|
||||
|
@ -178,7 +178,9 @@ struct fsl_mc_obj_desc {
|
||||
* @regions: pointer to array of MMIO region entries
|
||||
* @irqs: pointer to array of pointers to interrupts allocated to this device
|
||||
* @resource: generic resource associated with this MC object device, if any.
|
||||
* @driver_override: driver name to force a match
|
||||
* @driver_override: driver name to force a match; do not set directly,
|
||||
* because core frees it; use driver_set_override() to
|
||||
* set or clear it.
|
||||
*
|
||||
* Generic device object for MC object devices that are "attached" to a
|
||||
* MC bus.
|
||||
@ -212,7 +214,7 @@ struct fsl_mc_device {
|
||||
struct fsl_mc_device_irq **irqs;
|
||||
struct fsl_mc_resource *resource;
|
||||
struct device_link *consumer_link;
|
||||
char *driver_override;
|
||||
const char *driver_override;
|
||||
};
|
||||
|
||||
#define to_fsl_mc_device(_dev) \
|
||||
|
@ -1292,7 +1292,11 @@ struct hv_device {
|
||||
u16 device_id;
|
||||
|
||||
struct device device;
|
||||
char *driver_override; /* Driver name to force a match */
|
||||
/*
|
||||
* Driver name to force a match. Do not set directly, because core
|
||||
* frees it. Use driver_set_override() to set or clear it.
|
||||
*/
|
||||
const char *driver_override;
|
||||
|
||||
struct vmbus_channel *channel;
|
||||
struct kset *channels_kset;
|
||||
|
@ -512,7 +512,11 @@ struct pci_dev {
|
||||
u16 acs_cap; /* ACS Capability offset */
|
||||
phys_addr_t rom; /* Physical address if not from BAR */
|
||||
size_t romlen; /* Length if not from BAR */
|
||||
char *driver_override; /* Driver name to force a match */
|
||||
/*
|
||||
* Driver name to force a match. Do not set directly, because core
|
||||
* frees it. Use driver_set_override() to set or clear it.
|
||||
*/
|
||||
const char *driver_override;
|
||||
|
||||
unsigned long priv_flags; /* Private flags for the PCI driver */
|
||||
|
||||
|
@ -31,7 +31,11 @@ struct platform_device {
|
||||
struct resource *resource;
|
||||
|
||||
const struct platform_device_id *id_entry;
|
||||
char *driver_override; /* Driver name to force a match */
|
||||
/*
|
||||
* Driver name to force a match. Do not set directly, because core
|
||||
* frees it. Use driver_set_override() to set or clear it.
|
||||
*/
|
||||
const char *driver_override;
|
||||
|
||||
/* MFD cell pointer */
|
||||
struct mfd_cell *mfd_cell;
|
||||
|
@ -41,7 +41,9 @@ struct rpmsg_channel_info {
|
||||
* rpmsg_device - device that belong to the rpmsg bus
|
||||
* @dev: the device struct
|
||||
* @id: device id (used to match between rpmsg drivers and devices)
|
||||
* @driver_override: driver name to force a match
|
||||
* @driver_override: driver name to force a match; do not set directly,
|
||||
* because core frees it; use driver_set_override() to
|
||||
* set or clear it.
|
||||
* @src: local address
|
||||
* @dst: destination address
|
||||
* @ept: the rpmsg endpoint of this channel
|
||||
@ -51,7 +53,7 @@ struct rpmsg_channel_info {
|
||||
struct rpmsg_device {
|
||||
struct device dev;
|
||||
struct rpmsg_device_id id;
|
||||
char *driver_override;
|
||||
const char *driver_override;
|
||||
u32 src;
|
||||
u32 dst;
|
||||
struct rpmsg_endpoint *ept;
|
||||
@ -163,6 +165,8 @@ static inline __rpmsg64 cpu_to_rpmsg64(struct rpmsg_device *rpdev, u64 val)
|
||||
|
||||
#if IS_ENABLED(CONFIG_RPMSG)
|
||||
|
||||
int rpmsg_register_device_override(struct rpmsg_device *rpdev,
|
||||
const char *driver_override);
|
||||
int rpmsg_register_device(struct rpmsg_device *rpdev);
|
||||
int rpmsg_unregister_device(struct device *parent,
|
||||
struct rpmsg_channel_info *chinfo);
|
||||
@ -190,6 +194,12 @@ ssize_t rpmsg_get_mtu(struct rpmsg_endpoint *ept);
|
||||
|
||||
#else
|
||||
|
||||
static inline int rpmsg_register_device_override(struct rpmsg_device *rpdev,
|
||||
const char *driver_override)
|
||||
{
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static inline int rpmsg_register_device(struct rpmsg_device *rpdev)
|
||||
{
|
||||
return -ENXIO;
|
||||
|
@ -138,6 +138,8 @@ extern int spi_delay_exec(struct spi_delay *_delay, struct spi_transfer *xfer);
|
||||
* for driver coldplugging, and in uevents used for hotplugging
|
||||
* @driver_override: If the name of a driver is written to this attribute, then
|
||||
* the device will bind to the named driver and only the named driver.
|
||||
* Do not set directly, because core frees it; use driver_set_override() to
|
||||
* set or clear it.
|
||||
* @cs_gpiod: gpio descriptor of the chipselect line (optional, NULL when
|
||||
* not using a GPIO line)
|
||||
* @word_delay: delay to be inserted between consecutive
|
||||
|
@ -64,7 +64,9 @@ struct vdpa_mgmt_dev;
|
||||
* struct vdpa_device - representation of a vDPA device
|
||||
* @dev: underlying device
|
||||
* @dma_dev: the actual device that is performing DMA
|
||||
* @driver_override: driver name to force a match
|
||||
* @driver_override: driver name to force a match; do not set directly,
|
||||
* because core frees it; use driver_set_override() to
|
||||
* set or clear it.
|
||||
* @config: the configuration ops for this device.
|
||||
* @cf_lock: Protects get and set access to configuration layout.
|
||||
* @index: device index
|
||||
|
29
include/trace/events/thermal_pressure.h
Normal file
29
include/trace/events/thermal_pressure.h
Normal file
@ -0,0 +1,29 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM thermal_pressure
|
||||
|
||||
#if !defined(_TRACE_THERMAL_PRESSURE_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define _TRACE_THERMAL_PRESSURE_H
|
||||
|
||||
#include <linux/tracepoint.h>
|
||||
|
||||
TRACE_EVENT(thermal_pressure_update,
|
||||
TP_PROTO(int cpu, unsigned long thermal_pressure),
|
||||
TP_ARGS(cpu, thermal_pressure),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__field(unsigned long, thermal_pressure)
|
||||
__field(int, cpu)
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
__entry->thermal_pressure = thermal_pressure;
|
||||
__entry->cpu = cpu;
|
||||
),
|
||||
|
||||
TP_printk("cpu=%d thermal_pressure=%lu", __entry->cpu, __entry->thermal_pressure)
|
||||
);
|
||||
#endif /* _TRACE_THERMAL_PRESSURE_H */
|
||||
|
||||
/* This part must be outside protection */
|
||||
#include <trace/define_trace.h>
|
@ -31,9 +31,12 @@ MODULE_IMPORT_NS(TEST_FIRMWARE);
|
||||
#define TEST_FIRMWARE_NAME "test-firmware.bin"
|
||||
#define TEST_FIRMWARE_NUM_REQS 4
|
||||
#define TEST_FIRMWARE_BUF_SIZE SZ_1K
|
||||
#define TEST_UPLOAD_MAX_SIZE SZ_2K
|
||||
#define TEST_UPLOAD_BLK_SIZE 37 /* Avoid powers of two in testing */
|
||||
|
||||
static DEFINE_MUTEX(test_fw_mutex);
|
||||
static const struct firmware *test_firmware;
|
||||
static LIST_HEAD(test_upload_list);
|
||||
|
||||
struct test_batched_req {
|
||||
u8 idx;
|
||||
@ -63,6 +66,7 @@ struct test_batched_req {
|
||||
* @reqs: stores all requests information
|
||||
* @read_fw_idx: index of thread from which we want to read firmware results
|
||||
* from through the read_fw trigger.
|
||||
* @upload_name: firmware name to be used with upload_read sysfs node
|
||||
* @test_result: a test may use this to collect the result from the call
|
||||
* of the request_firmware*() calls used in their tests. In order of
|
||||
* priority we always keep first any setup error. If no setup errors were
|
||||
@ -101,6 +105,7 @@ struct test_config {
|
||||
bool send_uevent;
|
||||
u8 num_requests;
|
||||
u8 read_fw_idx;
|
||||
char *upload_name;
|
||||
|
||||
/*
|
||||
* These below don't belong her but we'll move them once we create
|
||||
@ -112,8 +117,34 @@ struct test_config {
|
||||
struct device *device);
|
||||
};
|
||||
|
||||
struct upload_inject_err {
|
||||
const char *prog;
|
||||
enum fw_upload_err err_code;
|
||||
};
|
||||
|
||||
struct test_firmware_upload {
|
||||
char *name;
|
||||
struct list_head node;
|
||||
char *buf;
|
||||
size_t size;
|
||||
bool cancel_request;
|
||||
struct upload_inject_err inject;
|
||||
struct fw_upload *fwl;
|
||||
};
|
||||
|
||||
static struct test_config *test_fw_config;
|
||||
|
||||
static struct test_firmware_upload *upload_lookup_name(const char *name)
|
||||
{
|
||||
struct test_firmware_upload *tst;
|
||||
|
||||
list_for_each_entry(tst, &test_upload_list, node)
|
||||
if (strncmp(name, tst->name, strlen(tst->name)) == 0)
|
||||
return tst;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t test_fw_misc_read(struct file *f, char __user *buf,
|
||||
size_t size, loff_t *offset)
|
||||
{
|
||||
@ -198,6 +229,7 @@ static int __test_firmware_config_init(void)
|
||||
test_fw_config->req_firmware = request_firmware;
|
||||
test_fw_config->test_result = 0;
|
||||
test_fw_config->reqs = NULL;
|
||||
test_fw_config->upload_name = NULL;
|
||||
|
||||
return 0;
|
||||
|
||||
@ -277,6 +309,13 @@ static ssize_t config_show(struct device *dev,
|
||||
test_fw_config->sync_direct ? "true" : "false");
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len,
|
||||
"read_fw_idx:\t%u\n", test_fw_config->read_fw_idx);
|
||||
if (test_fw_config->upload_name)
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len,
|
||||
"upload_name:\t%s\n",
|
||||
test_fw_config->upload_name);
|
||||
else
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len,
|
||||
"upload_name:\tEMTPY\n");
|
||||
|
||||
mutex_unlock(&test_fw_mutex);
|
||||
|
||||
@ -392,6 +431,32 @@ static ssize_t config_name_show(struct device *dev,
|
||||
}
|
||||
static DEVICE_ATTR_RW(config_name);
|
||||
|
||||
static ssize_t config_upload_name_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct test_firmware_upload *tst;
|
||||
int ret = count;
|
||||
|
||||
mutex_lock(&test_fw_mutex);
|
||||
tst = upload_lookup_name(buf);
|
||||
if (tst)
|
||||
test_fw_config->upload_name = tst->name;
|
||||
else
|
||||
ret = -EINVAL;
|
||||
mutex_unlock(&test_fw_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t config_upload_name_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return config_test_show_str(buf, test_fw_config->upload_name);
|
||||
}
|
||||
static DEVICE_ATTR_RW(config_upload_name);
|
||||
|
||||
static ssize_t config_num_requests_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
@ -989,6 +1054,278 @@ ssize_t trigger_batched_requests_async_store(struct device *dev,
|
||||
}
|
||||
static DEVICE_ATTR_WO(trigger_batched_requests_async);
|
||||
|
||||
static void upload_release(struct test_firmware_upload *tst)
|
||||
{
|
||||
firmware_upload_unregister(tst->fwl);
|
||||
kfree(tst->buf);
|
||||
kfree(tst->name);
|
||||
kfree(tst);
|
||||
}
|
||||
|
||||
static void upload_release_all(void)
|
||||
{
|
||||
struct test_firmware_upload *tst, *tmp;
|
||||
|
||||
list_for_each_entry_safe(tst, tmp, &test_upload_list, node) {
|
||||
list_del(&tst->node);
|
||||
upload_release(tst);
|
||||
}
|
||||
test_fw_config->upload_name = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* This table is replicated from .../firmware_loader/sysfs_upload.c
|
||||
* and needs to be kept in sync.
|
||||
*/
|
||||
static const char * const fw_upload_err_str[] = {
|
||||
[FW_UPLOAD_ERR_NONE] = "none",
|
||||
[FW_UPLOAD_ERR_HW_ERROR] = "hw-error",
|
||||
[FW_UPLOAD_ERR_TIMEOUT] = "timeout",
|
||||
[FW_UPLOAD_ERR_CANCELED] = "user-abort",
|
||||
[FW_UPLOAD_ERR_BUSY] = "device-busy",
|
||||
[FW_UPLOAD_ERR_INVALID_SIZE] = "invalid-file-size",
|
||||
[FW_UPLOAD_ERR_RW_ERROR] = "read-write-error",
|
||||
[FW_UPLOAD_ERR_WEAROUT] = "flash-wearout",
|
||||
};
|
||||
|
||||
static void upload_err_inject_error(struct test_firmware_upload *tst,
|
||||
const u8 *p, const char *prog)
|
||||
{
|
||||
enum fw_upload_err err;
|
||||
|
||||
for (err = FW_UPLOAD_ERR_NONE + 1; err < FW_UPLOAD_ERR_MAX; err++) {
|
||||
if (strncmp(p, fw_upload_err_str[err],
|
||||
strlen(fw_upload_err_str[err])) == 0) {
|
||||
tst->inject.prog = prog;
|
||||
tst->inject.err_code = err;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void upload_err_inject_prog(struct test_firmware_upload *tst,
|
||||
const u8 *p)
|
||||
{
|
||||
static const char * const progs[] = {
|
||||
"preparing:", "transferring:", "programming:"
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(progs); i++) {
|
||||
if (strncmp(p, progs[i], strlen(progs[i])) == 0) {
|
||||
upload_err_inject_error(tst, p + strlen(progs[i]),
|
||||
progs[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define FIVE_MINUTES_MS (5 * 60 * 1000)
|
||||
static enum fw_upload_err
|
||||
fw_upload_wait_on_cancel(struct test_firmware_upload *tst)
|
||||
{
|
||||
int ms_delay;
|
||||
|
||||
for (ms_delay = 0; ms_delay < FIVE_MINUTES_MS; ms_delay += 100) {
|
||||
msleep(100);
|
||||
if (tst->cancel_request)
|
||||
return FW_UPLOAD_ERR_CANCELED;
|
||||
}
|
||||
return FW_UPLOAD_ERR_NONE;
|
||||
}
|
||||
|
||||
static enum fw_upload_err test_fw_upload_prepare(struct fw_upload *fwl,
|
||||
const u8 *data, u32 size)
|
||||
{
|
||||
struct test_firmware_upload *tst = fwl->dd_handle;
|
||||
enum fw_upload_err ret = FW_UPLOAD_ERR_NONE;
|
||||
const char *progress = "preparing:";
|
||||
|
||||
tst->cancel_request = false;
|
||||
|
||||
if (!size || size > TEST_UPLOAD_MAX_SIZE) {
|
||||
ret = FW_UPLOAD_ERR_INVALID_SIZE;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (strncmp(data, "inject:", strlen("inject:")) == 0)
|
||||
upload_err_inject_prog(tst, data + strlen("inject:"));
|
||||
|
||||
memset(tst->buf, 0, TEST_UPLOAD_MAX_SIZE);
|
||||
tst->size = size;
|
||||
|
||||
if (tst->inject.err_code == FW_UPLOAD_ERR_NONE ||
|
||||
strncmp(tst->inject.prog, progress, strlen(progress)) != 0)
|
||||
return FW_UPLOAD_ERR_NONE;
|
||||
|
||||
if (tst->inject.err_code == FW_UPLOAD_ERR_CANCELED)
|
||||
ret = fw_upload_wait_on_cancel(tst);
|
||||
else
|
||||
ret = tst->inject.err_code;
|
||||
|
||||
err_out:
|
||||
/*
|
||||
* The cleanup op only executes if the prepare op succeeds.
|
||||
* If the prepare op fails, it must do it's own clean-up.
|
||||
*/
|
||||
tst->inject.err_code = FW_UPLOAD_ERR_NONE;
|
||||
tst->inject.prog = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum fw_upload_err test_fw_upload_write(struct fw_upload *fwl,
|
||||
const u8 *data, u32 offset,
|
||||
u32 size, u32 *written)
|
||||
{
|
||||
struct test_firmware_upload *tst = fwl->dd_handle;
|
||||
const char *progress = "transferring:";
|
||||
u32 blk_size;
|
||||
|
||||
if (tst->cancel_request)
|
||||
return FW_UPLOAD_ERR_CANCELED;
|
||||
|
||||
blk_size = min_t(u32, TEST_UPLOAD_BLK_SIZE, size);
|
||||
memcpy(tst->buf + offset, data + offset, blk_size);
|
||||
|
||||
*written = blk_size;
|
||||
|
||||
if (tst->inject.err_code == FW_UPLOAD_ERR_NONE ||
|
||||
strncmp(tst->inject.prog, progress, strlen(progress)) != 0)
|
||||
return FW_UPLOAD_ERR_NONE;
|
||||
|
||||
if (tst->inject.err_code == FW_UPLOAD_ERR_CANCELED)
|
||||
return fw_upload_wait_on_cancel(tst);
|
||||
|
||||
return tst->inject.err_code;
|
||||
}
|
||||
|
||||
static enum fw_upload_err test_fw_upload_complete(struct fw_upload *fwl)
|
||||
{
|
||||
struct test_firmware_upload *tst = fwl->dd_handle;
|
||||
const char *progress = "programming:";
|
||||
|
||||
if (tst->cancel_request)
|
||||
return FW_UPLOAD_ERR_CANCELED;
|
||||
|
||||
if (tst->inject.err_code == FW_UPLOAD_ERR_NONE ||
|
||||
strncmp(tst->inject.prog, progress, strlen(progress)) != 0)
|
||||
return FW_UPLOAD_ERR_NONE;
|
||||
|
||||
if (tst->inject.err_code == FW_UPLOAD_ERR_CANCELED)
|
||||
return fw_upload_wait_on_cancel(tst);
|
||||
|
||||
return tst->inject.err_code;
|
||||
}
|
||||
|
||||
static void test_fw_upload_cancel(struct fw_upload *fwl)
|
||||
{
|
||||
struct test_firmware_upload *tst = fwl->dd_handle;
|
||||
|
||||
tst->cancel_request = true;
|
||||
}
|
||||
|
||||
static void test_fw_cleanup(struct fw_upload *fwl)
|
||||
{
|
||||
struct test_firmware_upload *tst = fwl->dd_handle;
|
||||
|
||||
tst->inject.err_code = FW_UPLOAD_ERR_NONE;
|
||||
tst->inject.prog = NULL;
|
||||
}
|
||||
|
||||
static const struct fw_upload_ops upload_test_ops = {
|
||||
.prepare = test_fw_upload_prepare,
|
||||
.write = test_fw_upload_write,
|
||||
.poll_complete = test_fw_upload_complete,
|
||||
.cancel = test_fw_upload_cancel,
|
||||
.cleanup = test_fw_cleanup
|
||||
};
|
||||
|
||||
static ssize_t upload_register_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct test_firmware_upload *tst;
|
||||
struct fw_upload *fwl;
|
||||
char *name;
|
||||
int ret;
|
||||
|
||||
name = kstrndup(buf, count, GFP_KERNEL);
|
||||
if (!name)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_lock(&test_fw_mutex);
|
||||
tst = upload_lookup_name(name);
|
||||
if (tst) {
|
||||
ret = -EEXIST;
|
||||
goto free_name;
|
||||
}
|
||||
|
||||
tst = kzalloc(sizeof(*tst), GFP_KERNEL);
|
||||
if (!tst) {
|
||||
ret = -ENOMEM;
|
||||
goto free_name;
|
||||
}
|
||||
|
||||
tst->name = name;
|
||||
tst->buf = kzalloc(TEST_UPLOAD_MAX_SIZE, GFP_KERNEL);
|
||||
if (!tst->buf) {
|
||||
ret = -ENOMEM;
|
||||
goto free_tst;
|
||||
}
|
||||
|
||||
fwl = firmware_upload_register(THIS_MODULE, dev, tst->name,
|
||||
&upload_test_ops, tst);
|
||||
if (IS_ERR(fwl)) {
|
||||
ret = PTR_ERR(fwl);
|
||||
goto free_buf;
|
||||
}
|
||||
|
||||
tst->fwl = fwl;
|
||||
list_add_tail(&tst->node, &test_upload_list);
|
||||
mutex_unlock(&test_fw_mutex);
|
||||
return count;
|
||||
|
||||
free_buf:
|
||||
kfree(tst->buf);
|
||||
|
||||
free_tst:
|
||||
kfree(tst);
|
||||
|
||||
free_name:
|
||||
mutex_unlock(&test_fw_mutex);
|
||||
kfree(name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
static DEVICE_ATTR_WO(upload_register);
|
||||
|
||||
static ssize_t upload_unregister_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct test_firmware_upload *tst;
|
||||
int ret = count;
|
||||
|
||||
mutex_lock(&test_fw_mutex);
|
||||
tst = upload_lookup_name(buf);
|
||||
if (!tst) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (test_fw_config->upload_name == tst->name)
|
||||
test_fw_config->upload_name = NULL;
|
||||
|
||||
list_del(&tst->node);
|
||||
upload_release(tst);
|
||||
|
||||
out:
|
||||
mutex_unlock(&test_fw_mutex);
|
||||
return ret;
|
||||
}
|
||||
static DEVICE_ATTR_WO(upload_unregister);
|
||||
|
||||
static ssize_t test_result_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
@ -1051,6 +1388,45 @@ static ssize_t read_firmware_show(struct device *dev,
|
||||
}
|
||||
static DEVICE_ATTR_RO(read_firmware);
|
||||
|
||||
static ssize_t upload_read_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct test_firmware_upload *tst = NULL;
|
||||
struct test_firmware_upload *tst_iter;
|
||||
int ret = -EINVAL;
|
||||
|
||||
if (!test_fw_config->upload_name) {
|
||||
pr_err("Set config_upload_name before using upload_read\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&test_fw_mutex);
|
||||
list_for_each_entry(tst_iter, &test_upload_list, node)
|
||||
if (tst_iter->name == test_fw_config->upload_name) {
|
||||
tst = tst_iter;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!tst) {
|
||||
pr_err("Firmware name not found: %s\n",
|
||||
test_fw_config->upload_name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (tst->size > PAGE_SIZE) {
|
||||
pr_err("Testing interface must use PAGE_SIZE firmware for now\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
memcpy(buf, tst->buf, tst->size);
|
||||
ret = tst->size;
|
||||
out:
|
||||
mutex_unlock(&test_fw_mutex);
|
||||
return ret;
|
||||
}
|
||||
static DEVICE_ATTR_RO(upload_read);
|
||||
|
||||
#define TEST_FW_DEV_ATTR(name) &dev_attr_##name.attr
|
||||
|
||||
static struct attribute *test_dev_attrs[] = {
|
||||
@ -1066,6 +1442,7 @@ static struct attribute *test_dev_attrs[] = {
|
||||
TEST_FW_DEV_ATTR(config_sync_direct),
|
||||
TEST_FW_DEV_ATTR(config_send_uevent),
|
||||
TEST_FW_DEV_ATTR(config_read_fw_idx),
|
||||
TEST_FW_DEV_ATTR(config_upload_name),
|
||||
|
||||
/* These don't use the config at all - they could be ported! */
|
||||
TEST_FW_DEV_ATTR(trigger_request),
|
||||
@ -1082,6 +1459,9 @@ static struct attribute *test_dev_attrs[] = {
|
||||
TEST_FW_DEV_ATTR(release_all_firmware),
|
||||
TEST_FW_DEV_ATTR(test_result),
|
||||
TEST_FW_DEV_ATTR(read_firmware),
|
||||
TEST_FW_DEV_ATTR(upload_read),
|
||||
TEST_FW_DEV_ATTR(upload_register),
|
||||
TEST_FW_DEV_ATTR(upload_unregister),
|
||||
NULL,
|
||||
};
|
||||
|
||||
@ -1128,6 +1508,7 @@ static void __exit test_firmware_exit(void)
|
||||
mutex_lock(&test_fw_mutex);
|
||||
release_firmware(test_firmware);
|
||||
misc_deregister(&test_fw_misc_device);
|
||||
upload_release_all();
|
||||
__test_firmware_config_free();
|
||||
kfree(test_fw_config);
|
||||
mutex_unlock(&test_fw_mutex);
|
||||
|
@ -981,11 +981,11 @@ __END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
abi_book.pl - parse the Linux ABI files and produce a ReST book.
|
||||
get_abi.pl - parse the Linux ABI files and produce a ReST book.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<abi_book.pl> [--debug <level>] [--enable-lineno] [--man] [--help]
|
||||
B<get_abi.pl> [--debug <level>] [--enable-lineno] [--man] [--help]
|
||||
[--(no-)rst-source] [--dir=<dir>] [--show-hints]
|
||||
[--search-string <regex>]
|
||||
<COMMAND> [<ARGUMENT>]
|
||||
|
@ -4,7 +4,7 @@ CFLAGS = -Wall \
|
||||
-O2
|
||||
|
||||
TEST_PROGS := fw_run_tests.sh
|
||||
TEST_FILES := fw_fallback.sh fw_filesystem.sh fw_lib.sh
|
||||
TEST_FILES := fw_fallback.sh fw_filesystem.sh fw_upload.sh fw_lib.sh
|
||||
TEST_GEN_FILES := fw_namespace
|
||||
|
||||
include ../lib.mk
|
||||
|
@ -3,3 +3,4 @@ CONFIG_FW_LOADER=y
|
||||
CONFIG_FW_LOADER_USER_HELPER=y
|
||||
CONFIG_IKCONFIG=y
|
||||
CONFIG_IKCONFIG_PROC=y
|
||||
CONFIG_FW_UPLOAD=y
|
||||
|
@ -11,6 +11,9 @@ TEST_REQS_FW_SET_CUSTOM_PATH="yes"
|
||||
TEST_DIR=$(dirname $0)
|
||||
source $TEST_DIR/fw_lib.sh
|
||||
|
||||
RUN_XZ="xz -C crc32 --lzma2=dict=2MiB"
|
||||
RUN_ZSTD="zstd -q"
|
||||
|
||||
check_mods
|
||||
check_setup
|
||||
verify_reqs
|
||||
@ -211,7 +214,7 @@ read_firmwares()
|
||||
else
|
||||
fwfile="$FW"
|
||||
fi
|
||||
if [ "$1" = "xzonly" ]; then
|
||||
if [ "$1" = "componly" ]; then
|
||||
fwfile="${fwfile}-orig"
|
||||
fi
|
||||
for i in $(seq 0 3); do
|
||||
@ -235,7 +238,7 @@ read_partial_firmwares()
|
||||
fwfile="${FW}"
|
||||
fi
|
||||
|
||||
if [ "$1" = "xzonly" ]; then
|
||||
if [ "$1" = "componly" ]; then
|
||||
fwfile="${fwfile}-orig"
|
||||
fi
|
||||
|
||||
@ -409,10 +412,8 @@ test_request_firmware_nowait_custom()
|
||||
config_unset_uevent
|
||||
RANDOM_FILE_PATH=$(setup_random_file)
|
||||
RANDOM_FILE="$(basename $RANDOM_FILE_PATH)"
|
||||
if [ "$2" = "both" ]; then
|
||||
xz -9 -C crc32 -k $RANDOM_FILE_PATH
|
||||
elif [ "$2" = "xzonly" ]; then
|
||||
xz -9 -C crc32 $RANDOM_FILE_PATH
|
||||
if [ -n "$2" -a "$2" != "normal" ]; then
|
||||
compress_"$2"_"$COMPRESS_FORMAT" $RANDOM_FILE_PATH
|
||||
fi
|
||||
config_set_name $RANDOM_FILE
|
||||
config_trigger_async
|
||||
@ -435,6 +436,32 @@ test_request_partial_firmware_into_buf()
|
||||
echo "OK"
|
||||
}
|
||||
|
||||
do_tests ()
|
||||
{
|
||||
mode="$1"
|
||||
suffix="$2"
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware$suffix $i $mode
|
||||
done
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware_into_buf$suffix $i $mode
|
||||
done
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware_direct$suffix $i $mode
|
||||
done
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_request_firmware_nowait_uevent$suffix $i $mode
|
||||
done
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_request_firmware_nowait_custom$suffix $i $mode
|
||||
done
|
||||
}
|
||||
|
||||
# Only continue if batched request triggers are present on the
|
||||
# test-firmware driver
|
||||
test_config_present
|
||||
@ -442,25 +469,7 @@ test_config_present
|
||||
# test with the file present
|
||||
echo
|
||||
echo "Testing with the file present..."
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware $i normal
|
||||
done
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware_into_buf $i normal
|
||||
done
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware_direct $i normal
|
||||
done
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_request_firmware_nowait_uevent $i normal
|
||||
done
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_request_firmware_nowait_custom $i normal
|
||||
done
|
||||
do_tests normal
|
||||
|
||||
# Partial loads cannot use fallback, so do not repeat tests.
|
||||
test_request_partial_firmware_into_buf 0 10
|
||||
@ -472,25 +481,7 @@ test_request_partial_firmware_into_buf 2 10
|
||||
# a hung task, which would require a hard reset.
|
||||
echo
|
||||
echo "Testing with the file missing..."
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware_nofile $i
|
||||
done
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware_into_buf_nofile $i
|
||||
done
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware_direct_nofile $i
|
||||
done
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_request_firmware_nowait_uevent_nofile $i
|
||||
done
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_request_firmware_nowait_custom_nofile $i
|
||||
done
|
||||
do_tests nofile _nofile
|
||||
|
||||
# Partial loads cannot use fallback, so do not repeat tests.
|
||||
test_request_partial_firmware_into_buf_nofile 0 10
|
||||
@ -498,55 +489,58 @@ test_request_partial_firmware_into_buf_nofile 0 5
|
||||
test_request_partial_firmware_into_buf_nofile 1 6
|
||||
test_request_partial_firmware_into_buf_nofile 2 10
|
||||
|
||||
test "$HAS_FW_LOADER_COMPRESS" != "yes" && exit 0
|
||||
test_request_firmware_compressed ()
|
||||
{
|
||||
export COMPRESS_FORMAT="$1"
|
||||
|
||||
# test with both files present
|
||||
xz -9 -C crc32 -k $FW
|
||||
config_set_name $NAME
|
||||
echo
|
||||
echo "Testing with both plain and xz files present..."
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware $i both
|
||||
done
|
||||
# test with both files present
|
||||
compress_both_"$COMPRESS_FORMAT" $FW
|
||||
compress_both_"$COMPRESS_FORMAT" $FW_INTO_BUF
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware_into_buf $i both
|
||||
done
|
||||
config_set_name $NAME
|
||||
echo
|
||||
echo "Testing with both plain and $COMPRESS_FORMAT files present..."
|
||||
do_tests both
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware_direct $i both
|
||||
done
|
||||
# test with only compressed file present
|
||||
mv "$FW" "${FW}-orig"
|
||||
mv "$FW_INTO_BUF" "${FW_INTO_BUF}-orig"
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_request_firmware_nowait_uevent $i both
|
||||
done
|
||||
config_set_name $NAME
|
||||
echo
|
||||
echo "Testing with only $COMPRESS_FORMAT file present..."
|
||||
do_tests componly
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_request_firmware_nowait_custom $i both
|
||||
done
|
||||
mv "${FW}-orig" "$FW"
|
||||
mv "${FW_INTO_BUF}-orig" "$FW_INTO_BUF"
|
||||
}
|
||||
|
||||
# test with only xz file present
|
||||
mv "$FW" "${FW}-orig"
|
||||
echo
|
||||
echo "Testing with only xz file present..."
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware $i xzonly
|
||||
done
|
||||
compress_both_XZ ()
|
||||
{
|
||||
$RUN_XZ -k "$@"
|
||||
}
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware_into_buf $i xzonly
|
||||
done
|
||||
compress_componly_XZ ()
|
||||
{
|
||||
$RUN_XZ "$@"
|
||||
}
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_batched_request_firmware_direct $i xzonly
|
||||
done
|
||||
compress_both_ZSTD ()
|
||||
{
|
||||
$RUN_ZSTD -k "$@"
|
||||
}
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_request_firmware_nowait_uevent $i xzonly
|
||||
done
|
||||
compress_componly_ZSTD ()
|
||||
{
|
||||
$RUN_ZSTD --rm "$@"
|
||||
}
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
test_request_firmware_nowait_custom $i xzonly
|
||||
done
|
||||
if test "$HAS_FW_LOADER_COMPRESS_XZ" = "yes"; then
|
||||
test_request_firmware_compressed XZ
|
||||
fi
|
||||
|
||||
if test "$HAS_FW_LOADER_COMPRESS_ZSTD" = "yes"; then
|
||||
test_request_firmware_compressed ZSTD
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
@ -62,7 +62,9 @@ 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)"
|
||||
HAS_FW_LOADER_COMPRESS="$(kconfig_has CONFIG_FW_LOADER_COMPRESS=y)"
|
||||
HAS_FW_LOADER_COMPRESS_XZ="$(kconfig_has CONFIG_FW_LOADER_COMPRESS_XZ=y)"
|
||||
HAS_FW_LOADER_COMPRESS_ZSTD="$(kconfig_has CONFIG_FW_LOADER_COMPRESS_ZSTD=y)"
|
||||
HAS_FW_UPLOAD="$(kconfig_has CONFIG_FW_UPLOAD=y)"
|
||||
PROC_FW_IGNORE_SYSFS_FALLBACK="0"
|
||||
PROC_FW_FORCE_SYSFS_FALLBACK="0"
|
||||
|
||||
@ -98,9 +100,14 @@ check_setup()
|
||||
|
||||
OLD_FWPATH="$(cat /sys/module/firmware_class/parameters/path)"
|
||||
|
||||
if [ "$HAS_FW_LOADER_COMPRESS" = "yes" ]; then
|
||||
if [ "$HAS_FW_LOADER_COMPRESS_XZ" = "yes" ]; then
|
||||
if ! which xz 2> /dev/null > /dev/null; then
|
||||
HAS_FW_LOADER_COMPRESS=""
|
||||
HAS_FW_LOADER_COMPRESS_XZ=""
|
||||
fi
|
||||
fi
|
||||
if [ "$HAS_FW_LOADER_COMPRESS_ZSTD" = "yes" ]; then
|
||||
if ! which zstd 2> /dev/null > /dev/null; then
|
||||
HAS_FW_LOADER_COMPRESS_ZSTD=""
|
||||
fi
|
||||
fi
|
||||
}
|
||||
@ -113,6 +120,12 @@ verify_reqs()
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
if [ "$TEST_REQS_FW_UPLOAD" = "yes" ]; then
|
||||
if [ ! "$HAS_FW_UPLOAD" = "yes" ]; then
|
||||
echo "firmware upload disabled so ignoring test"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
setup_tmp_file()
|
||||
|
@ -22,6 +22,10 @@ run_tests()
|
||||
proc_set_force_sysfs_fallback $1
|
||||
proc_set_ignore_sysfs_fallback $2
|
||||
$TEST_DIR/fw_fallback.sh
|
||||
|
||||
proc_set_force_sysfs_fallback $1
|
||||
proc_set_ignore_sysfs_fallback $2
|
||||
$TEST_DIR/fw_upload.sh
|
||||
}
|
||||
|
||||
run_test_config_0001()
|
||||
|
214
tools/testing/selftests/firmware/fw_upload.sh
Executable file
214
tools/testing/selftests/firmware/fw_upload.sh
Executable file
@ -0,0 +1,214 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# This validates the user-initiated fw upload mechanism of the firmware
|
||||
# loader. It verifies that one or more firmware devices can be created
|
||||
# for a device driver. It also verifies the data transfer, the
|
||||
# cancellation support, and the error flows.
|
||||
set -e
|
||||
|
||||
TEST_REQS_FW_UPLOAD="yes"
|
||||
TEST_DIR=$(dirname $0)
|
||||
|
||||
progress_states="preparing transferring programming"
|
||||
errors="hw-error
|
||||
timeout
|
||||
device-busy
|
||||
invalid-file-size
|
||||
read-write-error
|
||||
flash-wearout"
|
||||
error_abort="user-abort"
|
||||
fwname1=fw1
|
||||
fwname2=fw2
|
||||
fwname3=fw3
|
||||
|
||||
source $TEST_DIR/fw_lib.sh
|
||||
|
||||
check_mods
|
||||
check_setup
|
||||
verify_reqs
|
||||
|
||||
trap "upload_finish" EXIT
|
||||
|
||||
upload_finish() {
|
||||
local fwdevs="$fwname1 $fwname2 $fwname3"
|
||||
|
||||
for name in $fwdevs; do
|
||||
if [ -e "$DIR/$name" ]; then
|
||||
echo -n "$name" > "$DIR"/upload_unregister
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
upload_fw() {
|
||||
local name="$1"
|
||||
local file="$2"
|
||||
|
||||
echo 1 > "$DIR"/"$name"/loading
|
||||
cat "$file" > "$DIR"/"$name"/data
|
||||
echo 0 > "$DIR"/"$name"/loading
|
||||
}
|
||||
|
||||
verify_fw() {
|
||||
local name="$1"
|
||||
local file="$2"
|
||||
|
||||
echo -n "$name" > "$DIR"/config_upload_name
|
||||
if ! cmp "$file" "$DIR"/upload_read > /dev/null 2>&1; then
|
||||
echo "$0: firmware compare for $name did not match" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$0: firmware upload for $name works" >&2
|
||||
return 0
|
||||
}
|
||||
|
||||
inject_error() {
|
||||
local name="$1"
|
||||
local status="$2"
|
||||
local error="$3"
|
||||
|
||||
echo 1 > "$DIR"/"$name"/loading
|
||||
echo -n "inject":"$status":"$error" > "$DIR"/"$name"/data
|
||||
echo 0 > "$DIR"/"$name"/loading
|
||||
}
|
||||
|
||||
await_status() {
|
||||
local name="$1"
|
||||
local expected="$2"
|
||||
local status
|
||||
local i
|
||||
|
||||
let i=0
|
||||
while [ $i -lt 50 ]; do
|
||||
status=$(cat "$DIR"/"$name"/status)
|
||||
if [ "$status" = "$expected" ]; then
|
||||
return 0;
|
||||
fi
|
||||
sleep 1e-03
|
||||
let i=$i+1
|
||||
done
|
||||
|
||||
echo "$0: Invalid status: Expected $expected, Actual $status" >&2
|
||||
return 1;
|
||||
}
|
||||
|
||||
await_idle() {
|
||||
local name="$1"
|
||||
|
||||
await_status "$name" "idle"
|
||||
return $?
|
||||
}
|
||||
|
||||
expect_error() {
|
||||
local name="$1"
|
||||
local expected="$2"
|
||||
local error=$(cat "$DIR"/"$name"/error)
|
||||
|
||||
if [ "$error" != "$expected" ]; then
|
||||
echo "Invalid error: Expected $expected, Actual $error" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
random_firmware() {
|
||||
local bs="$1"
|
||||
local count="$2"
|
||||
local file=$(mktemp -p /tmp uploadfwXXX.bin)
|
||||
|
||||
dd if=/dev/urandom of="$file" bs="$bs" count="$count" > /dev/null 2>&1
|
||||
echo "$file"
|
||||
}
|
||||
|
||||
test_upload_cancel() {
|
||||
local name="$1"
|
||||
local status
|
||||
|
||||
for status in $progress_states; do
|
||||
inject_error $name $status $error_abort
|
||||
if ! await_status $name $status; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo 1 > "$DIR"/"$name"/cancel
|
||||
|
||||
if ! await_idle $name; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! expect_error $name "$status":"$error_abort"; then
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$0: firmware upload cancellation works"
|
||||
return 0
|
||||
}
|
||||
|
||||
test_error_handling() {
|
||||
local name=$1
|
||||
local status
|
||||
local error
|
||||
|
||||
for status in $progress_states; do
|
||||
for error in $errors; do
|
||||
inject_error $name $status $error
|
||||
|
||||
if ! await_idle $name; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! expect_error $name "$status":"$error"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
done
|
||||
done
|
||||
echo "$0: firmware upload error handling works"
|
||||
}
|
||||
|
||||
test_fw_too_big() {
|
||||
local name=$1
|
||||
local fw_too_big=`random_firmware 512 5`
|
||||
local expected="preparing:invalid-file-size"
|
||||
|
||||
upload_fw $name $fw_too_big
|
||||
rm -f $fw_too_big
|
||||
|
||||
if ! await_idle $name; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! expect_error $name $expected; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$0: oversized firmware error handling works"
|
||||
}
|
||||
|
||||
echo -n "$fwname1" > "$DIR"/upload_register
|
||||
echo -n "$fwname2" > "$DIR"/upload_register
|
||||
echo -n "$fwname3" > "$DIR"/upload_register
|
||||
|
||||
test_upload_cancel $fwname1
|
||||
test_error_handling $fwname1
|
||||
test_fw_too_big $fwname1
|
||||
|
||||
fw_file1=`random_firmware 512 4`
|
||||
fw_file2=`random_firmware 512 3`
|
||||
fw_file3=`random_firmware 512 2`
|
||||
|
||||
upload_fw $fwname1 $fw_file1
|
||||
upload_fw $fwname2 $fw_file2
|
||||
upload_fw $fwname3 $fw_file3
|
||||
|
||||
verify_fw ${fwname1} ${fw_file1}
|
||||
verify_fw ${fwname2} ${fw_file2}
|
||||
verify_fw ${fwname3} ${fw_file3}
|
||||
|
||||
echo -n "$fwname1" > "$DIR"/upload_unregister
|
||||
echo -n "$fwname2" > "$DIR"/upload_unregister
|
||||
echo -n "$fwname3" > "$DIR"/upload_unregister
|
||||
|
||||
exit 0
|
Loading…
Reference in New Issue
Block a user