Extend the firmware loader subsystem to support a persistent sysfs
interface that userspace may use to initiate a firmware update. For
example, FPGA based PCIe cards automatically load firmware and FPGA images
from local FLASH when the card boots. The images in FLASH may be updated
with new images that are uploaded by the user.
A device driver may call firmware_upload_register() to expose persistent
"loading" and "data" sysfs files at /sys/class/firmare/<NAME>/*. These
files are used in the same way as the fallback sysfs "loading" and "data"
files. However, when 0 is written to "loading" to complete the write of
firmware data, the data is also transferred to the lower-level driver
using pre-registered call-back functions. The data transfer is done in
the context of a kernel worker thread.
Additional sysfs nodes are added in the same location as "loading" and
"data" to monitor the transfer of the image data to the device using
callback functions provided by the lower-level device driver and to allow
the data transfer to be cancelled.
Example usage:
$ pwd
/sys/class/firmware/n3000bmc-sec-update.8
$ ls
cancel device loading remaining_size subsystem
data error power status uevent
$ echo 1 > loading
$ cat /tmp/firmware.bin > data
$ echo 0 > loading
$ while :; do cat status; cat remaining_size ; sleep 3; done
preparing
44590080
<--snip-->
transferring
44459008
transferring
44311552
<--snip-->
transferring
173056
<--snip-->
programming
0
<--snip-->
idle
0
^C
$ cat error
The first two patches in this set make minor changes to enable the
fw_priv data structure and the sysfs interfaces to be used multiple times
during the existence of the device driver instance. The third patch is
mostly a reorganization of existing code in preparation for sharing common
code with the firmware-upload support. The fourth and fifth patches provide
the code for user-initiated firmware uploads. The final 3 patches extend
selftest support to test firmware-upload functionality.
Changelog v1 -> v2:
- Rebased to 5.18-rc2.
- It was discovered that the new function in v1, fw_state_is_done(), is
equivalent to the existing fw_sysfs_done() function. Renamed
fw_sysfs_done() and fw_sysfs_loading() to fw_state_is_done() and
fw_state_is_loading() respectively, and placed them along side companion
functions in drivers/base/firmware_loader/firmware.h.
- Removed the "if !fw_sysfs_done(fw_priv))" condition in switch case 1 of
firmware_loading_store(). It is rendered unnecessary by other changes
to the function.
- Updated documentation Date and KernelVersion fields to July 2022
and 5.19.
- Unconditionally set fw_priv->is_paged_buf to true in
firmware_upload_register();
Changelog RFC -> v1:
- Renamed files fw_sysfs.c and fw_sysfs.h to sysfs.c and sysfs.h
- Moved "MODULE_IMPORT_NS(FIRMWARE_LOADER_PRIVATE);" from sysfs.c to
sysfs.h to address an error identified by the kernel test robot
<[email protected]>
- renamed fw_upload_register() and fw_upload_unregister() to
firmware_upload_register() and fw_upload_unregister().
- Moved ifdef'd section of code out of firmware_loading_store() in sysfs.c
into a new function, fw_upload_start(), in sysfs_upload.c.
- Changed #defines to enums for error codes and progress states
- Added additional kernel-doc supported symbols into the documentation.
Some rewording in documentation as well.
- Added module reference counting for the parent module in the
firmware_upload_register() and firmware_upload_unregister() functions
to fix problems found when testing with test_firmware module.
- Removed unnecessary module reference counting for THIS_MODULE.
- Added a new patch to modify the test_firmware module to support
testing of the firmware upload mechanism.
- Added a new patch to modify the test_firmware module to support
error injection for firmware upload.
- Added a new patch to extend the existing firmware selftests to cover
firmware upload.
Russ Weight (8):
firmware_loader: Clear data and size in fw_free_paged_buf
firmware_loader: Check fw_state_is_done in loading_store
firmware_loader: Split sysfs support from fallback
firmware_loader: Add firmware-upload support
firmware_loader: Add sysfs nodes to monitor fw_upload
test_firmware: Add test support for firmware upload
test_firmware: Error injection for firmware upload
selftests: firmware: Add firmware upload selftests
.../ABI/testing/sysfs-class-firmware | 77 ++++
.../driver-api/firmware/fw_upload.rst | 117 +++++
Documentation/driver-api/firmware/index.rst | 1 +
drivers/base/firmware_loader/Kconfig | 18 +
drivers/base/firmware_loader/Makefile | 2 +
drivers/base/firmware_loader/fallback.c | 430 ------------------
drivers/base/firmware_loader/fallback.h | 46 +-
drivers/base/firmware_loader/firmware.h | 16 +
drivers/base/firmware_loader/main.c | 18 +-
drivers/base/firmware_loader/sysfs.c | 423 +++++++++++++++++
drivers/base/firmware_loader/sysfs.h | 100 ++++
drivers/base/firmware_loader/sysfs_upload.c | 397 ++++++++++++++++
drivers/base/firmware_loader/sysfs_upload.h | 46 ++
include/linux/firmware.h | 82 ++++
lib/test_firmware.c | 378 +++++++++++++++
tools/testing/selftests/firmware/Makefile | 2 +-
tools/testing/selftests/firmware/config | 1 +
tools/testing/selftests/firmware/fw_lib.sh | 7 +
.../selftests/firmware/fw_run_tests.sh | 4 +
tools/testing/selftests/firmware/fw_upload.sh | 214 +++++++++
20 files changed, 1893 insertions(+), 486 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-class-firmware
create mode 100644 Documentation/driver-api/firmware/fw_upload.rst
create mode 100644 drivers/base/firmware_loader/sysfs.c
create mode 100644 drivers/base/firmware_loader/sysfs.h
create mode 100644 drivers/base/firmware_loader/sysfs_upload.c
create mode 100644 drivers/base/firmware_loader/sysfs_upload.h
create mode 100755 tools/testing/selftests/firmware/fw_upload.sh
--
2.25.1
Add support for testing the firmware upload driver. There are four sysfs
nodes added:
upload_register: write-only
Write the name of the firmware device node to be created
upload_unregister: write-only
Write the name of the firmware device node to be destroyed
config_upload_name: read/write
Set the name to be used by upload_read
upload_read: read-only
Read back the data associated with the firmware device node named
in config_upload_name
You can create multiple, concurrent firmware device nodes for firmware
upload testing. Read firmware back and validate it using config_upload_name
and upload_red.
Example:
$ cd /sys/devices/virtual/misc/test_firmware
$ echo -n fw1 > upload_register
$ ls fw1
cancel data device error loading power remaining_size status
subsystem uevent
$ dd if=/dev/urandom of=/tmp/random-firmware.bin bs=512 count=4
4+0 records in
4+0 records out
2048 bytes (2.0 kB, 2.0 KiB) copied, 0.000131959 s, 15.5 MB/s
$ echo 1 > fw1/loading
$ cat /tmp/random-firmware.bin > fw1/data
$ echo 0 > fw1/loading
$ cat fw1/status
idle
$ cat fw1/error
$ echo -n fw1 > config_upload_name
$ cmp /tmp/random-firmware.bin upload_read
$ echo $?
0
$ echo -n fw1 > upload_unregister
Signed-off-by: Russ Weight <[email protected]>
---
v2:
- No changes from v1
---
lib/test_firmware.c | 261 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 261 insertions(+)
diff --git a/lib/test_firmware.c b/lib/test_firmware.c
index 1bccd6cd5f48..2b8c56d7bf37 100644
--- a/lib/test_firmware.c
+++ b/lib/test_firmware.c
@@ -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,28 @@ struct test_config {
struct device *device);
};
+struct test_firmware_upload {
+ char *name;
+ struct list_head node;
+ char *buf;
+ size_t size;
+ bool cancel_request;
+ 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 +223,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 +303,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 +425,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 +1048,167 @@ 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;
+}
+
+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;
+
+ tst->cancel_request = false;
+
+ if (!size || size > TEST_UPLOAD_MAX_SIZE)
+ return FW_UPLOAD_ERR_INVALID_SIZE;
+
+ memset(tst->buf, 0, TEST_UPLOAD_MAX_SIZE);
+ tst->size = size;
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+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;
+ 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;
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err test_fw_upload_complete(struct fw_upload *fwl)
+{
+ struct test_firmware_upload *tst = fwl->dd_handle;
+
+ if (tst->cancel_request)
+ return FW_UPLOAD_ERR_CANCELED;
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static void test_fw_upload_cancel(struct fw_upload *fwl)
+{
+ struct test_firmware_upload *tst = fwl->dd_handle;
+
+ tst->cancel_request = true;
+}
+
+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,
+};
+
+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 +1271,42 @@ 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;
+ 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, &test_upload_list, node)
+ if (tst->name == test_fw_config->upload_name)
+ break;
+
+ if (tst->name != test_fw_config->upload_name) {
+ 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 +1322,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 +1339,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 +1388,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);
--
2.25.1
Add error injection capability to the test_firmware module specifically
for firmware upload testing. Error injection instructions are transferred
as the first part of the firmware payload. The format of an error
injection string is similar to the error strings that may be read from
the error sysfs node.
To inject the error "programming:hw-error", one would use the error
injection string "inject:programming:hw-error" as the firmware payload:
$ echo 1 > loading
$ echo inject:programming:hw-error > data
$ echo 0 > loading
$ cat status
idle
$ cat error
programming:hw-error
The first part of the error string is the progress state of the upload at
the time of the error. The progress state would be one of the following:
"preparing", "transferring", or "programming". The second part of the
error string is one of the following: "hw-error", "timeout", "device-busy",
"invalid-file-size", "read-write-error", "flash-wearout", and "user-abort".
Note that all of the error strings except "user-abort" will fail without
delay. The "user-abort" error will cause the firmware upload to stall at
the requested progress state for up to 5 minutes to allow you to echo 1
to the cancel sysfs node. It is this cancellation that causes the
'user-abort" error. If the upload is not cancelled within the 5 minute
time period, then the upload will complete without an error.
Signed-off-by: Russ Weight <[email protected]>
---
v2:
- No changes from v1
---
lib/test_firmware.c | 127 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 122 insertions(+), 5 deletions(-)
diff --git a/lib/test_firmware.c b/lib/test_firmware.c
index 2b8c56d7bf37..76115c1a2629 100644
--- a/lib/test_firmware.c
+++ b/lib/test_firmware.c
@@ -117,12 +117,18 @@ 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;
};
@@ -1067,20 +1073,105 @@ static void upload_release_all(void)
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)
- return FW_UPLOAD_ERR_INVALID_SIZE;
+ 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;
- return FW_UPLOAD_ERR_NONE;
+ 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,
@@ -1088,6 +1179,7 @@ static enum fw_upload_err test_fw_upload_write(struct fw_upload *fwl,
u32 size, u32 *written)
{
struct test_firmware_upload *tst = fwl->dd_handle;
+ const char *progress = "transferring:";
u32 blk_size;
if (tst->cancel_request)
@@ -1097,17 +1189,33 @@ static enum fw_upload_err test_fw_upload_write(struct fw_upload *fwl,
memcpy(tst->buf + offset, data + offset, blk_size);
*written = blk_size;
- return FW_UPLOAD_ERR_NONE;
+
+ 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;
- return FW_UPLOAD_ERR_NONE;
+ 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)
@@ -1117,11 +1225,20 @@ static void test_fw_upload_cancel(struct fw_upload *fwl)
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,
--
2.25.1
Rename fw_sysfs_done() and fw_sysfs_loading() to fw_state_is_done() and
fw_state_is_loading() respectively, and place them along side companion
functions in drivers/base/firmware_loader/firmware.h.
Use the fw_state_is_done() function to exit early from
firmware_loading_store() if the state is already "done". This is being done
in preparation for supporting persistent sysfs nodes to allow userspace to
upload firmware to a device, potentially reusing the sysfs loading and data
files multiple times.
Signed-off-by: Russ Weight <[email protected]>
---
v1:
- No change from RFC patch
v2:
- It was discovered that the new function in v1, fw_state_is_done(), is
equivalent to the existing fw_sysfs_done() function. Renamed
fw_sysfs_done() and fw_sysfs_loading() to fw_state_is_done() and
fw_state_is_loading() respectively, and placed them along side companion
functions in drivers/base/firmware_loader/firmware.h.
- Removed the "if !fw_sysfs_done(fw_priv))" condition in switch case 1 of
firmware_loading_store(). It is rendered unnecessary by other changes
to the function by this patch.
---
drivers/base/firmware_loader/fallback.c | 28 +++++++------------------
drivers/base/firmware_loader/firmware.h | 10 +++++++++
2 files changed, 18 insertions(+), 20 deletions(-)
diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c
index 4afb0e9312c0..8063eb595719 100644
--- a/drivers/base/firmware_loader/fallback.c
+++ b/drivers/base/firmware_loader/fallback.c
@@ -58,16 +58,6 @@ 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);
@@ -91,7 +81,7 @@ 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))
+ if (fw_state_is_aborted(fw_priv) || fw_state_is_done(fw_priv))
return;
fw_state_aborted(fw_priv);
@@ -220,7 +210,7 @@ static ssize_t firmware_loading_show(struct device *dev,
mutex_lock(&fw_lock);
if (fw_sysfs->fw_priv)
- loading = fw_sysfs_loading(fw_sysfs->fw_priv);
+ loading = fw_state_is_loading(fw_sysfs->fw_priv);
mutex_unlock(&fw_lock);
return sysfs_emit(buf, "%d\n", loading);
@@ -250,19 +240,17 @@ static ssize_t firmware_loading_store(struct device *dev,
mutex_lock(&fw_lock);
fw_priv = fw_sysfs->fw_priv;
- if (fw_state_is_aborted(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 */
- if (!fw_sysfs_done(fw_priv)) {
- fw_free_paged_buf(fw_priv);
- fw_state_start(fw_priv);
- }
+ fw_free_paged_buf(fw_priv);
+ fw_state_start(fw_priv);
break;
case 0:
- if (fw_sysfs_loading(fw_priv)) {
+ if (fw_state_is_loading(fw_priv)) {
int rc;
/*
@@ -350,7 +338,7 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj,
mutex_lock(&fw_lock);
fw_priv = fw_sysfs->fw_priv;
- if (!fw_priv || fw_sysfs_done(fw_priv)) {
+ if (!fw_priv || fw_state_is_done(fw_priv)) {
ret_count = -ENODEV;
goto out;
}
@@ -410,7 +398,7 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj,
mutex_lock(&fw_lock);
fw_priv = fw_sysfs->fw_priv;
- if (!fw_priv || fw_sysfs_done(fw_priv)) {
+ if (!fw_priv || fw_state_is_done(fw_priv)) {
retval = -ENODEV;
goto out;
}
diff --git a/drivers/base/firmware_loader/firmware.h b/drivers/base/firmware_loader/firmware.h
index 2889f446ad41..d5ff32a1ba2d 100644
--- a/drivers/base/firmware_loader/firmware.h
+++ b/drivers/base/firmware_loader/firmware.h
@@ -149,6 +149,16 @@ 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 assign_fw(struct firmware *fw, struct device *device);
#ifdef CONFIG_FW_LOADER
--
2.25.1