2022-04-22 19:46:24

by Russ Weight

[permalink] [raw]
Subject: [PATCH v5 0/8] Extend FW framework for user FW uploads

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/firmware/<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/secure-update.1
$ 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.

The first driver to use firmware-upload is being submitted _soon_ under the
title of "Intel MAX10 BMC Secure Update Driver".

Changelog v4 -> v5:
- Added Tested-by tag
- More cleanup for "#ifdef CONFIG_FW_LOADER_USER_HELPER". Error reported
by the kernel test robot <[email protected]>.

Changelog v3 -> v4:
- Added Reviewed-by tag
- Fixed errors reported by the kernel test robot <[email protected]> by
adjusting the functions that are bound by
"#ifdef CONFIG_FW_LOADER_USER_HELPER".

Changelog v2 -> v3:
- Added Reviewed-by tag
- Added kdoc support for enum fw_upload_prog progress codes

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 | 126 +++++
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 | 425 +++++++++++++++++
drivers/base/firmware_loader/sysfs.h | 100 ++++
drivers/base/firmware_loader/sysfs_upload.c | 397 ++++++++++++++++
drivers/base/firmware_loader/sysfs_upload.h | 54 +++
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, 1912 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


2022-04-22 21:15:51

by Russ Weight

[permalink] [raw]
Subject: [PATCH v5 2/8] firmware_loader: Check fw_state_is_done in loading_store

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.

Reviewed-by: Luis Chamberlain <[email protected]>
Reviewed-by: Tianfei zhang <[email protected]>
Tested-by: Matthew Gerlach <[email protected]>
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.
v3:
- Added Reviewed-by tag
v4:
- Added Reviewed-by tag
v5:
- Added Tested-by tag
---
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

2022-04-22 22:19:03

by Russ Weight

[permalink] [raw]
Subject: [PATCH v5 4/8] firmware_loader: Add firmware-upload support

Extend the firmware subsystem to support a persistent sysfs interface that
userspace may use to initiate a firmware update. 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 at his/her convenience.

A device driver may call firmware_upload_register() to expose persistent
"loading" and "data" sysfs files. These files are used in the same way as
the fallback sysfs "loading" and "data" files. When 0 is written to
"loading" to complete the write of firmware data, the data is 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.

Reviewed-by: Luis Chamberlain <[email protected]>
Reviewed-by: Tianfei zhang <[email protected]>
Tested-by: Matthew Gerlach <[email protected]>
Signed-off-by: Russ Weight <[email protected]>
---
v1:
- 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. This
module holds a reference count for the parent module, and the parent
module has symbol dependencies on this module.
v2:
- 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();
v3:
- Added Reviewed-by tag
- Added kdoc support for enum fw_upload_prog progress codes
v4:
- Added Reviewed-by tag
v5:
- Added Tested-by tag
---
.../ABI/testing/sysfs-class-firmware | 32 ++
.../driver-api/firmware/fw_upload.rst | 107 +++++++
Documentation/driver-api/firmware/index.rst | 1 +
drivers/base/firmware_loader/Kconfig | 14 +
drivers/base/firmware_loader/Makefile | 1 +
drivers/base/firmware_loader/firmware.h | 6 +
drivers/base/firmware_loader/main.c | 16 +-
drivers/base/firmware_loader/sysfs.c | 19 +-
drivers/base/firmware_loader/sysfs.h | 4 +
drivers/base/firmware_loader/sysfs_upload.c | 276 ++++++++++++++++++
drivers/base/firmware_loader/sysfs_upload.h | 49 ++++
include/linux/firmware.h | 82 ++++++
12 files changed, 595 insertions(+), 12 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_upload.c
create mode 100644 drivers/base/firmware_loader/sysfs_upload.h

diff --git a/Documentation/ABI/testing/sysfs-class-firmware b/Documentation/ABI/testing/sysfs-class-firmware
new file mode 100644
index 000000000000..18336c23b70d
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-firmware
@@ -0,0 +1,32 @@
+What: /sys/class/firmware/.../data
+Date: July 2022
+KernelVersion: 5.19
+Contact: Russ Weight <[email protected]>
+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/.../loading
+Date: July 2022
+KernelVersion: 5.19
+Contact: Russ Weight <[email protected]>
+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/.../timeout
+Date: July 2022
+KernelVersion: 5.19
+Contact: Russ Weight <[email protected]>
+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.
diff --git a/Documentation/driver-api/firmware/fw_upload.rst b/Documentation/driver-api/firmware/fw_upload.rst
new file mode 100644
index 000000000000..afbd8baca0d7
--- /dev/null
+++ b/Documentation/driver-api/firmware/fw_upload.rst
@@ -0,0 +1,107 @@
+.. 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.
+
+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:
+
+.. 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
diff --git a/Documentation/driver-api/firmware/index.rst b/Documentation/driver-api/firmware/index.rst
index 57415d657173..9d2c19dc8e36 100644
--- a/Documentation/driver-api/firmware/index.rst
+++ b/Documentation/driver-api/firmware/index.rst
@@ -8,6 +8,7 @@ Linux Firmware API
core
efi/index
request_firmware
+ fw_upload
other_interfaces

