From 23cfbc6ec44e5e80d5522976ff45ffcdcddfb230 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 21 Apr 2022 17:29:04 +0200 Subject: firmware: Add the support for ZSTD-compressed firmware files As the growing demand on ZSTD compressions, there have been requests for the support of ZSTD-compressed firmware files, so here it is: this patch extends the firmware loader code to allow loading ZSTD files. The implementation is fairly straightforward, it just adds a ZSTD decompression routine for the file expander. (And the code is even simpler than XZ thanks to the ZSTD API that gives the original decompressed size from the header.) Link: https://lore.kernel.org/all/20210127154939.13288-1-tiwai@suse.de/ Tested-by: Piotr Gorski Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/r/20220421152908.4718-2-tiwai@suse.de Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/Kconfig | 24 +++++++++--- drivers/base/firmware_loader/main.c | 76 ++++++++++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 9 deletions(-) (limited to 'drivers/base/firmware_loader') diff --git a/drivers/base/firmware_loader/Kconfig b/drivers/base/firmware_loader/Kconfig index 38f3b66bf52b..08bb50451a96 100644 --- a/drivers/base/firmware_loader/Kconfig +++ b/drivers/base/firmware_loader/Kconfig @@ -159,21 +159,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 diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 94d1789a233e..74830aeec7f6 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -304,10 +305,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 +466,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]; @@ -757,7 +822,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); -- cgit v1.2.3 From 4ac4a90d7728b161f0ce0527feb19d60af961dfb Mon Sep 17 00:00:00 2001 From: Russ Weight Date: Thu, 21 Apr 2022 14:21:57 -0700 Subject: firmware_loader: Clear data and size in fw_free_paged_buf The fw_free_paged_buf() function resets the paged buffer information in the fw_priv data structure. Additionally, clear the data and size members of fw_priv in order to facilitate the reuse of fw_priv. This is being done in preparation for enabling userspace to initiate multiple firmware uploads using this sysfs interface. Reviewed-by: Luis Chamberlain Reviewed-by: Tianfei zhang Tested-by: Matthew Gerlach Signed-off-by: Russ Weight Link: https://lore.kernel.org/r/20220421212204.36052-2-russell.h.weight@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/main.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/base/firmware_loader') diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 74830aeec7f6..3d9b2074912d 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -254,6 +254,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) -- cgit v1.2.3 From 736da0b657f615db7e29606eb8818871534a8943 Mon Sep 17 00:00:00 2001 From: Russ Weight Date: Thu, 21 Apr 2022 14:21:58 -0700 Subject: 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 Reviewed-by: Tianfei zhang Tested-by: Matthew Gerlach Signed-off-by: Russ Weight Link: https://lore.kernel.org/r/20220421212204.36052-3-russell.h.weight@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/fallback.c | 28 ++++++++-------------------- drivers/base/firmware_loader/firmware.h | 10 ++++++++++ 2 files changed, 18 insertions(+), 20 deletions(-) (limited to 'drivers/base/firmware_loader') 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 -- cgit v1.2.3 From 3677563eb8731e1ad5970e3e57f74e5f9d63502a Mon Sep 17 00:00:00 2001 From: Thiébaud Weksteen Date: Fri, 22 Apr 2022 11:32:15 +1000 Subject: firmware_loader: use kernel credentials when reading firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Device drivers may decide to not load firmware when probed to avoid slowing down the boot process should the firmware filesystem not be available yet. In this case, the firmware loading request may be done when a device file associated with the driver is first accessed. The credentials of the userspace process accessing the device file may be used to validate access to the firmware files requested by the driver. Ensure that the kernel assumes the responsibility of reading the firmware. This was observed on Android for a graphic driver loading their firmware when the device file (e.g. /dev/mali0) was first opened by userspace (i.e. surfaceflinger). The security context of surfaceflinger was used to validate the access to the firmware file (e.g. /vendor/firmware/mali.bin). Because previous configurations were relying on the userspace fallback mechanism, the security context of the userspace daemon (i.e. ueventd) was consistently used to read firmware files. More devices are found to use the command line argument firmware_class.path which gives the kernel the opportunity to read the firmware directly, hence surfacing this misattribution. Signed-off-by: Thiébaud Weksteen Reviewed-by: Luis Chamberlain Tested-by: John Stultz Link: https://lore.kernel.org/r/20220422013215.2301793-1-tweek@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/main.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'drivers/base/firmware_loader') diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 3d9b2074912d..6ac6c7753acc 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -802,6 +802,8 @@ _request_firmware(const struct firmware **firmware_p, const char *name, size_t offset, u32 opt_flags) { struct firmware *fw = NULL; + struct cred *kern_cred = NULL; + const struct cred *old_cred; bool nondirect = false; int ret; @@ -818,6 +820,18 @@ _request_firmware(const struct firmware **firmware_p, const char *name, if (ret <= 0) /* error or already assigned */ goto out; + /* + * We are about to try to access the firmware file. Because we may have been + * called by a driver when serving an unrelated request from userland, we use + * the kernel credentials to read the file. + */ + kern_cred = prepare_kernel_cred(NULL); + if (!kern_cred) { + ret = -ENOMEM; + goto out; + } + old_cred = override_creds(kern_cred); + ret = fw_get_filesystem_firmware(device, fw->priv, "", NULL); /* Only full reads can support decompression, platform, and sysfs. */ @@ -848,6 +862,8 @@ _request_firmware(const struct firmware **firmware_p, const char *name, } else ret = assign_fw(fw, device); + revert_creds(old_cred); + out: if (ret < 0) { fw_abort_batch_reqs(fw); -- cgit v1.2.3 From e0c11a8b985137aebf4bcd07cd957b80ac23924d Mon Sep 17 00:00:00 2001 From: Russ Weight Date: Thu, 21 Apr 2022 14:21:59 -0700 Subject: firmware_loader: Split sysfs support from fallback In preparation for sharing the "loading" and "data" sysfs nodes with the new firmware upload support, split out sysfs functionality from fallback.c and fallback.h into sysfs.c and sysfs.h. This includes the firmware class driver code that is associated with the sysfs files and the fw_fallback_config support for the timeout sysfs node. CONFIG_FW_LOADER_SYSFS is created and is selected by CONFIG_FW_LOADER_USER_HELPER in order to include sysfs.o in firmware_class-objs. This is mostly just a code reorganization. There are a few symbols that change in scope, and these can be identified by looking at the header file changes. A few white-space warnings from checkpatch are also addressed in this patch. Reviewed-by: Luis Chamberlain Reviewed-by: Tianfei zhang Tested-by: Matthew Gerlach Signed-off-by: Russ Weight Link: https://lore.kernel.org/r/20220421212204.36052-4-russell.h.weight@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/Kconfig | 4 + drivers/base/firmware_loader/Makefile | 1 + drivers/base/firmware_loader/fallback.c | 418 -------------------------------- drivers/base/firmware_loader/fallback.h | 46 +--- drivers/base/firmware_loader/sysfs.c | 401 ++++++++++++++++++++++++++++++ drivers/base/firmware_loader/sysfs.h | 96 ++++++++ 6 files changed, 503 insertions(+), 463 deletions(-) create mode 100644 drivers/base/firmware_loader/sysfs.c create mode 100644 drivers/base/firmware_loader/sysfs.h (limited to 'drivers/base/firmware_loader') diff --git a/drivers/base/firmware_loader/Kconfig b/drivers/base/firmware_loader/Kconfig index 08bb50451a96..d13e4383ea0c 100644 --- a/drivers/base/firmware_loader/Kconfig +++ b/drivers/base/firmware_loader/Kconfig @@ -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 diff --git a/drivers/base/firmware_loader/Makefile b/drivers/base/firmware_loader/Makefile index e87843408fe6..aab213f82288 100644 --- a/drivers/base/firmware_loader/Makefile +++ b/drivers/base/firmware_loader/Makefile @@ -6,5 +6,6 @@ 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 obj-y += builtin/ diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c index 8063eb595719..bf68e3947814 100644 --- a/drivers/base/firmware_loader/fallback.c +++ b/drivers/base/firmware_loader/fallback.c @@ -3,12 +3,9 @@ #include #include #include -#include #include -#include #include #include -#include #include #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 @@ -63,37 +44,6 @@ 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_state_is_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) @@ -110,374 +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_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); - } - 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_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, - 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 diff --git a/drivers/base/firmware_loader/fallback.h b/drivers/base/firmware_loader/fallback.h index 9f3055d3b4ca..144148595660 100644 --- a/drivers/base/firmware_loader/fallback.h +++ b/drivers/base/firmware_loader/fallback.h @@ -6,29 +6,7 @@ #include #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 diff --git a/drivers/base/firmware_loader/sysfs.c b/drivers/base/firmware_loader/sysfs.c new file mode 100644 index 000000000000..1509cb6b6e60 --- /dev/null +++ b/drivers/base/firmware_loader/sysfs.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include + +#include "firmware.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); + + 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, +}; + +#ifdef CONFIG_FW_LOADER_USER_HELPER +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); +} +#endif + +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); + } + 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_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, + 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 +}; + +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; +} diff --git a/drivers/base/firmware_loader/sysfs.h b/drivers/base/firmware_loader/sysfs.h new file mode 100644 index 000000000000..b7b5c1766052 --- /dev/null +++ b/drivers/base/firmware_loader/sysfs.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __FIRMWARE_SYSFS_H +#define __FIRMWARE_SYSFS_H + +#include + +MODULE_IMPORT_NS(FIRMWARE_LOADER_PRIVATE); + +extern struct firmware_fallback_config fw_fallback_config; + +#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; +} + +int register_sysfs_loader(void); +void unregister_sysfs_loader(void); +#ifdef 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_SYSCTL */ +#else /* CONFIG_FW_LOADER_USER_HELPER */ +static inline int register_sysfs_loader(void) +{ + return 0; +} + +static inline void unregister_sysfs_loader(void) +{ +} +#endif /* CONFIG_FW_LOADER_USER_HELPER */ + +struct fw_sysfs { + bool nowait; + struct device dev; + struct fw_priv *fw_priv; + struct firmware *fw; +}; + +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); + +#endif /* __FIRMWARE_SYSFS_H */ -- cgit v1.2.3 From 97730bbb242cde22b7140acd202ffd88823886c9 Mon Sep 17 00:00:00 2001 From: Russ Weight Date: Thu, 21 Apr 2022 14:22:00 -0700 Subject: 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 Reviewed-by: Tianfei zhang Tested-by: Matthew Gerlach Signed-off-by: Russ Weight Link: https://lore.kernel.org/r/20220421212204.36052-5-russell.h.weight@intel.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-firmware | 32 +++ Documentation/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 (limited to 'drivers/base/firmware_loader') 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 +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 +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 +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 d13e4383ea0c..7e663dd972a7 100644 --- a/drivers/base/firmware_loader/Kconfig +++ b/drivers/base/firmware_loader/Kconfig @@ -202,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 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 6ac6c7753acc..9a8579e56c26 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -92,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; @@ -164,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; @@ -225,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); 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 #include -#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 @@ out: 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 +#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 +#include +#include + +#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 + +/** + * 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 -- cgit v1.2.3 From 536fd8184b7dfa30e28e5b459e7c5c91c3a8063f Mon Sep 17 00:00:00 2001 From: Russ Weight Date: Thu, 21 Apr 2022 14:22:01 -0700 Subject: firmware_loader: Add sysfs nodes to monitor fw_upload Add additional sysfs nodes to monitor the transfer of firmware upload data to the target device: cancel: Write 1 to cancel the data transfer error: Display error status for a failed firmware upload remaining_size: Display the remaining amount of data to be transferred status: Display the progress of the firmware upload Reviewed-by: Luis Chamberlain Reviewed-by: Tianfei zhang Tested-by: Matthew Gerlach Signed-off-by: Russ Weight Link: https://lore.kernel.org/r/20220421212204.36052-6-russell.h.weight@intel.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-firmware | 45 +++++++++ Documentation/driver-api/firmware/fw_upload.rst | 23 ++++- drivers/base/firmware_loader/sysfs.c | 9 ++ drivers/base/firmware_loader/sysfs_upload.c | 121 ++++++++++++++++++++++++ drivers/base/firmware_loader/sysfs_upload.h | 5 + 5 files changed, 201 insertions(+), 2 deletions(-) (limited to 'drivers/base/firmware_loader') diff --git a/Documentation/ABI/testing/sysfs-class-firmware b/Documentation/ABI/testing/sysfs-class-firmware index 18336c23b70d..978d3d500400 100644 --- a/Documentation/ABI/testing/sysfs-class-firmware +++ b/Documentation/ABI/testing/sysfs-class-firmware @@ -10,6 +10,30 @@ Description: The data sysfs file is used for firmware-fallback and for 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 +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 +Description: Read-only. Returns a string describing a failed firmware + upload. This string will be in the form of :, + where will be one of the status strings described + for the status sysfs file and 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 @@ -22,6 +46,27 @@ Description: The loading sysfs file is used for both firmware-fallback and 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 +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 +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 diff --git a/Documentation/driver-api/firmware/fw_upload.rst b/Documentation/driver-api/firmware/fw_upload.rst index afbd8baca0d7..76922591e446 100644 --- a/Documentation/driver-api/firmware/fw_upload.rst +++ b/Documentation/driver-api/firmware/fw_upload.rst @@ -9,7 +9,8 @@ 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. +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 ============================ @@ -93,7 +94,9 @@ Firmware Upload Ops Firmware Upload Progress Codes ------------------------------ -The following progress codes are used internally by the firmware loader: +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 @@ -105,3 +108,19 @@ 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 diff --git a/drivers/base/firmware_loader/sysfs.c b/drivers/base/firmware_loader/sysfs.c index 4a956cc3b7ea..c09fcebeada9 100644 --- a/drivers/base/firmware_loader/sysfs.c +++ b/drivers/base/firmware_loader/sysfs.c @@ -371,6 +371,12 @@ static struct bin_attribute firmware_attr_data = { 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 }; @@ -382,6 +388,9 @@ static struct bin_attribute *fw_dev_bin_attrs[] = { 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[] = { diff --git a/drivers/base/firmware_loader/sysfs_upload.c b/drivers/base/firmware_loader/sysfs_upload.c index 0a6450d1974f..c504dae00dbe 100644 --- a/drivers/base/firmware_loader/sysfs_upload.c +++ b/drivers/base/firmware_loader/sysfs_upload.c @@ -11,6 +11,127 @@ * 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) { diff --git a/drivers/base/firmware_loader/sysfs_upload.h b/drivers/base/firmware_loader/sysfs_upload.h index 18bd4d99f064..9edd47d3f36a 100644 --- a/drivers/base/firmware_loader/sysfs_upload.h +++ b/drivers/base/firmware_loader/sysfs_upload.h @@ -37,6 +37,11 @@ struct fw_upload_priv { }; #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 -- cgit v1.2.3 From 4388f887b857de8576a8bf7fefc1202dc7dd10df Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 27 Apr 2022 16:19:05 +0200 Subject: Revert "firmware_loader: use kernel credentials when reading firmware" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 3677563eb8731e1ad5970e3e57f74e5f9d63502a as it leaks memory :( Reported-by: Qian Cai Link: https://lore.kernel.org/r/20220427135823.GD71@qian Cc: Thiébaud Weksteen Cc: Luis Chamberlain Cc: John Stultz Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/main.c | 16 ---------------- 1 file changed, 16 deletions(-) (limited to 'drivers/base/firmware_loader') diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 9a8579e56c26..921d955b7208 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -798,8 +798,6 @@ _request_firmware(const struct firmware **firmware_p, const char *name, size_t offset, u32 opt_flags) { struct firmware *fw = NULL; - struct cred *kern_cred = NULL; - const struct cred *old_cred; bool nondirect = false; int ret; @@ -816,18 +814,6 @@ _request_firmware(const struct firmware **firmware_p, const char *name, if (ret <= 0) /* error or already assigned */ goto out; - /* - * We are about to try to access the firmware file. Because we may have been - * called by a driver when serving an unrelated request from userland, we use - * the kernel credentials to read the file. - */ - kern_cred = prepare_kernel_cred(NULL); - if (!kern_cred) { - ret = -ENOMEM; - goto out; - } - old_cred = override_creds(kern_cred); - ret = fw_get_filesystem_firmware(device, fw->priv, "", NULL); /* Only full reads can support decompression, platform, and sysfs. */ @@ -858,8 +844,6 @@ _request_firmware(const struct firmware **firmware_p, const char *name, } else ret = assign_fw(fw, device); - revert_creds(old_cred); - out: if (ret < 0) { fw_abort_batch_reqs(fw); -- cgit v1.2.3 From bc187f6f8d12568413656e2e5b0d51a001e21915 Mon Sep 17 00:00:00 2001 From: Russ Weight Date: Tue, 26 Apr 2022 13:03:55 -0700 Subject: firmware_loader: Fix configs for sysfs split Fix the CONFIGs around register_sysfs_loader(), unregister_sysfs_loader(), register_firmware_config_sysctl(), and unregister_firmware_config_sysctl(). The full definitions of the register_sysfs_loader() and unregister_sysfs_loader() functions should be used whenever CONFIG_FW_LOADER_SYSFS is defined. The register_firmware_config_sysctl() and unregister_firmware_config_sysctl() functions should be stubbed out unless CONFIG_FW_LOADER_USER_HELPER CONFIG_SYSCTL are both defined. Signed-off-by: Russ Weight Link: https://lore.kernel.org/r/20220426200356.126085-2-russell.h.weight@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/sysfs.c | 2 -- drivers/base/firmware_loader/sysfs.h | 10 ++++++---- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/base/firmware_loader') diff --git a/drivers/base/firmware_loader/sysfs.c b/drivers/base/firmware_loader/sysfs.c index c09fcebeada9..eb7d9322a56e 100644 --- a/drivers/base/firmware_loader/sysfs.c +++ b/drivers/base/firmware_loader/sysfs.c @@ -110,7 +110,6 @@ static struct class firmware_class = { .dev_release = fw_dev_release, }; -#ifdef CONFIG_FW_LOADER_USER_HELPER int register_sysfs_loader(void) { int ret = class_register(&firmware_class); @@ -125,7 +124,6 @@ void unregister_sysfs_loader(void) unregister_firmware_config_sysctl(); class_unregister(&firmware_class); } -#endif static ssize_t firmware_loading_show(struct device *dev, struct device_attribute *attr, char *buf) diff --git a/drivers/base/firmware_loader/sysfs.h b/drivers/base/firmware_loader/sysfs.h index c21bcfe374ff..e6c487937817 100644 --- a/drivers/base/firmware_loader/sysfs.h +++ b/drivers/base/firmware_loader/sysfs.h @@ -46,10 +46,12 @@ 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); -#ifdef CONFIG_SYSCTL +#if defined(CONFIG_FW_LOADER_USER_HELPER) && defined(CONFIG_SYSCTL) int register_firmware_config_sysctl(void); void unregister_firmware_config_sysctl(void); #else @@ -59,8 +61,8 @@ static inline int register_firmware_config_sysctl(void) } static inline void unregister_firmware_config_sysctl(void) { } -#endif /* CONFIG_SYSCTL */ -#else /* CONFIG_FW_LOADER_USER_HELPER */ +#endif /* CONFIG_FW_LOADER_USER_HELPER && CONFIG_SYSCTL */ +#else /* CONFIG_FW_LOADER_SYSFS */ static inline int register_sysfs_loader(void) { return 0; @@ -69,7 +71,7 @@ static inline int register_sysfs_loader(void) static inline void unregister_sysfs_loader(void) { } -#endif /* CONFIG_FW_LOADER_USER_HELPER */ +#endif /* CONFIG_FW_LOADER_SYSFS */ struct fw_sysfs { bool nowait; -- cgit v1.2.3 From f8ae07f4b8bfde0f33761e1a1aaee45a4e85e9d6 Mon Sep 17 00:00:00 2001 From: Russ Weight Date: Tue, 26 Apr 2022 13:03:56 -0700 Subject: firmware_loader: Move definitions from sysfs_upload.h to sysfs.h Move definitions required by sysfs.c from sysfs_upload.h to sysfs.h so that sysfs.c does not need to include sysfs_upload.h. Signed-off-by: Russ Weight Link: https://lore.kernel.org/r/20220426200356.126085-3-russell.h.weight@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/sysfs.c | 1 - drivers/base/firmware_loader/sysfs.h | 15 +++++++++++++++ drivers/base/firmware_loader/sysfs_upload.c | 1 - drivers/base/firmware_loader/sysfs_upload.h | 23 +++++------------------ 4 files changed, 20 insertions(+), 20 deletions(-) (limited to 'drivers/base/firmware_loader') diff --git a/drivers/base/firmware_loader/sysfs.c b/drivers/base/firmware_loader/sysfs.c index eb7d9322a56e..5b0b85b70b6f 100644 --- a/drivers/base/firmware_loader/sysfs.c +++ b/drivers/base/firmware_loader/sysfs.c @@ -7,7 +7,6 @@ #include #include "sysfs.h" -#include "sysfs_upload.h" /* * sysfs support for firmware loader diff --git a/drivers/base/firmware_loader/sysfs.h b/drivers/base/firmware_loader/sysfs.h index e6c487937817..5d8ff1675c79 100644 --- a/drivers/base/firmware_loader/sysfs.h +++ b/drivers/base/firmware_loader/sysfs.h @@ -99,4 +99,19 @@ 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 */ diff --git a/drivers/base/firmware_loader/sysfs_upload.c b/drivers/base/firmware_loader/sysfs_upload.c index c504dae00dbe..8cdcf3516c7e 100644 --- a/drivers/base/firmware_loader/sysfs_upload.c +++ b/drivers/base/firmware_loader/sysfs_upload.c @@ -4,7 +4,6 @@ #include #include -#include "sysfs.h" #include "sysfs_upload.h" /* diff --git a/drivers/base/firmware_loader/sysfs_upload.h b/drivers/base/firmware_loader/sysfs_upload.h index 9edd47d3f36a..31931ff7808a 100644 --- a/drivers/base/firmware_loader/sysfs_upload.h +++ b/drivers/base/firmware_loader/sysfs_upload.h @@ -1,9 +1,11 @@ /* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __FIRMWARE_UPLOAD_H -#define __FIRMWARE_UPLOAD_H +#ifndef __SYSFS_UPLOAD_H +#define __SYSFS_UPLOAD_H #include +#include "sysfs.h" + /** * enum fw_upload_prog - firmware upload progress codes * @FW_UPLOAD_PROG_IDLE: there is no firmware upload in progress @@ -36,19 +38,4 @@ struct fw_upload_priv { enum fw_upload_err err_code; /* security manager error code */ }; -#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_UPLOAD_H */ +#endif /* __SYSFS_UPLOAD_H */ -- cgit v1.2.3 From 6370b04f24bc10c1f2056c0f12dd651ac0121a6f Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Mon, 2 May 2022 12:14:56 +0700 Subject: firmware_loader: describe 'module' parameter of firmware_upload_register() Stephen Rothwell reported kernel-doc warning: drivers/base/firmware_loader/sysfs_upload.c:285: warning: Function parameter or member 'module' not described in 'firmware_upload_register' Fix the warning by describing the 'module' parameter. Link: https://lore.kernel.org/linux-next/20220502083658.266d55f8@canb.auug.org.au/ Fixes: 97730bbb242cde ("firmware_loader: Add firmware-upload support") Reported-by: Stephen Rothwell Cc: Russ Weight Cc: Luis Chamberlain Cc: Greg Kroah-Hartman Cc: "Rafael J. Wysocki" Cc: Linux Kernel Mailing List Cc: Linux Next Mailing List Reviewed-by: Russ Weight Signed-off-by: Bagas Sanjaya Link: https://lore.kernel.org/r/20220502051456.30741-1-bagasdotme@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/sysfs_upload.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/base/firmware_loader') diff --git a/drivers/base/firmware_loader/sysfs_upload.c b/drivers/base/firmware_loader/sysfs_upload.c index 8cdcf3516c7e..87044d52322a 100644 --- a/drivers/base/firmware_loader/sysfs_upload.c +++ b/drivers/base/firmware_loader/sysfs_upload.c @@ -266,6 +266,7 @@ int fw_upload_start(struct fw_sysfs *fw_sysfs) /** * 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 -- cgit v1.2.3