.. only:: subproject and html
diff --git a/drivers/base/firmware_loader/Kconfig b/drivers/base/firmware_loader/Kconfig
index 9e03178eee00..adf2b182d74d 100644
--- a/drivers/base/firmware_loader/Kconfig
+++ b/drivers/base/firmware_loader/Kconfig
@@ -190,5 +190,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
diff --git a/drivers/base/firmware_loader/Makefile b/drivers/base/firmware_loader/Makefile
index aab213f82288..60d19f9e0ddc 100644
--- a/drivers/base/firmware_loader/Makefile
+++ b/drivers/base/firmware_loader/Makefile
@@ -7,5 +7,6 @@ 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/
diff --git a/drivers/base/firmware_loader/firmware.h b/drivers/base/firmware_loader/firmware.h
index d5ff32a1ba2d..fe77e91c38a2 100644
--- a/drivers/base/firmware_loader/firmware.h
+++ b/drivers/base/firmware_loader/firmware.h
@@ -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)
@@ -159,7 +160,12 @@ 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);
diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c
index 2cc11d93753a..874a5ef31c56 100644
--- a/drivers/base/firmware_loader/main.c
+++ b/drivers/base/firmware_loader/main.c
@@ -91,9 +91,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 +163,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 +220,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);
diff --git a/drivers/base/firmware_loader/sysfs.c b/drivers/base/firmware_loader/sysfs.c
index 1509cb6b6e60..4a956cc3b7ea 100644
--- a/drivers/base/firmware_loader/sysfs.c
+++ b/drivers/base/firmware_loader/sysfs.c
@@ -6,8 +6,8 @@
#include <linux/slab.h>
#include <linux/types.h>

-#include "firmware.h"
#include "sysfs.h"
+#include "sysfs_upload.h"

/*
* sysfs support for firmware loader
@@ -94,6 +94,10 @@ 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);
}

@@ -199,6 +203,14 @@ static ssize_t firmware_loading_store(struct device *dev,
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;
}
@@ -208,6 +220,9 @@ static ssize_t firmware_loading_store(struct device *dev,
fallthrough;
case -1:
fw_load_abort(fw_sysfs);
+ if (fw_sysfs->fw_upload_priv)
+ fw_state_init(fw_sysfs->fw_priv);
+
break;
}
out:
@@ -215,7 +230,7 @@ static ssize_t firmware_loading_store(struct device *dev,
return written;
}

-static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store);
+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)
diff --git a/drivers/base/firmware_loader/sysfs.h b/drivers/base/firmware_loader/sysfs.h
index b7b5c1766052..c21bcfe374ff 100644
--- a/drivers/base/firmware_loader/sysfs.h
+++ b/drivers/base/firmware_loader/sysfs.h
@@ -4,9 +4,12 @@

#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
/**
@@ -73,6 +76,7 @@ struct fw_sysfs {
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)
diff --git a/drivers/base/firmware_loader/sysfs_upload.c b/drivers/base/firmware_loader/sysfs_upload.c
new file mode 100644
index 000000000000..0a6450d1974f
--- /dev/null
+++ b/drivers/base/firmware_loader/sysfs_upload.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "sysfs.h"
+#include "sysfs_upload.h"
+
+/*
+ * Support for user-space to initiate a firmware upload to a device.
+ */
+
+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
+ * @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);
diff --git a/drivers/base/firmware_loader/sysfs_upload.h b/drivers/base/firmware_loader/sysfs_upload.h
new file mode 100644
index 000000000000..18bd4d99f064
--- /dev/null
+++ b/drivers/base/firmware_loader/sysfs_upload.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __FIRMWARE_UPLOAD_H
+#define __FIRMWARE_UPLOAD_H
+
+#include <linux/device.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 */
+};
+
+#ifdef CONFIG_FW_UPLOAD
+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_UPLOAD_H */
diff --git a/include/linux/firmware.h b/include/linux/firmware.h
index ec2ccfebef65..de7fea3bca51 100644
--- a/include/linux/firmware.h
+++ b/include/linux/firmware.h
@@ -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
--
2.25.1