From 29a1c00ce1df8a75bea6e6a86876a10c8dcc2c59 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:42 -0800 Subject: test_firmware: add simple firmware firmware test library We'll expland on this later, for now just add basic module checker. While at it, move this all to use /bin/bash as we'll have much more flexibility with it. Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/firmware/fw_fallback.sh | 7 ++-- tools/testing/selftests/firmware/fw_filesystem.sh | 20 ++--------- tools/testing/selftests/firmware/fw_lib.sh | 44 +++++++++++++++++++++++ 3 files changed, 51 insertions(+), 20 deletions(-) create mode 100755 tools/testing/selftests/firmware/fw_lib.sh diff --git a/tools/testing/selftests/firmware/fw_fallback.sh b/tools/testing/selftests/firmware/fw_fallback.sh index 722cad91df74..755147a8c967 100755 --- a/tools/testing/selftests/firmware/fw_fallback.sh +++ b/tools/testing/selftests/firmware/fw_fallback.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # This validates that the kernel will fall back to using the fallback mechanism # to load firmware it can't find on disk itself. We must request a firmware @@ -6,9 +6,10 @@ # won't find so that we can do the load ourself manually. set -e -modprobe test_firmware +TEST_DIR=$(dirname $0) +source $TEST_DIR/fw_lib.sh -DIR=/sys/devices/virtual/misc/test_firmware +check_mods # CONFIG_FW_LOADER_USER_HELPER has a sysfs class under /sys/class/firmware/ # These days no one enables CONFIG_FW_LOADER_USER_HELPER so check for that diff --git a/tools/testing/selftests/firmware/fw_filesystem.sh b/tools/testing/selftests/firmware/fw_filesystem.sh index f9508e1a4058..748f2f5737e9 100755 --- a/tools/testing/selftests/firmware/fw_filesystem.sh +++ b/tools/testing/selftests/firmware/fw_filesystem.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # This validates that the kernel will load firmware out of its list of # firmware locations on disk. Since the user helper does similar work, @@ -6,24 +6,10 @@ # know so we can be sure we're not accidentally testing the user helper. set -e -DIR=/sys/devices/virtual/misc/test_firmware TEST_DIR=$(dirname $0) +source $TEST_DIR/fw_lib.sh -test_modprobe() -{ - if [ ! -d $DIR ]; then - echo "$0: $DIR not present" - echo "You must have the following enabled in your kernel:" - cat $TEST_DIR/config - exit 1 - fi -} - -trap "test_modprobe" EXIT - -if [ ! -d $DIR ]; then - modprobe test_firmware -fi +check_mods # CONFIG_FW_LOADER_USER_HELPER has a sysfs class under /sys/class/firmware/ # These days most distros enable CONFIG_FW_LOADER_USER_HELPER but disable diff --git a/tools/testing/selftests/firmware/fw_lib.sh b/tools/testing/selftests/firmware/fw_lib.sh new file mode 100755 index 000000000000..c14bbca7ecf9 --- /dev/null +++ b/tools/testing/selftests/firmware/fw_lib.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# Library of helpers for test scripts. +set -e + +DIR=/sys/devices/virtual/misc/test_firmware + +PROC_CONFIG="/proc/config.gz" +TEST_DIR=$(dirname $0) + +print_reqs_exit() +{ + echo "You must have the following enabled in your kernel:" >&2 + cat $TEST_DIR/config >&2 + exit 1 +} + +test_modprobe() +{ + if [ ! -d $DIR ]; then + print_reqs_exit + fi +} + +check_mods() +{ + trap "test_modprobe" EXIT + if [ ! -d $DIR ]; then + modprobe test_firmware + fi + if [ ! -f $PROC_CONFIG ]; then + if modprobe configs 2>/dev/null; then + echo "Loaded configs module" + if [ ! -f $PROC_CONFIG ]; then + echo "You must have the following enabled in your kernel:" >&2 + cat $TEST_DIR/config >&2 + echo "Resorting to old heuristics" >&2 + fi + else + echo "Failed to load configs module, using old heuristics" >&2 + fi + fi +} -- cgit v1.2.3 From ef557787f4f04b62d7a50101154ffe1614b88a7a Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:43 -0800 Subject: test_firmware: enable custom fallback testing on limited kernel configs When a kernel is not built with: CONFIG_HAS_FW_LOADER_USER_HELPER_FALLBACK=y We don't currently enable testing fw_fallback.sh. For kernels that still enable the fallback mechanism, its possible to use the async request firmware API call request_firmware_nowait() using the custom interface to use the fallback mechanism, so we should be able to test this but we currently cannot. We can enable testing without CONFIG_HAS_FW_LOADER_USER_HELPER_FALLBACK=y by relying on /proc/config.gz (CONFIG_IKCONFIG_PROC), if present. If you don't have this we'll have no option but to rely on old heuristics for now. We stuff the new kconfig_has() helper into our shared library as we'll later expando on its use elsewhere. Acked-by: Kees Cook Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/firmware/config | 4 ++++ tools/testing/selftests/firmware/fw_fallback.sh | 6 +++++- tools/testing/selftests/firmware/fw_lib.sh | 24 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/firmware/config b/tools/testing/selftests/firmware/config index c8137f70e291..bf634dda0720 100644 --- a/tools/testing/selftests/firmware/config +++ b/tools/testing/selftests/firmware/config @@ -1 +1,5 @@ CONFIG_TEST_FIRMWARE=y +CONFIG_FW_LOADER=y +CONFIG_FW_LOADER_USER_HELPER=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y diff --git a/tools/testing/selftests/firmware/fw_fallback.sh b/tools/testing/selftests/firmware/fw_fallback.sh index 755147a8c967..bf850050e5e9 100755 --- a/tools/testing/selftests/firmware/fw_fallback.sh +++ b/tools/testing/selftests/firmware/fw_fallback.sh @@ -15,6 +15,7 @@ check_mods # These days no one enables CONFIG_FW_LOADER_USER_HELPER so check for that # as an indicator for CONFIG_FW_LOADER_USER_HELPER. HAS_FW_LOADER_USER_HELPER=$(if [ -d /sys/class/firmware/ ]; then echo yes; else echo no; fi) +HAS_FW_LOADER_USER_HELPER_FALLBACK=$(kconfig_has CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y) if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then OLD_TIMEOUT=$(cat /sys/class/firmware/timeout) @@ -287,7 +288,10 @@ run_sysfs_custom_load_tests() fi } -run_sysfs_main_tests +if [ "$HAS_FW_LOADER_USER_HELPER_FALLBACK" = "yes" ]; then + run_sysfs_main_tests +fi + run_sysfs_custom_load_tests exit 0 diff --git a/tools/testing/selftests/firmware/fw_lib.sh b/tools/testing/selftests/firmware/fw_lib.sh index c14bbca7ecf9..467567c758b9 100755 --- a/tools/testing/selftests/firmware/fw_lib.sh +++ b/tools/testing/selftests/firmware/fw_lib.sh @@ -42,3 +42,27 @@ check_mods() fi fi } + +kconfig_has() +{ + if [ -f $PROC_CONFIG ]; then + if zgrep -q $1 $PROC_CONFIG 2>/dev/null; then + echo "yes" + else + echo "no" + fi + else + # We currently don't have easy heuristics to infer this + # so best we can do is just try to use the kernel assuming + # you had enabled it. This matches the old behaviour. + if [ "$1" = "CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y" ]; then + echo "yes" + elif [ "$1" = "CONFIG_FW_LOADER_USER_HELPER=y" ]; then + if [ -d /sys/class/firmware/ ]; then + echo yes + else + echo no + fi + fi + fi +} -- cgit v1.2.3 From 9cc853203c3bf4d3b0c466c5525750c071624b05 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:44 -0800 Subject: test_firmware: replace syfs fallback check with kconfig_has helper Now that we have a kconfig checker just use that instead of relying on testing a sysfs directory being present, since our requirements are spelled out. Acked-by: Kees Cook Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/firmware/fw_fallback.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tools/testing/selftests/firmware/fw_fallback.sh b/tools/testing/selftests/firmware/fw_fallback.sh index bf850050e5e9..323c921a2469 100755 --- a/tools/testing/selftests/firmware/fw_fallback.sh +++ b/tools/testing/selftests/firmware/fw_fallback.sh @@ -11,10 +11,7 @@ source $TEST_DIR/fw_lib.sh check_mods -# CONFIG_FW_LOADER_USER_HELPER has a sysfs class under /sys/class/firmware/ -# These days no one enables CONFIG_FW_LOADER_USER_HELPER so check for that -# as an indicator for CONFIG_FW_LOADER_USER_HELPER. -HAS_FW_LOADER_USER_HELPER=$(if [ -d /sys/class/firmware/ ]; then echo yes; else echo no; fi) +HAS_FW_LOADER_USER_HELPER=$(kconfig_has CONFIG_FW_LOADER_USER_HELPER=y) HAS_FW_LOADER_USER_HELPER_FALLBACK=$(kconfig_has CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y) if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then -- cgit v1.2.3 From ad4365f138363cc9c2271d4181bc35e3f06551de Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:45 -0800 Subject: firmware: enable to split firmware_class into separate target files The firmware loader code has grown quite a bit over the years. The practice of stuffing everything we need into one file makes the code hard to follow. In order to split the firmware loader code into different components we must pick a module name and a first object target file. We must keep the firmware_class name to remain compatible with scripts which have been relying on the sysfs loader path for years, so the old module name stays. We can however rename the C file without affecting the module name. The firmware_class used to represent the idea that the code was a simple sysfs firmware loader, provided by the struct class firmware_class. The sysfs firmware loader used to be the default, today its only the fallback mechanism. This only renames the target code then to make emphasis of what the code does these days. With this change new features can also use a new object files. Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/Makefile | 1 + drivers/base/firmware_class.c | 1940 ---------------------------------------- drivers/base/firmware_loader.c | 1940 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1941 insertions(+), 1940 deletions(-) delete mode 100644 drivers/base/firmware_class.c create mode 100644 drivers/base/firmware_loader.c diff --git a/drivers/base/Makefile b/drivers/base/Makefile index e32a52490051..f261143fafbf 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_HAS_DMA) += dma-mapping.o obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.o obj-$(CONFIG_ISA_BUS_API) += isa.o obj-$(CONFIG_FW_LOADER) += firmware_class.o +firmware_class-objs := firmware_loader.o obj-$(CONFIG_NUMA) += node.o obj-$(CONFIG_MEMORY_HOTPLUG_SPARSE) += memory.o ifeq ($(CONFIG_SYSFS),y) diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c deleted file mode 100644 index 7dd36ace6152..000000000000 --- a/drivers/base/firmware_class.c +++ /dev/null @@ -1,1940 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * firmware_class.c - Multi purpose firmware loading support - * - * Copyright (c) 2003 Manuel Estrada Sainz - * - * Please see Documentation/firmware_class/ for more information. - * - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "base.h" - -MODULE_AUTHOR("Manuel Estrada Sainz"); -MODULE_DESCRIPTION("Multi purpose firmware loading support"); -MODULE_LICENSE("GPL"); - -enum fw_status { - FW_STATUS_UNKNOWN, - FW_STATUS_LOADING, - FW_STATUS_DONE, - FW_STATUS_ABORTED, -}; - -/* - * Concurrent request_firmware() for the same firmware need to be - * serialized. struct fw_state is simple state machine which hold the - * state of the firmware loading. - */ -struct fw_state { - struct completion completion; - enum fw_status status; -}; - -/* firmware behavior options */ -#define FW_OPT_UEVENT (1U << 0) -#define FW_OPT_NOWAIT (1U << 1) -#define FW_OPT_USERHELPER (1U << 2) -#define FW_OPT_NO_WARN (1U << 3) -#define FW_OPT_NOCACHE (1U << 4) -#define FW_OPT_NOFALLBACK (1U << 5) - -struct firmware_cache { - /* firmware_buf instance will be added into the below list */ - spinlock_t lock; - struct list_head head; - int state; - -#ifdef CONFIG_PM_SLEEP - /* - * Names of firmware images which have been cached successfully - * will be added into the below list so that device uncache - * helper can trace which firmware images have been cached - * before. - */ - spinlock_t name_lock; - struct list_head fw_names; - - struct delayed_work work; - - struct notifier_block pm_notify; -#endif -}; - -struct fw_priv { - struct kref ref; - struct list_head list; - struct firmware_cache *fwc; - struct fw_state fw_st; - void *data; - size_t size; - size_t allocated_size; -#ifdef CONFIG_FW_LOADER_USER_HELPER - bool is_paged_buf; - bool need_uevent; - struct page **pages; - int nr_pages; - int page_array_size; - struct list_head pending_list; -#endif - const char *fw_name; -}; - -struct fw_cache_entry { - struct list_head list; - const char *name; -}; - -struct fw_name_devm { - unsigned long magic; - const char *name; -}; - -static inline struct fw_priv *to_fw_priv(struct kref *ref) -{ - return container_of(ref, struct fw_priv, ref); -} - -#define FW_LOADER_NO_CACHE 0 -#define FW_LOADER_START_CACHE 1 - -/* fw_lock could be moved to 'struct fw_sysfs' but since it is just - * guarding for corner cases a global lock should be OK */ -static DEFINE_MUTEX(fw_lock); - -static struct firmware_cache fw_cache; - -/* Builtin firmware support */ - -#ifdef CONFIG_FW_LOADER - -extern struct builtin_fw __start_builtin_fw[]; -extern struct builtin_fw __end_builtin_fw[]; - -static void fw_copy_to_prealloc_buf(struct firmware *fw, - void *buf, size_t size) -{ - if (!buf || size < fw->size) - return; - memcpy(buf, fw->data, fw->size); -} - -static bool fw_get_builtin_firmware(struct firmware *fw, const char *name, - void *buf, size_t size) -{ - struct builtin_fw *b_fw; - - for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++) { - if (strcmp(name, b_fw->name) == 0) { - fw->size = b_fw->size; - fw->data = b_fw->data; - fw_copy_to_prealloc_buf(fw, buf, size); - - return true; - } - } - - return false; -} - -static bool fw_is_builtin_firmware(const struct firmware *fw) -{ - struct builtin_fw *b_fw; - - for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++) - if (fw->data == b_fw->data) - return true; - - return false; -} - -#else /* Module case - no builtin firmware support */ - -static inline bool fw_get_builtin_firmware(struct firmware *fw, - const char *name, void *buf, - size_t size) -{ - return false; -} - -static inline bool fw_is_builtin_firmware(const struct firmware *fw) -{ - return false; -} -#endif - -static int loading_timeout = 60; /* In seconds */ - -static inline long firmware_loading_timeout(void) -{ - return loading_timeout > 0 ? loading_timeout * HZ : MAX_JIFFY_OFFSET; -} - -static void fw_state_init(struct fw_priv *fw_priv) -{ - struct fw_state *fw_st = &fw_priv->fw_st; - - init_completion(&fw_st->completion); - fw_st->status = FW_STATUS_UNKNOWN; -} - -static int __fw_state_wait_common(struct fw_priv *fw_priv, long timeout) -{ - struct fw_state *fw_st = &fw_priv->fw_st; - long ret; - - ret = wait_for_completion_killable_timeout(&fw_st->completion, timeout); - if (ret != 0 && fw_st->status == FW_STATUS_ABORTED) - return -ENOENT; - if (!ret) - return -ETIMEDOUT; - - return ret < 0 ? ret : 0; -} - -static void __fw_state_set(struct fw_priv *fw_priv, - enum fw_status status) -{ - struct fw_state *fw_st = &fw_priv->fw_st; - - WRITE_ONCE(fw_st->status, status); - - if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED) - complete_all(&fw_st->completion); -} - -static inline void fw_state_start(struct fw_priv *fw_priv) -{ - __fw_state_set(fw_priv, FW_STATUS_LOADING); -} - -static inline void fw_state_done(struct fw_priv *fw_priv) -{ - __fw_state_set(fw_priv, FW_STATUS_DONE); -} - -static inline void fw_state_aborted(struct fw_priv *fw_priv) -{ - __fw_state_set(fw_priv, FW_STATUS_ABORTED); -} - -static inline int fw_state_wait(struct fw_priv *fw_priv) -{ - return __fw_state_wait_common(fw_priv, MAX_SCHEDULE_TIMEOUT); -} - -static bool __fw_state_check(struct fw_priv *fw_priv, - enum fw_status status) -{ - struct fw_state *fw_st = &fw_priv->fw_st; - - return fw_st->status == status; -} - -static inline bool fw_state_is_aborted(struct fw_priv *fw_priv) -{ - return __fw_state_check(fw_priv, FW_STATUS_ABORTED); -} - -#ifdef CONFIG_FW_LOADER_USER_HELPER - -static inline bool fw_sysfs_done(struct fw_priv *fw_priv) -{ - return __fw_state_check(fw_priv, FW_STATUS_DONE); -} - -static inline bool fw_sysfs_loading(struct fw_priv *fw_priv) -{ - return __fw_state_check(fw_priv, FW_STATUS_LOADING); -} - -static inline int fw_sysfs_wait_timeout(struct fw_priv *fw_priv, long timeout) -{ - return __fw_state_wait_common(fw_priv, timeout); -} - -#endif /* CONFIG_FW_LOADER_USER_HELPER */ - -static int fw_cache_piggyback_on_request(const char *name); - -static struct fw_priv *__allocate_fw_priv(const char *fw_name, - struct firmware_cache *fwc, - void *dbuf, size_t size) -{ - struct fw_priv *fw_priv; - - fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC); - if (!fw_priv) - return NULL; - - fw_priv->fw_name = kstrdup_const(fw_name, GFP_ATOMIC); - if (!fw_priv->fw_name) { - kfree(fw_priv); - return NULL; - } - - kref_init(&fw_priv->ref); - fw_priv->fwc = fwc; - fw_priv->data = dbuf; - fw_priv->allocated_size = size; - fw_state_init(fw_priv); -#ifdef CONFIG_FW_LOADER_USER_HELPER - INIT_LIST_HEAD(&fw_priv->pending_list); -#endif - - pr_debug("%s: fw-%s fw_priv=%p\n", __func__, fw_name, fw_priv); - - return fw_priv; -} - -static struct fw_priv *__lookup_fw_priv(const char *fw_name) -{ - struct fw_priv *tmp; - struct firmware_cache *fwc = &fw_cache; - - list_for_each_entry(tmp, &fwc->head, list) - if (!strcmp(tmp->fw_name, fw_name)) - return tmp; - return NULL; -} - -/* 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) -{ - struct fw_priv *tmp; - - spin_lock(&fwc->lock); - tmp = __lookup_fw_priv(fw_name); - if (tmp) { - kref_get(&tmp->ref); - spin_unlock(&fwc->lock); - *fw_priv = tmp; - pr_debug("batched request - sharing the same struct fw_priv and lookup for multiple requests\n"); - return 1; - } - tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size); - if (tmp) - list_add(&tmp->list, &fwc->head); - spin_unlock(&fwc->lock); - - *fw_priv = tmp; - - return tmp ? 0 : -ENOMEM; -} - -static void __free_fw_priv(struct kref *ref) - __releases(&fwc->lock) -{ - struct fw_priv *fw_priv = to_fw_priv(ref); - struct firmware_cache *fwc = fw_priv->fwc; - - 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); - - list_del(&fw_priv->list); - spin_unlock(&fwc->lock); - -#ifdef CONFIG_FW_LOADER_USER_HELPER - if (fw_priv->is_paged_buf) { - int i; - vunmap(fw_priv->data); - for (i = 0; i < fw_priv->nr_pages; i++) - __free_page(fw_priv->pages[i]); - vfree(fw_priv->pages); - } else -#endif - if (!fw_priv->allocated_size) - vfree(fw_priv->data); - kfree_const(fw_priv->fw_name); - kfree(fw_priv); -} - -static void free_fw_priv(struct fw_priv *fw_priv) -{ - struct firmware_cache *fwc = fw_priv->fwc; - spin_lock(&fwc->lock); - if (!kref_put(&fw_priv->ref, __free_fw_priv)) - spin_unlock(&fwc->lock); -} - -/* direct firmware loading support */ -static char fw_path_para[256]; -static const char * const fw_path[] = { - fw_path_para, - "/lib/firmware/updates/" UTS_RELEASE, - "/lib/firmware/updates", - "/lib/firmware/" UTS_RELEASE, - "/lib/firmware" -}; - -/* - * Typical usage is that passing 'firmware_class.path=$CUSTOMIZED_PATH' - * from kernel command line because firmware_class is generally built in - * kernel instead of module. - */ -module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644); -MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path"); - -static int -fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) -{ - loff_t size; - int i, len; - int rc = -ENOENT; - char *path; - enum kernel_read_file_id id = READING_FIRMWARE; - size_t msize = INT_MAX; - - /* Already populated data member means we're loading into a buffer */ - if (fw_priv->data) { - id = READING_FIRMWARE_PREALLOC_BUFFER; - msize = fw_priv->allocated_size; - } - - path = __getname(); - if (!path) - return -ENOMEM; - - for (i = 0; i < ARRAY_SIZE(fw_path); i++) { - /* skip the unset customized path */ - if (!fw_path[i][0]) - continue; - - len = snprintf(path, PATH_MAX, "%s/%s", - fw_path[i], fw_priv->fw_name); - if (len >= PATH_MAX) { - rc = -ENAMETOOLONG; - break; - } - - fw_priv->size = 0; - rc = kernel_read_file_from_path(path, &fw_priv->data, &size, - msize, id); - if (rc) { - if (rc == -ENOENT) - dev_dbg(device, "loading %s failed with error %d\n", - path, rc); - else - dev_warn(device, "loading %s failed with error %d\n", - path, rc); - continue; - } - dev_dbg(device, "direct-loading %s\n", fw_priv->fw_name); - fw_priv->size = size; - fw_state_done(fw_priv); - break; - } - __putname(path); - - return rc; -} - -/* firmware holds the ownership of pages */ -static void firmware_free_data(const struct firmware *fw) -{ - /* Loaded directly? */ - if (!fw->priv) { - vfree(fw->data); - return; - } - free_fw_priv(fw->priv); -} - -/* store the pages buffer info firmware from buf */ -static void fw_set_page_data(struct fw_priv *fw_priv, struct firmware *fw) -{ - fw->priv = fw_priv; -#ifdef CONFIG_FW_LOADER_USER_HELPER - fw->pages = fw_priv->pages; -#endif - fw->size = fw_priv->size; - fw->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); -} - -#ifdef CONFIG_PM_SLEEP -static void fw_name_devm_release(struct device *dev, void *res) -{ - struct fw_name_devm *fwn = res; - - if (fwn->magic == (unsigned long)&fw_cache) - pr_debug("%s: fw_name-%s devm-%p released\n", - __func__, fwn->name, res); - kfree_const(fwn->name); -} - -static int fw_devm_match(struct device *dev, void *res, - void *match_data) -{ - struct fw_name_devm *fwn = res; - - return (fwn->magic == (unsigned long)&fw_cache) && - !strcmp(fwn->name, match_data); -} - -static struct fw_name_devm *fw_find_devm_name(struct device *dev, - const char *name) -{ - struct fw_name_devm *fwn; - - fwn = devres_find(dev, fw_name_devm_release, - fw_devm_match, (void *)name); - return fwn; -} - -/* add firmware name into devres list */ -static int fw_add_devm_name(struct device *dev, const char *name) -{ - struct fw_name_devm *fwn; - - fwn = fw_find_devm_name(dev, name); - if (fwn) - return 1; - - fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm), - GFP_KERNEL); - if (!fwn) - return -ENOMEM; - fwn->name = kstrdup_const(name, GFP_KERNEL); - if (!fwn->name) { - devres_free(fwn); - return -ENOMEM; - } - - fwn->magic = (unsigned long)&fw_cache; - devres_add(dev, fwn); - - return 0; -} -#else -static int fw_add_devm_name(struct device *dev, const char *name) -{ - return 0; -} -#endif - -static int assign_fw(struct firmware *fw, struct device *device, - unsigned int opt_flags) -{ - struct fw_priv *fw_priv = fw->priv; - - mutex_lock(&fw_lock); - if (!fw_priv->size || fw_state_is_aborted(fw_priv)) { - mutex_unlock(&fw_lock); - return -ENOENT; - } - - /* - * add firmware name into devres list so that we can auto cache - * and uncache firmware for device. - * - * device may has been deleted already, but the problem - * should be fixed in devres or driver core. - */ - /* don't cache firmware handled without uevent */ - if (device && (opt_flags & FW_OPT_UEVENT) && - !(opt_flags & FW_OPT_NOCACHE)) - fw_add_devm_name(device, fw_priv->fw_name); - - /* - * After caching firmware image is started, let it piggyback - * on request firmware. - */ - if (!(opt_flags & FW_OPT_NOCACHE) && - fw_priv->fwc->state == FW_LOADER_START_CACHE) { - if (fw_cache_piggyback_on_request(fw_priv->fw_name)) - kref_get(&fw_priv->ref); - } - - /* pass the pages buffer to driver at the last minute */ - fw_set_page_data(fw_priv, fw); - mutex_unlock(&fw_lock); - return 0; -} - -/* - * user-mode helper code - */ -#ifdef CONFIG_FW_LOADER_USER_HELPER -struct fw_sysfs { - bool nowait; - struct device dev; - struct fw_priv *fw_priv; - struct firmware *fw; -}; - -static struct fw_sysfs *to_fw_sysfs(struct device *dev) -{ - return container_of(dev, struct fw_sysfs, dev); -} - -static void __fw_load_abort(struct fw_priv *fw_priv) -{ - /* - * There is a small window in which user can write to 'loading' - * between loading done and disappearance of 'loading' - */ - if (fw_sysfs_done(fw_priv)) - return; - - list_del_init(&fw_priv->pending_list); - fw_state_aborted(fw_priv); -} - -static void fw_load_abort(struct fw_sysfs *fw_sysfs) -{ - struct fw_priv *fw_priv = fw_sysfs->fw_priv; - - __fw_load_abort(fw_priv); -} - -static LIST_HEAD(pending_fw_head); - -static void kill_pending_fw_fallback_reqs(bool only_kill_custom) -{ - struct fw_priv *fw_priv; - struct fw_priv *next; - - mutex_lock(&fw_lock); - list_for_each_entry_safe(fw_priv, next, &pending_fw_head, - pending_list) { - if (!fw_priv->need_uevent || !only_kill_custom) - __fw_load_abort(fw_priv); - } - mutex_unlock(&fw_lock); -} - -static ssize_t timeout_show(struct class *class, struct class_attribute *attr, - char *buf) -{ - return sprintf(buf, "%d\n", loading_timeout); -} - -/** - * firmware_timeout_store - set number of seconds to wait for firmware - * @class: device class pointer - * @attr: device attribute pointer - * @buf: buffer to scan for timeout value - * @count: number of bytes in @buf - * - * Sets the number of seconds to wait for the firmware. Once - * this expires an error will be returned to the driver and no - * firmware will be provided. - * - * Note: zero means 'wait forever'. - **/ -static ssize_t timeout_store(struct class *class, struct class_attribute *attr, - const char *buf, size_t count) -{ - loading_timeout = simple_strtol(buf, NULL, 10); - if (loading_timeout < 0) - loading_timeout = 0; - - return count; -} -static CLASS_ATTR_RW(timeout); - -static struct attribute *firmware_class_attrs[] = { - &class_attr_timeout.attr, - NULL, -}; -ATTRIBUTE_GROUPS(firmware_class); - -static void fw_dev_release(struct device *dev) -{ - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - - kfree(fw_sysfs); -} - -static int do_firmware_uevent(struct fw_sysfs *fw_sysfs, struct kobj_uevent_env *env) -{ - if (add_uevent_var(env, "FIRMWARE=%s", fw_sysfs->fw_priv->fw_name)) - return -ENOMEM; - if (add_uevent_var(env, "TIMEOUT=%i", loading_timeout)) - return -ENOMEM; - if (add_uevent_var(env, "ASYNC=%d", fw_sysfs->nowait)) - return -ENOMEM; - - return 0; -} - -static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env) -{ - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - int err = 0; - - mutex_lock(&fw_lock); - if (fw_sysfs->fw_priv) - err = do_firmware_uevent(fw_sysfs, env); - mutex_unlock(&fw_lock); - return err; -} - -static struct class firmware_class = { - .name = "firmware", - .class_groups = firmware_class_groups, - .dev_uevent = firmware_uevent, - .dev_release = fw_dev_release, -}; - -static inline int register_sysfs_loader(void) -{ - return class_register(&firmware_class); -} - -static inline void unregister_sysfs_loader(void) -{ - class_unregister(&firmware_class); -} - -static ssize_t firmware_loading_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - int loading = 0; - - mutex_lock(&fw_lock); - if (fw_sysfs->fw_priv) - loading = fw_sysfs_loading(fw_sysfs->fw_priv); - mutex_unlock(&fw_lock); - - return sprintf(buf, "%d\n", loading); -} - -/* Some architectures don't have PAGE_KERNEL_RO */ -#ifndef PAGE_KERNEL_RO -#define PAGE_KERNEL_RO PAGE_KERNEL -#endif - -/* one pages buffer should be mapped/unmapped only once */ -static int map_fw_priv_pages(struct fw_priv *fw_priv) -{ - if (!fw_priv->is_paged_buf) - return 0; - - vunmap(fw_priv->data); - fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, 0, - PAGE_KERNEL_RO); - if (!fw_priv->data) - return -ENOMEM; - return 0; -} - -/** - * firmware_loading_store - set value in the 'loading' control file - * @dev: device pointer - * @attr: device attribute pointer - * @buf: buffer to scan for loading control value - * @count: number of bytes in @buf - * - * The relevant values are: - * - * 1: Start a load, discarding any previous partial load. - * 0: Conclude the load and hand the data to the driver code. - * -1: Conclude the load with an error and discard any written data. - **/ -static ssize_t firmware_loading_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - struct fw_priv *fw_priv; - ssize_t written = count; - int loading = simple_strtol(buf, NULL, 10); - int i; - - mutex_lock(&fw_lock); - fw_priv = fw_sysfs->fw_priv; - if (fw_state_is_aborted(fw_priv)) - goto out; - - switch (loading) { - case 1: - /* discarding any previous partial load */ - if (!fw_sysfs_done(fw_priv)) { - for (i = 0; i < fw_priv->nr_pages; i++) - __free_page(fw_priv->pages[i]); - vfree(fw_priv->pages); - fw_priv->pages = NULL; - fw_priv->page_array_size = 0; - fw_priv->nr_pages = 0; - fw_state_start(fw_priv); - } - break; - case 0: - if (fw_sysfs_loading(fw_priv)) { - int rc; - - /* - * Several loading requests may be pending on - * one same firmware buf, so let all requests - * see the mapped 'buf->data' once the loading - * is completed. - * */ - rc = map_fw_priv_pages(fw_priv); - if (rc) - dev_err(dev, "%s: map pages failed\n", - __func__); - else - rc = security_kernel_post_read_file(NULL, - fw_priv->data, fw_priv->size, - READING_FIRMWARE); - - /* - * Same logic as fw_load_abort, only the DONE bit - * is ignored and we set ABORT only on failure. - */ - list_del_init(&fw_priv->pending_list); - if (rc) { - fw_state_aborted(fw_priv); - written = rc; - } else { - fw_state_done(fw_priv); - } - break; - } - /* fallthrough */ - default: - dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); - /* fallthrough */ - case -1: - fw_load_abort(fw_sysfs); - break; - } -out: - mutex_unlock(&fw_lock); - return written; -} - -static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store); - -static void firmware_rw_data(struct fw_priv *fw_priv, char *buffer, - loff_t offset, size_t count, bool read) -{ - if (read) - memcpy(buffer, fw_priv->data + offset, count); - else - memcpy(fw_priv->data + offset, buffer, count); -} - -static void firmware_rw(struct fw_priv *fw_priv, char *buffer, - loff_t offset, size_t count, bool read) -{ - while (count) { - void *page_data; - int page_nr = offset >> PAGE_SHIFT; - int page_ofs = offset & (PAGE_SIZE-1); - int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count); - - page_data = kmap(fw_priv->pages[page_nr]); - - if (read) - memcpy(buffer, page_data + page_ofs, page_cnt); - else - memcpy(page_data + page_ofs, buffer, page_cnt); - - kunmap(fw_priv->pages[page_nr]); - buffer += page_cnt; - offset += page_cnt; - count -= page_cnt; - } -} - -static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t offset, size_t count) -{ - struct device *dev = kobj_to_dev(kobj); - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - struct fw_priv *fw_priv; - ssize_t ret_count; - - mutex_lock(&fw_lock); - fw_priv = fw_sysfs->fw_priv; - if (!fw_priv || fw_sysfs_done(fw_priv)) { - ret_count = -ENODEV; - goto out; - } - if (offset > fw_priv->size) { - ret_count = 0; - goto out; - } - if (count > fw_priv->size - offset) - count = fw_priv->size - offset; - - ret_count = count; - - if (fw_priv->data) - firmware_rw_data(fw_priv, buffer, offset, count, true); - else - firmware_rw(fw_priv, buffer, offset, count, true); - -out: - mutex_unlock(&fw_lock); - return ret_count; -} - -static int fw_realloc_pages(struct fw_sysfs *fw_sysfs, int min_size) -{ - struct fw_priv *fw_priv= fw_sysfs->fw_priv; - int pages_needed = PAGE_ALIGN(min_size) >> PAGE_SHIFT; - - /* If the array of pages is too small, grow it... */ - if (fw_priv->page_array_size < pages_needed) { - int new_array_size = max(pages_needed, - fw_priv->page_array_size * 2); - struct page **new_pages; - - new_pages = vmalloc(new_array_size * sizeof(void *)); - if (!new_pages) { - fw_load_abort(fw_sysfs); - return -ENOMEM; - } - memcpy(new_pages, fw_priv->pages, - fw_priv->page_array_size * sizeof(void *)); - memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) * - (new_array_size - fw_priv->page_array_size)); - vfree(fw_priv->pages); - fw_priv->pages = new_pages; - fw_priv->page_array_size = new_array_size; - } - - while (fw_priv->nr_pages < pages_needed) { - fw_priv->pages[fw_priv->nr_pages] = - alloc_page(GFP_KERNEL | __GFP_HIGHMEM); - - if (!fw_priv->pages[fw_priv->nr_pages]) { - fw_load_abort(fw_sysfs); - return -ENOMEM; - } - fw_priv->nr_pages++; - } - return 0; -} - -/** - * firmware_data_write - write method for firmware - * @filp: open sysfs file - * @kobj: kobject for the device - * @bin_attr: bin_attr structure - * @buffer: buffer being written - * @offset: buffer offset for write in total data store area - * @count: buffer size - * - * Data written to the 'data' attribute will be later handed to - * the driver as a firmware image. - **/ -static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t offset, size_t count) -{ - struct device *dev = kobj_to_dev(kobj); - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - struct fw_priv *fw_priv; - ssize_t retval; - - if (!capable(CAP_SYS_RAWIO)) - return -EPERM; - - mutex_lock(&fw_lock); - fw_priv = fw_sysfs->fw_priv; - if (!fw_priv || fw_sysfs_done(fw_priv)) { - retval = -ENODEV; - goto out; - } - - if (fw_priv->data) { - if (offset + count > fw_priv->allocated_size) { - retval = -ENOMEM; - goto out; - } - firmware_rw_data(fw_priv, buffer, offset, count, false); - retval = count; - } else { - retval = fw_realloc_pages(fw_sysfs, offset + count); - if (retval) - goto out; - - retval = count; - firmware_rw(fw_priv, buffer, offset, count, false); - } - - fw_priv->size = max_t(size_t, offset + count, fw_priv->size); -out: - mutex_unlock(&fw_lock); - return retval; -} - -static struct bin_attribute firmware_attr_data = { - .attr = { .name = "data", .mode = 0644 }, - .size = 0, - .read = firmware_data_read, - .write = firmware_data_write, -}; - -static struct attribute *fw_dev_attrs[] = { - &dev_attr_loading.attr, - NULL -}; - -static struct bin_attribute *fw_dev_bin_attrs[] = { - &firmware_attr_data, - NULL -}; - -static const struct attribute_group fw_dev_attr_group = { - .attrs = fw_dev_attrs, - .bin_attrs = fw_dev_bin_attrs, -}; - -static const struct attribute_group *fw_dev_attr_groups[] = { - &fw_dev_attr_group, - NULL -}; - -static struct fw_sysfs * -fw_create_instance(struct firmware *firmware, const char *fw_name, - struct device *device, unsigned int opt_flags) -{ - struct fw_sysfs *fw_sysfs; - struct device *f_dev; - - fw_sysfs = kzalloc(sizeof(*fw_sysfs), GFP_KERNEL); - if (!fw_sysfs) { - fw_sysfs = ERR_PTR(-ENOMEM); - goto exit; - } - - fw_sysfs->nowait = !!(opt_flags & FW_OPT_NOWAIT); - fw_sysfs->fw = firmware; - f_dev = &fw_sysfs->dev; - - device_initialize(f_dev); - dev_set_name(f_dev, "%s", fw_name); - f_dev->parent = device; - f_dev->class = &firmware_class; - f_dev->groups = fw_dev_attr_groups; -exit: - return fw_sysfs; -} - -/* load a firmware via user helper */ -static int _request_firmware_load(struct fw_sysfs *fw_sysfs, - unsigned int opt_flags, long timeout) -{ - int retval = 0; - struct device *f_dev = &fw_sysfs->dev; - struct fw_priv *fw_priv = fw_sysfs->fw_priv; - - /* fall back on userspace loading */ - if (!fw_priv->data) - fw_priv->is_paged_buf = true; - - dev_set_uevent_suppress(f_dev, true); - - retval = device_add(f_dev); - if (retval) { - dev_err(f_dev, "%s: device_register failed\n", __func__); - goto err_put_dev; - } - - mutex_lock(&fw_lock); - list_add(&fw_priv->pending_list, &pending_fw_head); - mutex_unlock(&fw_lock); - - if (opt_flags & FW_OPT_UEVENT) { - fw_priv->need_uevent = true; - dev_set_uevent_suppress(f_dev, false); - dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_name); - kobject_uevent(&fw_sysfs->dev.kobj, KOBJ_ADD); - } else { - timeout = MAX_JIFFY_OFFSET; - } - - retval = fw_sysfs_wait_timeout(fw_priv, timeout); - if (retval < 0) { - mutex_lock(&fw_lock); - fw_load_abort(fw_sysfs); - mutex_unlock(&fw_lock); - } - - if (fw_state_is_aborted(fw_priv)) { - if (retval == -ERESTARTSYS) - retval = -EINTR; - else - retval = -EAGAIN; - } else if (fw_priv->is_paged_buf && !fw_priv->data) - retval = -ENOMEM; - - device_del(f_dev); -err_put_dev: - put_device(f_dev); - return retval; -} - -static int fw_load_from_user_helper(struct firmware *firmware, - const char *name, struct device *device, - unsigned int opt_flags) -{ - struct fw_sysfs *fw_sysfs; - long timeout; - int ret; - - timeout = firmware_loading_timeout(); - if (opt_flags & FW_OPT_NOWAIT) { - timeout = usermodehelper_read_lock_wait(timeout); - if (!timeout) { - dev_dbg(device, "firmware: %s loading timed out\n", - name); - return -EBUSY; - } - } else { - ret = usermodehelper_read_trylock(); - if (WARN_ON(ret)) { - dev_err(device, "firmware: %s will not be loaded\n", - name); - return ret; - } - } - - fw_sysfs = fw_create_instance(firmware, name, device, opt_flags); - if (IS_ERR(fw_sysfs)) { - ret = PTR_ERR(fw_sysfs); - goto out_unlock; - } - - fw_sysfs->fw_priv = firmware->priv; - ret = _request_firmware_load(fw_sysfs, opt_flags, timeout); - - if (!ret) - ret = assign_fw(firmware, device, opt_flags); - -out_unlock: - usermodehelper_read_unlock(); - - return ret; -} - -#ifdef CONFIG_FW_LOADER_USER_HELPER_FALLBACK -static bool fw_force_sysfs_fallback(unsigned int opt_flags) -{ - return true; -} -#else -static bool fw_force_sysfs_fallback(unsigned int opt_flags) -{ - if (!(opt_flags & FW_OPT_USERHELPER)) - return false; - return true; -} -#endif - -static bool fw_run_sysfs_fallback(unsigned int opt_flags) -{ - if ((opt_flags & FW_OPT_NOFALLBACK)) - return false; - - return fw_force_sysfs_fallback(opt_flags); -} - -static int fw_sysfs_fallback(struct firmware *fw, const char *name, - struct device *device, - unsigned int opt_flags, - int ret) -{ - if (!fw_run_sysfs_fallback(opt_flags)) - return ret; - - dev_warn(device, "Falling back to user helper\n"); - return fw_load_from_user_helper(fw, name, device, opt_flags); -} -#else /* CONFIG_FW_LOADER_USER_HELPER */ -static int fw_sysfs_fallback(struct firmware *fw, const char *name, - struct device *device, - unsigned int opt_flags, - int ret) -{ - /* Keep carrying over the same error */ - return ret; -} - -static inline void kill_pending_fw_fallback_reqs(bool only_kill_custom) { } - -static inline int register_sysfs_loader(void) -{ - return 0; -} - -static inline void unregister_sysfs_loader(void) -{ -} - -#endif /* CONFIG_FW_LOADER_USER_HELPER */ - -/* prepare firmware and firmware_buf structs; - * return 0 if a firmware is already assigned, 1 if need to load one, - * or a negative error code - */ -static int -_request_firmware_prepare(struct firmware **firmware_p, const char *name, - struct device *device, void *dbuf, size_t size) -{ - struct firmware *firmware; - struct fw_priv *fw_priv; - int ret; - - *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); - if (!firmware) { - dev_err(device, "%s: kmalloc(struct firmware) failed\n", - __func__); - return -ENOMEM; - } - - if (fw_get_builtin_firmware(firmware, name, dbuf, size)) { - dev_dbg(device, "using built-in %s\n", name); - return 0; /* assigned */ - } - - ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, dbuf, size); - - /* - * bind with 'priv' now to avoid warning in failure path - * of requesting firmware. - */ - firmware->priv = fw_priv; - - if (ret > 0) { - ret = fw_state_wait(fw_priv); - if (!ret) { - fw_set_page_data(fw_priv, firmware); - return 0; /* assigned */ - } - } - - if (ret < 0) - return ret; - return 1; /* need to load */ -} - -/* - * Batched requests need only one wake, we need to do this step last due to the - * fallback mechanism. The buf is protected with kref_get(), and it won't be - * released until the last user calls release_firmware(). - * - * Failed batched requests are possible as well, in such cases we just share - * the struct fw_priv and won't release it until all requests are woken - * and have gone through this same path. - */ -static void fw_abort_batch_reqs(struct firmware *fw) -{ - struct fw_priv *fw_priv; - - /* Loaded directly? */ - if (!fw || !fw->priv) - return; - - fw_priv = fw->priv; - if (!fw_state_is_aborted(fw_priv)) - fw_state_aborted(fw_priv); -} - -/* called from request_firmware() and request_firmware_work_func() */ -static int -_request_firmware(const struct firmware **firmware_p, const char *name, - struct device *device, void *buf, size_t size, - unsigned int opt_flags) -{ - struct firmware *fw = NULL; - int ret; - - if (!firmware_p) - return -EINVAL; - - if (!name || name[0] == '\0') { - ret = -EINVAL; - goto out; - } - - ret = _request_firmware_prepare(&fw, name, device, buf, size); - if (ret <= 0) /* error or already assigned */ - goto out; - - ret = fw_get_filesystem_firmware(device, fw->priv); - if (ret) { - if (!(opt_flags & FW_OPT_NO_WARN)) - dev_warn(device, - "Direct firmware load for %s failed with error %d\n", - name, ret); - ret = fw_sysfs_fallback(fw, name, device, opt_flags, ret); - } else - ret = assign_fw(fw, device, opt_flags); - - out: - if (ret < 0) { - fw_abort_batch_reqs(fw); - release_firmware(fw); - fw = NULL; - } - - *firmware_p = fw; - return ret; -} - -/** - * request_firmware: - send firmware request and wait for it - * @firmware_p: pointer to firmware image - * @name: name of firmware file - * @device: device for which firmware is being loaded - * - * @firmware_p will be used to return a firmware image by the name - * of @name for device @device. - * - * Should be called from user context where sleeping is allowed. - * - * @name will be used as $FIRMWARE in the uevent environment and - * should be distinctive enough not to be confused with any other - * firmware image for this or any other device. - * - * Caller must hold the reference count of @device. - * - * The function can be called safely inside device's suspend and - * resume callback. - **/ -int -request_firmware(const struct firmware **firmware_p, const char *name, - struct device *device) -{ - int ret; - - /* Need to pin this module until return */ - __module_get(THIS_MODULE); - ret = _request_firmware(firmware_p, name, device, NULL, 0, - FW_OPT_UEVENT); - module_put(THIS_MODULE); - return ret; -} -EXPORT_SYMBOL(request_firmware); - -/** - * request_firmware_direct: - load firmware directly without usermode helper - * @firmware_p: pointer to firmware image - * @name: name of firmware file - * @device: device for which firmware is being loaded - * - * This function works pretty much like request_firmware(), but this doesn't - * fall back to usermode helper even if the firmware couldn't be loaded - * directly from fs. Hence it's useful for loading optional firmwares, which - * aren't always present, without extra long timeouts of udev. - **/ -int request_firmware_direct(const struct firmware **firmware_p, - const char *name, struct device *device) -{ - int ret; - - __module_get(THIS_MODULE); - ret = _request_firmware(firmware_p, name, device, NULL, 0, - FW_OPT_UEVENT | FW_OPT_NO_WARN | - FW_OPT_NOFALLBACK); - module_put(THIS_MODULE); - return ret; -} -EXPORT_SYMBOL_GPL(request_firmware_direct); - -/** - * request_firmware_into_buf - load firmware into a previously allocated buffer - * @firmware_p: pointer to firmware image - * @name: name of firmware file - * @device: device for which firmware is being loaded and DMA region allocated - * @buf: address of buffer to load firmware into - * @size: size of buffer - * - * This function works pretty much like request_firmware(), but it doesn't - * allocate a buffer to hold the firmware data. Instead, the firmware - * is loaded directly into the buffer pointed to by @buf and the @firmware_p - * data member is pointed at @buf. - * - * This function doesn't cache firmware either. - */ -int -request_firmware_into_buf(const struct firmware **firmware_p, const char *name, - struct device *device, void *buf, size_t size) -{ - int ret; - - __module_get(THIS_MODULE); - ret = _request_firmware(firmware_p, name, device, buf, size, - FW_OPT_UEVENT | FW_OPT_NOCACHE); - module_put(THIS_MODULE); - return ret; -} -EXPORT_SYMBOL(request_firmware_into_buf); - -/** - * release_firmware: - release the resource associated with a firmware image - * @fw: firmware resource to release - **/ -void release_firmware(const struct firmware *fw) -{ - if (fw) { - if (!fw_is_builtin_firmware(fw)) - firmware_free_data(fw); - kfree(fw); - } -} -EXPORT_SYMBOL(release_firmware); - -/* Async support */ -struct firmware_work { - struct work_struct work; - struct module *module; - const char *name; - struct device *device; - void *context; - void (*cont)(const struct firmware *fw, void *context); - unsigned int opt_flags; -}; - -static void request_firmware_work_func(struct work_struct *work) -{ - struct firmware_work *fw_work; - const struct firmware *fw; - - fw_work = container_of(work, struct firmware_work, work); - - _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0, - fw_work->opt_flags); - fw_work->cont(fw, fw_work->context); - put_device(fw_work->device); /* taken in request_firmware_nowait() */ - - module_put(fw_work->module); - kfree_const(fw_work->name); - kfree(fw_work); -} - -/** - * request_firmware_nowait - asynchronous version of request_firmware - * @module: module requesting the firmware - * @uevent: sends uevent to copy the firmware image if this flag - * is non-zero else the firmware copy must be done manually. - * @name: name of firmware file - * @device: device for which firmware is being loaded - * @gfp: allocation flags - * @context: will be passed over to @cont, and - * @fw may be %NULL if firmware request fails. - * @cont: function will be called asynchronously when the firmware - * request is over. - * - * Caller must hold the reference count of @device. - * - * Asynchronous variant of request_firmware() for user contexts: - * - sleep for as small periods as possible since it may - * increase kernel boot time of built-in device drivers - * requesting firmware in their ->probe() methods, if - * @gfp is GFP_KERNEL. - * - * - can't sleep at all if @gfp is GFP_ATOMIC. - **/ -int -request_firmware_nowait( - struct module *module, bool uevent, - const char *name, struct device *device, gfp_t gfp, void *context, - void (*cont)(const struct firmware *fw, void *context)) -{ - struct firmware_work *fw_work; - - fw_work = kzalloc(sizeof(struct firmware_work), gfp); - if (!fw_work) - return -ENOMEM; - - fw_work->module = module; - fw_work->name = kstrdup_const(name, gfp); - if (!fw_work->name) { - kfree(fw_work); - return -ENOMEM; - } - fw_work->device = device; - fw_work->context = context; - fw_work->cont = cont; - fw_work->opt_flags = FW_OPT_NOWAIT | - (uevent ? FW_OPT_UEVENT : FW_OPT_USERHELPER); - - if (!try_module_get(module)) { - kfree_const(fw_work->name); - kfree(fw_work); - return -EFAULT; - } - - get_device(fw_work->device); - INIT_WORK(&fw_work->work, request_firmware_work_func); - schedule_work(&fw_work->work); - return 0; -} -EXPORT_SYMBOL(request_firmware_nowait); - -#ifdef CONFIG_PM_SLEEP -static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain); - -/** - * cache_firmware - cache one firmware image in kernel memory space - * @fw_name: the firmware image name - * - * Cache firmware in kernel memory so that drivers can use it when - * system isn't ready for them to request firmware image from userspace. - * Once it returns successfully, driver can use request_firmware or its - * nowait version to get the cached firmware without any interacting - * with userspace - * - * Return 0 if the firmware image has been cached successfully - * Return !0 otherwise - * - */ -static int cache_firmware(const char *fw_name) -{ - int ret; - const struct firmware *fw; - - pr_debug("%s: %s\n", __func__, fw_name); - - ret = request_firmware(&fw, fw_name, NULL); - if (!ret) - kfree(fw); - - pr_debug("%s: %s ret=%d\n", __func__, fw_name, ret); - - return ret; -} - -static struct fw_priv *lookup_fw_priv(const char *fw_name) -{ - struct fw_priv *tmp; - struct firmware_cache *fwc = &fw_cache; - - spin_lock(&fwc->lock); - tmp = __lookup_fw_priv(fw_name); - spin_unlock(&fwc->lock); - - return tmp; -} - -/** - * uncache_firmware - remove one cached firmware image - * @fw_name: the firmware image name - * - * Uncache one firmware image which has been cached successfully - * before. - * - * Return 0 if the firmware cache has been removed successfully - * Return !0 otherwise - * - */ -static int uncache_firmware(const char *fw_name) -{ - struct fw_priv *fw_priv; - struct firmware fw; - - pr_debug("%s: %s\n", __func__, fw_name); - - if (fw_get_builtin_firmware(&fw, fw_name, NULL, 0)) - return 0; - - fw_priv = lookup_fw_priv(fw_name); - if (fw_priv) { - free_fw_priv(fw_priv); - return 0; - } - - return -EINVAL; -} - -static struct fw_cache_entry *alloc_fw_cache_entry(const char *name) -{ - struct fw_cache_entry *fce; - - fce = kzalloc(sizeof(*fce), GFP_ATOMIC); - if (!fce) - goto exit; - - fce->name = kstrdup_const(name, GFP_ATOMIC); - if (!fce->name) { - kfree(fce); - fce = NULL; - goto exit; - } -exit: - return fce; -} - -static int __fw_entry_found(const char *name) -{ - struct firmware_cache *fwc = &fw_cache; - struct fw_cache_entry *fce; - - list_for_each_entry(fce, &fwc->fw_names, list) { - if (!strcmp(fce->name, name)) - return 1; - } - return 0; -} - -static int fw_cache_piggyback_on_request(const char *name) -{ - struct firmware_cache *fwc = &fw_cache; - struct fw_cache_entry *fce; - int ret = 0; - - spin_lock(&fwc->name_lock); - if (__fw_entry_found(name)) - goto found; - - fce = alloc_fw_cache_entry(name); - if (fce) { - ret = 1; - list_add(&fce->list, &fwc->fw_names); - pr_debug("%s: fw: %s\n", __func__, name); - } -found: - spin_unlock(&fwc->name_lock); - return ret; -} - -static void free_fw_cache_entry(struct fw_cache_entry *fce) -{ - kfree_const(fce->name); - kfree(fce); -} - -static void __async_dev_cache_fw_image(void *fw_entry, - async_cookie_t cookie) -{ - struct fw_cache_entry *fce = fw_entry; - struct firmware_cache *fwc = &fw_cache; - int ret; - - ret = cache_firmware(fce->name); - if (ret) { - spin_lock(&fwc->name_lock); - list_del(&fce->list); - spin_unlock(&fwc->name_lock); - - free_fw_cache_entry(fce); - } -} - -/* called with dev->devres_lock held */ -static void dev_create_fw_entry(struct device *dev, void *res, - void *data) -{ - struct fw_name_devm *fwn = res; - const char *fw_name = fwn->name; - struct list_head *head = data; - struct fw_cache_entry *fce; - - fce = alloc_fw_cache_entry(fw_name); - if (fce) - list_add(&fce->list, head); -} - -static int devm_name_match(struct device *dev, void *res, - void *match_data) -{ - struct fw_name_devm *fwn = res; - return (fwn->magic == (unsigned long)match_data); -} - -static void dev_cache_fw_image(struct device *dev, void *data) -{ - LIST_HEAD(todo); - struct fw_cache_entry *fce; - struct fw_cache_entry *fce_next; - struct firmware_cache *fwc = &fw_cache; - - devres_for_each_res(dev, fw_name_devm_release, - devm_name_match, &fw_cache, - dev_create_fw_entry, &todo); - - list_for_each_entry_safe(fce, fce_next, &todo, list) { - list_del(&fce->list); - - spin_lock(&fwc->name_lock); - /* only one cache entry for one firmware */ - if (!__fw_entry_found(fce->name)) { - list_add(&fce->list, &fwc->fw_names); - } else { - free_fw_cache_entry(fce); - fce = NULL; - } - spin_unlock(&fwc->name_lock); - - if (fce) - async_schedule_domain(__async_dev_cache_fw_image, - (void *)fce, - &fw_cache_domain); - } -} - -static void __device_uncache_fw_images(void) -{ - struct firmware_cache *fwc = &fw_cache; - struct fw_cache_entry *fce; - - spin_lock(&fwc->name_lock); - while (!list_empty(&fwc->fw_names)) { - fce = list_entry(fwc->fw_names.next, - struct fw_cache_entry, list); - list_del(&fce->list); - spin_unlock(&fwc->name_lock); - - uncache_firmware(fce->name); - free_fw_cache_entry(fce); - - spin_lock(&fwc->name_lock); - } - spin_unlock(&fwc->name_lock); -} - -/** - * device_cache_fw_images - cache devices' firmware - * - * If one device called request_firmware or its nowait version - * successfully before, the firmware names are recored into the - * device's devres link list, so device_cache_fw_images can call - * cache_firmware() to cache these firmwares for the device, - * then the device driver can load its firmwares easily at - * time when system is not ready to complete loading firmware. - */ -static void device_cache_fw_images(void) -{ - struct firmware_cache *fwc = &fw_cache; - int old_timeout; - DEFINE_WAIT(wait); - - pr_debug("%s\n", __func__); - - /* cancel uncache work */ - cancel_delayed_work_sync(&fwc->work); - - /* - * use small loading timeout for caching devices' firmware - * because all these firmware images have been loaded - * successfully at lease once, also system is ready for - * completing firmware loading now. The maximum size of - * firmware in current distributions is about 2M bytes, - * so 10 secs should be enough. - */ - old_timeout = loading_timeout; - loading_timeout = 10; - - mutex_lock(&fw_lock); - fwc->state = FW_LOADER_START_CACHE; - dpm_for_each_dev(NULL, dev_cache_fw_image); - mutex_unlock(&fw_lock); - - /* wait for completion of caching firmware for all devices */ - async_synchronize_full_domain(&fw_cache_domain); - - loading_timeout = old_timeout; -} - -/** - * device_uncache_fw_images - uncache devices' firmware - * - * uncache all firmwares which have been cached successfully - * by device_uncache_fw_images earlier - */ -static void device_uncache_fw_images(void) -{ - pr_debug("%s\n", __func__); - __device_uncache_fw_images(); -} - -static void device_uncache_fw_images_work(struct work_struct *work) -{ - device_uncache_fw_images(); -} - -/** - * device_uncache_fw_images_delay - uncache devices firmwares - * @delay: number of milliseconds to delay uncache device firmwares - * - * uncache all devices's firmwares which has been cached successfully - * by device_cache_fw_images after @delay milliseconds. - */ -static void device_uncache_fw_images_delay(unsigned long delay) -{ - queue_delayed_work(system_power_efficient_wq, &fw_cache.work, - msecs_to_jiffies(delay)); -} - -static int fw_pm_notify(struct notifier_block *notify_block, - unsigned long mode, void *unused) -{ - switch (mode) { - case PM_HIBERNATION_PREPARE: - case PM_SUSPEND_PREPARE: - case PM_RESTORE_PREPARE: - /* - * kill pending fallback requests with a custom fallback - * to avoid stalling suspend. - */ - kill_pending_fw_fallback_reqs(true); - device_cache_fw_images(); - break; - - case PM_POST_SUSPEND: - case PM_POST_HIBERNATION: - case PM_POST_RESTORE: - /* - * In case that system sleep failed and syscore_suspend is - * not called. - */ - mutex_lock(&fw_lock); - fw_cache.state = FW_LOADER_NO_CACHE; - mutex_unlock(&fw_lock); - - device_uncache_fw_images_delay(10 * MSEC_PER_SEC); - break; - } - - return 0; -} - -/* stop caching firmware once syscore_suspend is reached */ -static int fw_suspend(void) -{ - fw_cache.state = FW_LOADER_NO_CACHE; - return 0; -} - -static struct syscore_ops fw_syscore_ops = { - .suspend = fw_suspend, -}; - -static int __init register_fw_pm_ops(void) -{ - int ret; - - spin_lock_init(&fw_cache.name_lock); - INIT_LIST_HEAD(&fw_cache.fw_names); - - INIT_DELAYED_WORK(&fw_cache.work, - device_uncache_fw_images_work); - - fw_cache.pm_notify.notifier_call = fw_pm_notify; - ret = register_pm_notifier(&fw_cache.pm_notify); - if (ret) - return ret; - - register_syscore_ops(&fw_syscore_ops); - - return ret; -} - -static inline void unregister_fw_pm_ops(void) -{ - unregister_syscore_ops(&fw_syscore_ops); - unregister_pm_notifier(&fw_cache.pm_notify); -} -#else -static int fw_cache_piggyback_on_request(const char *name) -{ - return 0; -} -static inline int register_fw_pm_ops(void) -{ - return 0; -} -static inline void unregister_fw_pm_ops(void) -{ -} -#endif - -static void __init fw_cache_init(void) -{ - spin_lock_init(&fw_cache.lock); - INIT_LIST_HEAD(&fw_cache.head); - fw_cache.state = FW_LOADER_NO_CACHE; -} - -static int fw_shutdown_notify(struct notifier_block *unused1, - unsigned long unused2, void *unused3) -{ - /* - * Kill all pending fallback requests to avoid both stalling shutdown, - * and avoid a deadlock with the usermode_lock. - */ - kill_pending_fw_fallback_reqs(false); - - return NOTIFY_DONE; -} - -static struct notifier_block fw_shutdown_nb = { - .notifier_call = fw_shutdown_notify, -}; - -static int __init firmware_class_init(void) -{ - int ret; - - /* No need to unfold these on exit */ - fw_cache_init(); - - ret = register_fw_pm_ops(); - if (ret) - return ret; - - ret = register_reboot_notifier(&fw_shutdown_nb); - if (ret) - goto out; - - return register_sysfs_loader(); - -out: - unregister_fw_pm_ops(); - return ret; -} - -static void __exit firmware_class_exit(void) -{ - unregister_fw_pm_ops(); - unregister_reboot_notifier(&fw_shutdown_nb); - unregister_sysfs_loader(); -} - -fs_initcall(firmware_class_init); -module_exit(firmware_class_exit); diff --git a/drivers/base/firmware_loader.c b/drivers/base/firmware_loader.c new file mode 100644 index 000000000000..7dd36ace6152 --- /dev/null +++ b/drivers/base/firmware_loader.c @@ -0,0 +1,1940 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * firmware_class.c - Multi purpose firmware loading support + * + * Copyright (c) 2003 Manuel Estrada Sainz + * + * Please see Documentation/firmware_class/ for more information. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "base.h" + +MODULE_AUTHOR("Manuel Estrada Sainz"); +MODULE_DESCRIPTION("Multi purpose firmware loading support"); +MODULE_LICENSE("GPL"); + +enum fw_status { + FW_STATUS_UNKNOWN, + FW_STATUS_LOADING, + FW_STATUS_DONE, + FW_STATUS_ABORTED, +}; + +/* + * Concurrent request_firmware() for the same firmware need to be + * serialized. struct fw_state is simple state machine which hold the + * state of the firmware loading. + */ +struct fw_state { + struct completion completion; + enum fw_status status; +}; + +/* firmware behavior options */ +#define FW_OPT_UEVENT (1U << 0) +#define FW_OPT_NOWAIT (1U << 1) +#define FW_OPT_USERHELPER (1U << 2) +#define FW_OPT_NO_WARN (1U << 3) +#define FW_OPT_NOCACHE (1U << 4) +#define FW_OPT_NOFALLBACK (1U << 5) + +struct firmware_cache { + /* firmware_buf instance will be added into the below list */ + spinlock_t lock; + struct list_head head; + int state; + +#ifdef CONFIG_PM_SLEEP + /* + * Names of firmware images which have been cached successfully + * will be added into the below list so that device uncache + * helper can trace which firmware images have been cached + * before. + */ + spinlock_t name_lock; + struct list_head fw_names; + + struct delayed_work work; + + struct notifier_block pm_notify; +#endif +}; + +struct fw_priv { + struct kref ref; + struct list_head list; + struct firmware_cache *fwc; + struct fw_state fw_st; + void *data; + size_t size; + size_t allocated_size; +#ifdef CONFIG_FW_LOADER_USER_HELPER + bool is_paged_buf; + bool need_uevent; + struct page **pages; + int nr_pages; + int page_array_size; + struct list_head pending_list; +#endif + const char *fw_name; +}; + +struct fw_cache_entry { + struct list_head list; + const char *name; +}; + +struct fw_name_devm { + unsigned long magic; + const char *name; +}; + +static inline struct fw_priv *to_fw_priv(struct kref *ref) +{ + return container_of(ref, struct fw_priv, ref); +} + +#define FW_LOADER_NO_CACHE 0 +#define FW_LOADER_START_CACHE 1 + +/* fw_lock could be moved to 'struct fw_sysfs' but since it is just + * guarding for corner cases a global lock should be OK */ +static DEFINE_MUTEX(fw_lock); + +static struct firmware_cache fw_cache; + +/* Builtin firmware support */ + +#ifdef CONFIG_FW_LOADER + +extern struct builtin_fw __start_builtin_fw[]; +extern struct builtin_fw __end_builtin_fw[]; + +static void fw_copy_to_prealloc_buf(struct firmware *fw, + void *buf, size_t size) +{ + if (!buf || size < fw->size) + return; + memcpy(buf, fw->data, fw->size); +} + +static bool fw_get_builtin_firmware(struct firmware *fw, const char *name, + void *buf, size_t size) +{ + struct builtin_fw *b_fw; + + for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++) { + if (strcmp(name, b_fw->name) == 0) { + fw->size = b_fw->size; + fw->data = b_fw->data; + fw_copy_to_prealloc_buf(fw, buf, size); + + return true; + } + } + + return false; +} + +static bool fw_is_builtin_firmware(const struct firmware *fw) +{ + struct builtin_fw *b_fw; + + for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++) + if (fw->data == b_fw->data) + return true; + + return false; +} + +#else /* Module case - no builtin firmware support */ + +static inline bool fw_get_builtin_firmware(struct firmware *fw, + const char *name, void *buf, + size_t size) +{ + return false; +} + +static inline bool fw_is_builtin_firmware(const struct firmware *fw) +{ + return false; +} +#endif + +static int loading_timeout = 60; /* In seconds */ + +static inline long firmware_loading_timeout(void) +{ + return loading_timeout > 0 ? loading_timeout * HZ : MAX_JIFFY_OFFSET; +} + +static void fw_state_init(struct fw_priv *fw_priv) +{ + struct fw_state *fw_st = &fw_priv->fw_st; + + init_completion(&fw_st->completion); + fw_st->status = FW_STATUS_UNKNOWN; +} + +static int __fw_state_wait_common(struct fw_priv *fw_priv, long timeout) +{ + struct fw_state *fw_st = &fw_priv->fw_st; + long ret; + + ret = wait_for_completion_killable_timeout(&fw_st->completion, timeout); + if (ret != 0 && fw_st->status == FW_STATUS_ABORTED) + return -ENOENT; + if (!ret) + return -ETIMEDOUT; + + return ret < 0 ? ret : 0; +} + +static void __fw_state_set(struct fw_priv *fw_priv, + enum fw_status status) +{ + struct fw_state *fw_st = &fw_priv->fw_st; + + WRITE_ONCE(fw_st->status, status); + + if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED) + complete_all(&fw_st->completion); +} + +static inline void fw_state_start(struct fw_priv *fw_priv) +{ + __fw_state_set(fw_priv, FW_STATUS_LOADING); +} + +static inline void fw_state_done(struct fw_priv *fw_priv) +{ + __fw_state_set(fw_priv, FW_STATUS_DONE); +} + +static inline void fw_state_aborted(struct fw_priv *fw_priv) +{ + __fw_state_set(fw_priv, FW_STATUS_ABORTED); +} + +static inline int fw_state_wait(struct fw_priv *fw_priv) +{ + return __fw_state_wait_common(fw_priv, MAX_SCHEDULE_TIMEOUT); +} + +static bool __fw_state_check(struct fw_priv *fw_priv, + enum fw_status status) +{ + struct fw_state *fw_st = &fw_priv->fw_st; + + return fw_st->status == status; +} + +static inline bool fw_state_is_aborted(struct fw_priv *fw_priv) +{ + return __fw_state_check(fw_priv, FW_STATUS_ABORTED); +} + +#ifdef CONFIG_FW_LOADER_USER_HELPER + +static inline bool fw_sysfs_done(struct fw_priv *fw_priv) +{ + return __fw_state_check(fw_priv, FW_STATUS_DONE); +} + +static inline bool fw_sysfs_loading(struct fw_priv *fw_priv) +{ + return __fw_state_check(fw_priv, FW_STATUS_LOADING); +} + +static inline int fw_sysfs_wait_timeout(struct fw_priv *fw_priv, long timeout) +{ + return __fw_state_wait_common(fw_priv, timeout); +} + +#endif /* CONFIG_FW_LOADER_USER_HELPER */ + +static int fw_cache_piggyback_on_request(const char *name); + +static struct fw_priv *__allocate_fw_priv(const char *fw_name, + struct firmware_cache *fwc, + void *dbuf, size_t size) +{ + struct fw_priv *fw_priv; + + fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC); + if (!fw_priv) + return NULL; + + fw_priv->fw_name = kstrdup_const(fw_name, GFP_ATOMIC); + if (!fw_priv->fw_name) { + kfree(fw_priv); + return NULL; + } + + kref_init(&fw_priv->ref); + fw_priv->fwc = fwc; + fw_priv->data = dbuf; + fw_priv->allocated_size = size; + fw_state_init(fw_priv); +#ifdef CONFIG_FW_LOADER_USER_HELPER + INIT_LIST_HEAD(&fw_priv->pending_list); +#endif + + pr_debug("%s: fw-%s fw_priv=%p\n", __func__, fw_name, fw_priv); + + return fw_priv; +} + +static struct fw_priv *__lookup_fw_priv(const char *fw_name) +{ + struct fw_priv *tmp; + struct firmware_cache *fwc = &fw_cache; + + list_for_each_entry(tmp, &fwc->head, list) + if (!strcmp(tmp->fw_name, fw_name)) + return tmp; + return NULL; +} + +/* 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) +{ + struct fw_priv *tmp; + + spin_lock(&fwc->lock); + tmp = __lookup_fw_priv(fw_name); + if (tmp) { + kref_get(&tmp->ref); + spin_unlock(&fwc->lock); + *fw_priv = tmp; + pr_debug("batched request - sharing the same struct fw_priv and lookup for multiple requests\n"); + return 1; + } + tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size); + if (tmp) + list_add(&tmp->list, &fwc->head); + spin_unlock(&fwc->lock); + + *fw_priv = tmp; + + return tmp ? 0 : -ENOMEM; +} + +static void __free_fw_priv(struct kref *ref) + __releases(&fwc->lock) +{ + struct fw_priv *fw_priv = to_fw_priv(ref); + struct firmware_cache *fwc = fw_priv->fwc; + + 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); + + list_del(&fw_priv->list); + spin_unlock(&fwc->lock); + +#ifdef CONFIG_FW_LOADER_USER_HELPER + if (fw_priv->is_paged_buf) { + int i; + vunmap(fw_priv->data); + for (i = 0; i < fw_priv->nr_pages; i++) + __free_page(fw_priv->pages[i]); + vfree(fw_priv->pages); + } else +#endif + if (!fw_priv->allocated_size) + vfree(fw_priv->data); + kfree_const(fw_priv->fw_name); + kfree(fw_priv); +} + +static void free_fw_priv(struct fw_priv *fw_priv) +{ + struct firmware_cache *fwc = fw_priv->fwc; + spin_lock(&fwc->lock); + if (!kref_put(&fw_priv->ref, __free_fw_priv)) + spin_unlock(&fwc->lock); +} + +/* direct firmware loading support */ +static char fw_path_para[256]; +static const char * const fw_path[] = { + fw_path_para, + "/lib/firmware/updates/" UTS_RELEASE, + "/lib/firmware/updates", + "/lib/firmware/" UTS_RELEASE, + "/lib/firmware" +}; + +/* + * Typical usage is that passing 'firmware_class.path=$CUSTOMIZED_PATH' + * from kernel command line because firmware_class is generally built in + * kernel instead of module. + */ +module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644); +MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path"); + +static int +fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) +{ + loff_t size; + int i, len; + int rc = -ENOENT; + char *path; + enum kernel_read_file_id id = READING_FIRMWARE; + size_t msize = INT_MAX; + + /* Already populated data member means we're loading into a buffer */ + if (fw_priv->data) { + id = READING_FIRMWARE_PREALLOC_BUFFER; + msize = fw_priv->allocated_size; + } + + path = __getname(); + if (!path) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(fw_path); i++) { + /* skip the unset customized path */ + if (!fw_path[i][0]) + continue; + + len = snprintf(path, PATH_MAX, "%s/%s", + fw_path[i], fw_priv->fw_name); + if (len >= PATH_MAX) { + rc = -ENAMETOOLONG; + break; + } + + fw_priv->size = 0; + rc = kernel_read_file_from_path(path, &fw_priv->data, &size, + msize, id); + if (rc) { + if (rc == -ENOENT) + dev_dbg(device, "loading %s failed with error %d\n", + path, rc); + else + dev_warn(device, "loading %s failed with error %d\n", + path, rc); + continue; + } + dev_dbg(device, "direct-loading %s\n", fw_priv->fw_name); + fw_priv->size = size; + fw_state_done(fw_priv); + break; + } + __putname(path); + + return rc; +} + +/* firmware holds the ownership of pages */ +static void firmware_free_data(const struct firmware *fw) +{ + /* Loaded directly? */ + if (!fw->priv) { + vfree(fw->data); + return; + } + free_fw_priv(fw->priv); +} + +/* store the pages buffer info firmware from buf */ +static void fw_set_page_data(struct fw_priv *fw_priv, struct firmware *fw) +{ + fw->priv = fw_priv; +#ifdef CONFIG_FW_LOADER_USER_HELPER + fw->pages = fw_priv->pages; +#endif + fw->size = fw_priv->size; + fw->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); +} + +#ifdef CONFIG_PM_SLEEP +static void fw_name_devm_release(struct device *dev, void *res) +{ + struct fw_name_devm *fwn = res; + + if (fwn->magic == (unsigned long)&fw_cache) + pr_debug("%s: fw_name-%s devm-%p released\n", + __func__, fwn->name, res); + kfree_const(fwn->name); +} + +static int fw_devm_match(struct device *dev, void *res, + void *match_data) +{ + struct fw_name_devm *fwn = res; + + return (fwn->magic == (unsigned long)&fw_cache) && + !strcmp(fwn->name, match_data); +} + +static struct fw_name_devm *fw_find_devm_name(struct device *dev, + const char *name) +{ + struct fw_name_devm *fwn; + + fwn = devres_find(dev, fw_name_devm_release, + fw_devm_match, (void *)name); + return fwn; +} + +/* add firmware name into devres list */ +static int fw_add_devm_name(struct device *dev, const char *name) +{ + struct fw_name_devm *fwn; + + fwn = fw_find_devm_name(dev, name); + if (fwn) + return 1; + + fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm), + GFP_KERNEL); + if (!fwn) + return -ENOMEM; + fwn->name = kstrdup_const(name, GFP_KERNEL); + if (!fwn->name) { + devres_free(fwn); + return -ENOMEM; + } + + fwn->magic = (unsigned long)&fw_cache; + devres_add(dev, fwn); + + return 0; +} +#else +static int fw_add_devm_name(struct device *dev, const char *name) +{ + return 0; +} +#endif + +static int assign_fw(struct firmware *fw, struct device *device, + unsigned int opt_flags) +{ + struct fw_priv *fw_priv = fw->priv; + + mutex_lock(&fw_lock); + if (!fw_priv->size || fw_state_is_aborted(fw_priv)) { + mutex_unlock(&fw_lock); + return -ENOENT; + } + + /* + * add firmware name into devres list so that we can auto cache + * and uncache firmware for device. + * + * device may has been deleted already, but the problem + * should be fixed in devres or driver core. + */ + /* don't cache firmware handled without uevent */ + if (device && (opt_flags & FW_OPT_UEVENT) && + !(opt_flags & FW_OPT_NOCACHE)) + fw_add_devm_name(device, fw_priv->fw_name); + + /* + * After caching firmware image is started, let it piggyback + * on request firmware. + */ + if (!(opt_flags & FW_OPT_NOCACHE) && + fw_priv->fwc->state == FW_LOADER_START_CACHE) { + if (fw_cache_piggyback_on_request(fw_priv->fw_name)) + kref_get(&fw_priv->ref); + } + + /* pass the pages buffer to driver at the last minute */ + fw_set_page_data(fw_priv, fw); + mutex_unlock(&fw_lock); + return 0; +} + +/* + * user-mode helper code + */ +#ifdef CONFIG_FW_LOADER_USER_HELPER +struct fw_sysfs { + bool nowait; + struct device dev; + struct fw_priv *fw_priv; + struct firmware *fw; +}; + +static struct fw_sysfs *to_fw_sysfs(struct device *dev) +{ + return container_of(dev, struct fw_sysfs, dev); +} + +static void __fw_load_abort(struct fw_priv *fw_priv) +{ + /* + * There is a small window in which user can write to 'loading' + * between loading done and disappearance of 'loading' + */ + if (fw_sysfs_done(fw_priv)) + return; + + list_del_init(&fw_priv->pending_list); + fw_state_aborted(fw_priv); +} + +static void fw_load_abort(struct fw_sysfs *fw_sysfs) +{ + struct fw_priv *fw_priv = fw_sysfs->fw_priv; + + __fw_load_abort(fw_priv); +} + +static LIST_HEAD(pending_fw_head); + +static void kill_pending_fw_fallback_reqs(bool only_kill_custom) +{ + struct fw_priv *fw_priv; + struct fw_priv *next; + + mutex_lock(&fw_lock); + list_for_each_entry_safe(fw_priv, next, &pending_fw_head, + pending_list) { + if (!fw_priv->need_uevent || !only_kill_custom) + __fw_load_abort(fw_priv); + } + mutex_unlock(&fw_lock); +} + +static ssize_t timeout_show(struct class *class, struct class_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", loading_timeout); +} + +/** + * firmware_timeout_store - set number of seconds to wait for firmware + * @class: device class pointer + * @attr: device attribute pointer + * @buf: buffer to scan for timeout value + * @count: number of bytes in @buf + * + * Sets the number of seconds to wait for the firmware. Once + * this expires an error will be returned to the driver and no + * firmware will be provided. + * + * Note: zero means 'wait forever'. + **/ +static ssize_t timeout_store(struct class *class, struct class_attribute *attr, + const char *buf, size_t count) +{ + loading_timeout = simple_strtol(buf, NULL, 10); + if (loading_timeout < 0) + loading_timeout = 0; + + return count; +} +static CLASS_ATTR_RW(timeout); + +static struct attribute *firmware_class_attrs[] = { + &class_attr_timeout.attr, + NULL, +}; +ATTRIBUTE_GROUPS(firmware_class); + +static void fw_dev_release(struct device *dev) +{ + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + + kfree(fw_sysfs); +} + +static int do_firmware_uevent(struct fw_sysfs *fw_sysfs, struct kobj_uevent_env *env) +{ + if (add_uevent_var(env, "FIRMWARE=%s", fw_sysfs->fw_priv->fw_name)) + return -ENOMEM; + if (add_uevent_var(env, "TIMEOUT=%i", loading_timeout)) + return -ENOMEM; + if (add_uevent_var(env, "ASYNC=%d", fw_sysfs->nowait)) + return -ENOMEM; + + return 0; +} + +static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + int err = 0; + + mutex_lock(&fw_lock); + if (fw_sysfs->fw_priv) + err = do_firmware_uevent(fw_sysfs, env); + mutex_unlock(&fw_lock); + return err; +} + +static struct class firmware_class = { + .name = "firmware", + .class_groups = firmware_class_groups, + .dev_uevent = firmware_uevent, + .dev_release = fw_dev_release, +}; + +static inline int register_sysfs_loader(void) +{ + return class_register(&firmware_class); +} + +static inline void unregister_sysfs_loader(void) +{ + class_unregister(&firmware_class); +} + +static ssize_t firmware_loading_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + int loading = 0; + + mutex_lock(&fw_lock); + if (fw_sysfs->fw_priv) + loading = fw_sysfs_loading(fw_sysfs->fw_priv); + mutex_unlock(&fw_lock); + + return sprintf(buf, "%d\n", loading); +} + +/* Some architectures don't have PAGE_KERNEL_RO */ +#ifndef PAGE_KERNEL_RO +#define PAGE_KERNEL_RO PAGE_KERNEL +#endif + +/* one pages buffer should be mapped/unmapped only once */ +static int map_fw_priv_pages(struct fw_priv *fw_priv) +{ + if (!fw_priv->is_paged_buf) + return 0; + + vunmap(fw_priv->data); + fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, 0, + PAGE_KERNEL_RO); + if (!fw_priv->data) + return -ENOMEM; + return 0; +} + +/** + * firmware_loading_store - set value in the 'loading' control file + * @dev: device pointer + * @attr: device attribute pointer + * @buf: buffer to scan for loading control value + * @count: number of bytes in @buf + * + * The relevant values are: + * + * 1: Start a load, discarding any previous partial load. + * 0: Conclude the load and hand the data to the driver code. + * -1: Conclude the load with an error and discard any written data. + **/ +static ssize_t firmware_loading_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + struct fw_priv *fw_priv; + ssize_t written = count; + int loading = simple_strtol(buf, NULL, 10); + int i; + + mutex_lock(&fw_lock); + fw_priv = fw_sysfs->fw_priv; + if (fw_state_is_aborted(fw_priv)) + goto out; + + switch (loading) { + case 1: + /* discarding any previous partial load */ + if (!fw_sysfs_done(fw_priv)) { + for (i = 0; i < fw_priv->nr_pages; i++) + __free_page(fw_priv->pages[i]); + vfree(fw_priv->pages); + fw_priv->pages = NULL; + fw_priv->page_array_size = 0; + fw_priv->nr_pages = 0; + fw_state_start(fw_priv); + } + break; + case 0: + if (fw_sysfs_loading(fw_priv)) { + int rc; + + /* + * Several loading requests may be pending on + * one same firmware buf, so let all requests + * see the mapped 'buf->data' once the loading + * is completed. + * */ + rc = map_fw_priv_pages(fw_priv); + if (rc) + dev_err(dev, "%s: map pages failed\n", + __func__); + else + rc = security_kernel_post_read_file(NULL, + fw_priv->data, fw_priv->size, + READING_FIRMWARE); + + /* + * Same logic as fw_load_abort, only the DONE bit + * is ignored and we set ABORT only on failure. + */ + list_del_init(&fw_priv->pending_list); + if (rc) { + fw_state_aborted(fw_priv); + written = rc; + } else { + fw_state_done(fw_priv); + } + break; + } + /* fallthrough */ + default: + dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); + /* fallthrough */ + case -1: + fw_load_abort(fw_sysfs); + break; + } +out: + mutex_unlock(&fw_lock); + return written; +} + +static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store); + +static void firmware_rw_data(struct fw_priv *fw_priv, char *buffer, + loff_t offset, size_t count, bool read) +{ + if (read) + memcpy(buffer, fw_priv->data + offset, count); + else + memcpy(fw_priv->data + offset, buffer, count); +} + +static void firmware_rw(struct fw_priv *fw_priv, char *buffer, + loff_t offset, size_t count, bool read) +{ + while (count) { + void *page_data; + int page_nr = offset >> PAGE_SHIFT; + int page_ofs = offset & (PAGE_SIZE-1); + int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count); + + page_data = kmap(fw_priv->pages[page_nr]); + + if (read) + memcpy(buffer, page_data + page_ofs, page_cnt); + else + memcpy(page_data + page_ofs, buffer, page_cnt); + + kunmap(fw_priv->pages[page_nr]); + buffer += page_cnt; + offset += page_cnt; + count -= page_cnt; + } +} + +static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t offset, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + struct fw_priv *fw_priv; + ssize_t ret_count; + + mutex_lock(&fw_lock); + fw_priv = fw_sysfs->fw_priv; + if (!fw_priv || fw_sysfs_done(fw_priv)) { + ret_count = -ENODEV; + goto out; + } + if (offset > fw_priv->size) { + ret_count = 0; + goto out; + } + if (count > fw_priv->size - offset) + count = fw_priv->size - offset; + + ret_count = count; + + if (fw_priv->data) + firmware_rw_data(fw_priv, buffer, offset, count, true); + else + firmware_rw(fw_priv, buffer, offset, count, true); + +out: + mutex_unlock(&fw_lock); + return ret_count; +} + +static int fw_realloc_pages(struct fw_sysfs *fw_sysfs, int min_size) +{ + struct fw_priv *fw_priv= fw_sysfs->fw_priv; + int pages_needed = PAGE_ALIGN(min_size) >> PAGE_SHIFT; + + /* If the array of pages is too small, grow it... */ + if (fw_priv->page_array_size < pages_needed) { + int new_array_size = max(pages_needed, + fw_priv->page_array_size * 2); + struct page **new_pages; + + new_pages = vmalloc(new_array_size * sizeof(void *)); + if (!new_pages) { + fw_load_abort(fw_sysfs); + return -ENOMEM; + } + memcpy(new_pages, fw_priv->pages, + fw_priv->page_array_size * sizeof(void *)); + memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) * + (new_array_size - fw_priv->page_array_size)); + vfree(fw_priv->pages); + fw_priv->pages = new_pages; + fw_priv->page_array_size = new_array_size; + } + + while (fw_priv->nr_pages < pages_needed) { + fw_priv->pages[fw_priv->nr_pages] = + alloc_page(GFP_KERNEL | __GFP_HIGHMEM); + + if (!fw_priv->pages[fw_priv->nr_pages]) { + fw_load_abort(fw_sysfs); + return -ENOMEM; + } + fw_priv->nr_pages++; + } + return 0; +} + +/** + * firmware_data_write - write method for firmware + * @filp: open sysfs file + * @kobj: kobject for the device + * @bin_attr: bin_attr structure + * @buffer: buffer being written + * @offset: buffer offset for write in total data store area + * @count: buffer size + * + * Data written to the 'data' attribute will be later handed to + * the driver as a firmware image. + **/ +static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t offset, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + struct fw_priv *fw_priv; + ssize_t retval; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + mutex_lock(&fw_lock); + fw_priv = fw_sysfs->fw_priv; + if (!fw_priv || fw_sysfs_done(fw_priv)) { + retval = -ENODEV; + goto out; + } + + if (fw_priv->data) { + if (offset + count > fw_priv->allocated_size) { + retval = -ENOMEM; + goto out; + } + firmware_rw_data(fw_priv, buffer, offset, count, false); + retval = count; + } else { + retval = fw_realloc_pages(fw_sysfs, offset + count); + if (retval) + goto out; + + retval = count; + firmware_rw(fw_priv, buffer, offset, count, false); + } + + fw_priv->size = max_t(size_t, offset + count, fw_priv->size); +out: + mutex_unlock(&fw_lock); + return retval; +} + +static struct bin_attribute firmware_attr_data = { + .attr = { .name = "data", .mode = 0644 }, + .size = 0, + .read = firmware_data_read, + .write = firmware_data_write, +}; + +static struct attribute *fw_dev_attrs[] = { + &dev_attr_loading.attr, + NULL +}; + +static struct bin_attribute *fw_dev_bin_attrs[] = { + &firmware_attr_data, + NULL +}; + +static const struct attribute_group fw_dev_attr_group = { + .attrs = fw_dev_attrs, + .bin_attrs = fw_dev_bin_attrs, +}; + +static const struct attribute_group *fw_dev_attr_groups[] = { + &fw_dev_attr_group, + NULL +}; + +static struct fw_sysfs * +fw_create_instance(struct firmware *firmware, const char *fw_name, + struct device *device, unsigned int opt_flags) +{ + struct fw_sysfs *fw_sysfs; + struct device *f_dev; + + fw_sysfs = kzalloc(sizeof(*fw_sysfs), GFP_KERNEL); + if (!fw_sysfs) { + fw_sysfs = ERR_PTR(-ENOMEM); + goto exit; + } + + fw_sysfs->nowait = !!(opt_flags & FW_OPT_NOWAIT); + fw_sysfs->fw = firmware; + f_dev = &fw_sysfs->dev; + + device_initialize(f_dev); + dev_set_name(f_dev, "%s", fw_name); + f_dev->parent = device; + f_dev->class = &firmware_class; + f_dev->groups = fw_dev_attr_groups; +exit: + return fw_sysfs; +} + +/* load a firmware via user helper */ +static int _request_firmware_load(struct fw_sysfs *fw_sysfs, + unsigned int opt_flags, long timeout) +{ + int retval = 0; + struct device *f_dev = &fw_sysfs->dev; + struct fw_priv *fw_priv = fw_sysfs->fw_priv; + + /* fall back on userspace loading */ + if (!fw_priv->data) + fw_priv->is_paged_buf = true; + + dev_set_uevent_suppress(f_dev, true); + + retval = device_add(f_dev); + if (retval) { + dev_err(f_dev, "%s: device_register failed\n", __func__); + goto err_put_dev; + } + + mutex_lock(&fw_lock); + list_add(&fw_priv->pending_list, &pending_fw_head); + mutex_unlock(&fw_lock); + + if (opt_flags & FW_OPT_UEVENT) { + fw_priv->need_uevent = true; + dev_set_uevent_suppress(f_dev, false); + dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_name); + kobject_uevent(&fw_sysfs->dev.kobj, KOBJ_ADD); + } else { + timeout = MAX_JIFFY_OFFSET; + } + + retval = fw_sysfs_wait_timeout(fw_priv, timeout); + if (retval < 0) { + mutex_lock(&fw_lock); + fw_load_abort(fw_sysfs); + mutex_unlock(&fw_lock); + } + + if (fw_state_is_aborted(fw_priv)) { + if (retval == -ERESTARTSYS) + retval = -EINTR; + else + retval = -EAGAIN; + } else if (fw_priv->is_paged_buf && !fw_priv->data) + retval = -ENOMEM; + + device_del(f_dev); +err_put_dev: + put_device(f_dev); + return retval; +} + +static int fw_load_from_user_helper(struct firmware *firmware, + const char *name, struct device *device, + unsigned int opt_flags) +{ + struct fw_sysfs *fw_sysfs; + long timeout; + int ret; + + timeout = firmware_loading_timeout(); + if (opt_flags & FW_OPT_NOWAIT) { + timeout = usermodehelper_read_lock_wait(timeout); + if (!timeout) { + dev_dbg(device, "firmware: %s loading timed out\n", + name); + return -EBUSY; + } + } else { + ret = usermodehelper_read_trylock(); + if (WARN_ON(ret)) { + dev_err(device, "firmware: %s will not be loaded\n", + name); + return ret; + } + } + + fw_sysfs = fw_create_instance(firmware, name, device, opt_flags); + if (IS_ERR(fw_sysfs)) { + ret = PTR_ERR(fw_sysfs); + goto out_unlock; + } + + fw_sysfs->fw_priv = firmware->priv; + ret = _request_firmware_load(fw_sysfs, opt_flags, timeout); + + if (!ret) + ret = assign_fw(firmware, device, opt_flags); + +out_unlock: + usermodehelper_read_unlock(); + + return ret; +} + +#ifdef CONFIG_FW_LOADER_USER_HELPER_FALLBACK +static bool fw_force_sysfs_fallback(unsigned int opt_flags) +{ + return true; +} +#else +static bool fw_force_sysfs_fallback(unsigned int opt_flags) +{ + if (!(opt_flags & FW_OPT_USERHELPER)) + return false; + return true; +} +#endif + +static bool fw_run_sysfs_fallback(unsigned int opt_flags) +{ + if ((opt_flags & FW_OPT_NOFALLBACK)) + return false; + + return fw_force_sysfs_fallback(opt_flags); +} + +static int fw_sysfs_fallback(struct firmware *fw, const char *name, + struct device *device, + unsigned int opt_flags, + int ret) +{ + if (!fw_run_sysfs_fallback(opt_flags)) + return ret; + + dev_warn(device, "Falling back to user helper\n"); + return fw_load_from_user_helper(fw, name, device, opt_flags); +} +#else /* CONFIG_FW_LOADER_USER_HELPER */ +static int fw_sysfs_fallback(struct firmware *fw, const char *name, + struct device *device, + unsigned int opt_flags, + int ret) +{ + /* Keep carrying over the same error */ + return ret; +} + +static inline void kill_pending_fw_fallback_reqs(bool only_kill_custom) { } + +static inline int register_sysfs_loader(void) +{ + return 0; +} + +static inline void unregister_sysfs_loader(void) +{ +} + +#endif /* CONFIG_FW_LOADER_USER_HELPER */ + +/* prepare firmware and firmware_buf structs; + * return 0 if a firmware is already assigned, 1 if need to load one, + * or a negative error code + */ +static int +_request_firmware_prepare(struct firmware **firmware_p, const char *name, + struct device *device, void *dbuf, size_t size) +{ + struct firmware *firmware; + struct fw_priv *fw_priv; + int ret; + + *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); + if (!firmware) { + dev_err(device, "%s: kmalloc(struct firmware) failed\n", + __func__); + return -ENOMEM; + } + + if (fw_get_builtin_firmware(firmware, name, dbuf, size)) { + dev_dbg(device, "using built-in %s\n", name); + return 0; /* assigned */ + } + + ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, dbuf, size); + + /* + * bind with 'priv' now to avoid warning in failure path + * of requesting firmware. + */ + firmware->priv = fw_priv; + + if (ret > 0) { + ret = fw_state_wait(fw_priv); + if (!ret) { + fw_set_page_data(fw_priv, firmware); + return 0; /* assigned */ + } + } + + if (ret < 0) + return ret; + return 1; /* need to load */ +} + +/* + * Batched requests need only one wake, we need to do this step last due to the + * fallback mechanism. The buf is protected with kref_get(), and it won't be + * released until the last user calls release_firmware(). + * + * Failed batched requests are possible as well, in such cases we just share + * the struct fw_priv and won't release it until all requests are woken + * and have gone through this same path. + */ +static void fw_abort_batch_reqs(struct firmware *fw) +{ + struct fw_priv *fw_priv; + + /* Loaded directly? */ + if (!fw || !fw->priv) + return; + + fw_priv = fw->priv; + if (!fw_state_is_aborted(fw_priv)) + fw_state_aborted(fw_priv); +} + +/* called from request_firmware() and request_firmware_work_func() */ +static int +_request_firmware(const struct firmware **firmware_p, const char *name, + struct device *device, void *buf, size_t size, + unsigned int opt_flags) +{ + struct firmware *fw = NULL; + int ret; + + if (!firmware_p) + return -EINVAL; + + if (!name || name[0] == '\0') { + ret = -EINVAL; + goto out; + } + + ret = _request_firmware_prepare(&fw, name, device, buf, size); + if (ret <= 0) /* error or already assigned */ + goto out; + + ret = fw_get_filesystem_firmware(device, fw->priv); + if (ret) { + if (!(opt_flags & FW_OPT_NO_WARN)) + dev_warn(device, + "Direct firmware load for %s failed with error %d\n", + name, ret); + ret = fw_sysfs_fallback(fw, name, device, opt_flags, ret); + } else + ret = assign_fw(fw, device, opt_flags); + + out: + if (ret < 0) { + fw_abort_batch_reqs(fw); + release_firmware(fw); + fw = NULL; + } + + *firmware_p = fw; + return ret; +} + +/** + * request_firmware: - send firmware request and wait for it + * @firmware_p: pointer to firmware image + * @name: name of firmware file + * @device: device for which firmware is being loaded + * + * @firmware_p will be used to return a firmware image by the name + * of @name for device @device. + * + * Should be called from user context where sleeping is allowed. + * + * @name will be used as $FIRMWARE in the uevent environment and + * should be distinctive enough not to be confused with any other + * firmware image for this or any other device. + * + * Caller must hold the reference count of @device. + * + * The function can be called safely inside device's suspend and + * resume callback. + **/ +int +request_firmware(const struct firmware **firmware_p, const char *name, + struct device *device) +{ + int ret; + + /* Need to pin this module until return */ + __module_get(THIS_MODULE); + ret = _request_firmware(firmware_p, name, device, NULL, 0, + FW_OPT_UEVENT); + module_put(THIS_MODULE); + return ret; +} +EXPORT_SYMBOL(request_firmware); + +/** + * request_firmware_direct: - load firmware directly without usermode helper + * @firmware_p: pointer to firmware image + * @name: name of firmware file + * @device: device for which firmware is being loaded + * + * This function works pretty much like request_firmware(), but this doesn't + * fall back to usermode helper even if the firmware couldn't be loaded + * directly from fs. Hence it's useful for loading optional firmwares, which + * aren't always present, without extra long timeouts of udev. + **/ +int request_firmware_direct(const struct firmware **firmware_p, + const char *name, struct device *device) +{ + int ret; + + __module_get(THIS_MODULE); + ret = _request_firmware(firmware_p, name, device, NULL, 0, + FW_OPT_UEVENT | FW_OPT_NO_WARN | + FW_OPT_NOFALLBACK); + module_put(THIS_MODULE); + return ret; +} +EXPORT_SYMBOL_GPL(request_firmware_direct); + +/** + * request_firmware_into_buf - load firmware into a previously allocated buffer + * @firmware_p: pointer to firmware image + * @name: name of firmware file + * @device: device for which firmware is being loaded and DMA region allocated + * @buf: address of buffer to load firmware into + * @size: size of buffer + * + * This function works pretty much like request_firmware(), but it doesn't + * allocate a buffer to hold the firmware data. Instead, the firmware + * is loaded directly into the buffer pointed to by @buf and the @firmware_p + * data member is pointed at @buf. + * + * This function doesn't cache firmware either. + */ +int +request_firmware_into_buf(const struct firmware **firmware_p, const char *name, + struct device *device, void *buf, size_t size) +{ + int ret; + + __module_get(THIS_MODULE); + ret = _request_firmware(firmware_p, name, device, buf, size, + FW_OPT_UEVENT | FW_OPT_NOCACHE); + module_put(THIS_MODULE); + return ret; +} +EXPORT_SYMBOL(request_firmware_into_buf); + +/** + * release_firmware: - release the resource associated with a firmware image + * @fw: firmware resource to release + **/ +void release_firmware(const struct firmware *fw) +{ + if (fw) { + if (!fw_is_builtin_firmware(fw)) + firmware_free_data(fw); + kfree(fw); + } +} +EXPORT_SYMBOL(release_firmware); + +/* Async support */ +struct firmware_work { + struct work_struct work; + struct module *module; + const char *name; + struct device *device; + void *context; + void (*cont)(const struct firmware *fw, void *context); + unsigned int opt_flags; +}; + +static void request_firmware_work_func(struct work_struct *work) +{ + struct firmware_work *fw_work; + const struct firmware *fw; + + fw_work = container_of(work, struct firmware_work, work); + + _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0, + fw_work->opt_flags); + fw_work->cont(fw, fw_work->context); + put_device(fw_work->device); /* taken in request_firmware_nowait() */ + + module_put(fw_work->module); + kfree_const(fw_work->name); + kfree(fw_work); +} + +/** + * request_firmware_nowait - asynchronous version of request_firmware + * @module: module requesting the firmware + * @uevent: sends uevent to copy the firmware image if this flag + * is non-zero else the firmware copy must be done manually. + * @name: name of firmware file + * @device: device for which firmware is being loaded + * @gfp: allocation flags + * @context: will be passed over to @cont, and + * @fw may be %NULL if firmware request fails. + * @cont: function will be called asynchronously when the firmware + * request is over. + * + * Caller must hold the reference count of @device. + * + * Asynchronous variant of request_firmware() for user contexts: + * - sleep for as small periods as possible since it may + * increase kernel boot time of built-in device drivers + * requesting firmware in their ->probe() methods, if + * @gfp is GFP_KERNEL. + * + * - can't sleep at all if @gfp is GFP_ATOMIC. + **/ +int +request_firmware_nowait( + struct module *module, bool uevent, + const char *name, struct device *device, gfp_t gfp, void *context, + void (*cont)(const struct firmware *fw, void *context)) +{ + struct firmware_work *fw_work; + + fw_work = kzalloc(sizeof(struct firmware_work), gfp); + if (!fw_work) + return -ENOMEM; + + fw_work->module = module; + fw_work->name = kstrdup_const(name, gfp); + if (!fw_work->name) { + kfree(fw_work); + return -ENOMEM; + } + fw_work->device = device; + fw_work->context = context; + fw_work->cont = cont; + fw_work->opt_flags = FW_OPT_NOWAIT | + (uevent ? FW_OPT_UEVENT : FW_OPT_USERHELPER); + + if (!try_module_get(module)) { + kfree_const(fw_work->name); + kfree(fw_work); + return -EFAULT; + } + + get_device(fw_work->device); + INIT_WORK(&fw_work->work, request_firmware_work_func); + schedule_work(&fw_work->work); + return 0; +} +EXPORT_SYMBOL(request_firmware_nowait); + +#ifdef CONFIG_PM_SLEEP +static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain); + +/** + * cache_firmware - cache one firmware image in kernel memory space + * @fw_name: the firmware image name + * + * Cache firmware in kernel memory so that drivers can use it when + * system isn't ready for them to request firmware image from userspace. + * Once it returns successfully, driver can use request_firmware or its + * nowait version to get the cached firmware without any interacting + * with userspace + * + * Return 0 if the firmware image has been cached successfully + * Return !0 otherwise + * + */ +static int cache_firmware(const char *fw_name) +{ + int ret; + const struct firmware *fw; + + pr_debug("%s: %s\n", __func__, fw_name); + + ret = request_firmware(&fw, fw_name, NULL); + if (!ret) + kfree(fw); + + pr_debug("%s: %s ret=%d\n", __func__, fw_name, ret); + + return ret; +} + +static struct fw_priv *lookup_fw_priv(const char *fw_name) +{ + struct fw_priv *tmp; + struct firmware_cache *fwc = &fw_cache; + + spin_lock(&fwc->lock); + tmp = __lookup_fw_priv(fw_name); + spin_unlock(&fwc->lock); + + return tmp; +} + +/** + * uncache_firmware - remove one cached firmware image + * @fw_name: the firmware image name + * + * Uncache one firmware image which has been cached successfully + * before. + * + * Return 0 if the firmware cache has been removed successfully + * Return !0 otherwise + * + */ +static int uncache_firmware(const char *fw_name) +{ + struct fw_priv *fw_priv; + struct firmware fw; + + pr_debug("%s: %s\n", __func__, fw_name); + + if (fw_get_builtin_firmware(&fw, fw_name, NULL, 0)) + return 0; + + fw_priv = lookup_fw_priv(fw_name); + if (fw_priv) { + free_fw_priv(fw_priv); + return 0; + } + + return -EINVAL; +} + +static struct fw_cache_entry *alloc_fw_cache_entry(const char *name) +{ + struct fw_cache_entry *fce; + + fce = kzalloc(sizeof(*fce), GFP_ATOMIC); + if (!fce) + goto exit; + + fce->name = kstrdup_const(name, GFP_ATOMIC); + if (!fce->name) { + kfree(fce); + fce = NULL; + goto exit; + } +exit: + return fce; +} + +static int __fw_entry_found(const char *name) +{ + struct firmware_cache *fwc = &fw_cache; + struct fw_cache_entry *fce; + + list_for_each_entry(fce, &fwc->fw_names, list) { + if (!strcmp(fce->name, name)) + return 1; + } + return 0; +} + +static int fw_cache_piggyback_on_request(const char *name) +{ + struct firmware_cache *fwc = &fw_cache; + struct fw_cache_entry *fce; + int ret = 0; + + spin_lock(&fwc->name_lock); + if (__fw_entry_found(name)) + goto found; + + fce = alloc_fw_cache_entry(name); + if (fce) { + ret = 1; + list_add(&fce->list, &fwc->fw_names); + pr_debug("%s: fw: %s\n", __func__, name); + } +found: + spin_unlock(&fwc->name_lock); + return ret; +} + +static void free_fw_cache_entry(struct fw_cache_entry *fce) +{ + kfree_const(fce->name); + kfree(fce); +} + +static void __async_dev_cache_fw_image(void *fw_entry, + async_cookie_t cookie) +{ + struct fw_cache_entry *fce = fw_entry; + struct firmware_cache *fwc = &fw_cache; + int ret; + + ret = cache_firmware(fce->name); + if (ret) { + spin_lock(&fwc->name_lock); + list_del(&fce->list); + spin_unlock(&fwc->name_lock); + + free_fw_cache_entry(fce); + } +} + +/* called with dev->devres_lock held */ +static void dev_create_fw_entry(struct device *dev, void *res, + void *data) +{ + struct fw_name_devm *fwn = res; + const char *fw_name = fwn->name; + struct list_head *head = data; + struct fw_cache_entry *fce; + + fce = alloc_fw_cache_entry(fw_name); + if (fce) + list_add(&fce->list, head); +} + +static int devm_name_match(struct device *dev, void *res, + void *match_data) +{ + struct fw_name_devm *fwn = res; + return (fwn->magic == (unsigned long)match_data); +} + +static void dev_cache_fw_image(struct device *dev, void *data) +{ + LIST_HEAD(todo); + struct fw_cache_entry *fce; + struct fw_cache_entry *fce_next; + struct firmware_cache *fwc = &fw_cache; + + devres_for_each_res(dev, fw_name_devm_release, + devm_name_match, &fw_cache, + dev_create_fw_entry, &todo); + + list_for_each_entry_safe(fce, fce_next, &todo, list) { + list_del(&fce->list); + + spin_lock(&fwc->name_lock); + /* only one cache entry for one firmware */ + if (!__fw_entry_found(fce->name)) { + list_add(&fce->list, &fwc->fw_names); + } else { + free_fw_cache_entry(fce); + fce = NULL; + } + spin_unlock(&fwc->name_lock); + + if (fce) + async_schedule_domain(__async_dev_cache_fw_image, + (void *)fce, + &fw_cache_domain); + } +} + +static void __device_uncache_fw_images(void) +{ + struct firmware_cache *fwc = &fw_cache; + struct fw_cache_entry *fce; + + spin_lock(&fwc->name_lock); + while (!list_empty(&fwc->fw_names)) { + fce = list_entry(fwc->fw_names.next, + struct fw_cache_entry, list); + list_del(&fce->list); + spin_unlock(&fwc->name_lock); + + uncache_firmware(fce->name); + free_fw_cache_entry(fce); + + spin_lock(&fwc->name_lock); + } + spin_unlock(&fwc->name_lock); +} + +/** + * device_cache_fw_images - cache devices' firmware + * + * If one device called request_firmware or its nowait version + * successfully before, the firmware names are recored into the + * device's devres link list, so device_cache_fw_images can call + * cache_firmware() to cache these firmwares for the device, + * then the device driver can load its firmwares easily at + * time when system is not ready to complete loading firmware. + */ +static void device_cache_fw_images(void) +{ + struct firmware_cache *fwc = &fw_cache; + int old_timeout; + DEFINE_WAIT(wait); + + pr_debug("%s\n", __func__); + + /* cancel uncache work */ + cancel_delayed_work_sync(&fwc->work); + + /* + * use small loading timeout for caching devices' firmware + * because all these firmware images have been loaded + * successfully at lease once, also system is ready for + * completing firmware loading now. The maximum size of + * firmware in current distributions is about 2M bytes, + * so 10 secs should be enough. + */ + old_timeout = loading_timeout; + loading_timeout = 10; + + mutex_lock(&fw_lock); + fwc->state = FW_LOADER_START_CACHE; + dpm_for_each_dev(NULL, dev_cache_fw_image); + mutex_unlock(&fw_lock); + + /* wait for completion of caching firmware for all devices */ + async_synchronize_full_domain(&fw_cache_domain); + + loading_timeout = old_timeout; +} + +/** + * device_uncache_fw_images - uncache devices' firmware + * + * uncache all firmwares which have been cached successfully + * by device_uncache_fw_images earlier + */ +static void device_uncache_fw_images(void) +{ + pr_debug("%s\n", __func__); + __device_uncache_fw_images(); +} + +static void device_uncache_fw_images_work(struct work_struct *work) +{ + device_uncache_fw_images(); +} + +/** + * device_uncache_fw_images_delay - uncache devices firmwares + * @delay: number of milliseconds to delay uncache device firmwares + * + * uncache all devices's firmwares which has been cached successfully + * by device_cache_fw_images after @delay milliseconds. + */ +static void device_uncache_fw_images_delay(unsigned long delay) +{ + queue_delayed_work(system_power_efficient_wq, &fw_cache.work, + msecs_to_jiffies(delay)); +} + +static int fw_pm_notify(struct notifier_block *notify_block, + unsigned long mode, void *unused) +{ + switch (mode) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + case PM_RESTORE_PREPARE: + /* + * kill pending fallback requests with a custom fallback + * to avoid stalling suspend. + */ + kill_pending_fw_fallback_reqs(true); + device_cache_fw_images(); + break; + + case PM_POST_SUSPEND: + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + /* + * In case that system sleep failed and syscore_suspend is + * not called. + */ + mutex_lock(&fw_lock); + fw_cache.state = FW_LOADER_NO_CACHE; + mutex_unlock(&fw_lock); + + device_uncache_fw_images_delay(10 * MSEC_PER_SEC); + break; + } + + return 0; +} + +/* stop caching firmware once syscore_suspend is reached */ +static int fw_suspend(void) +{ + fw_cache.state = FW_LOADER_NO_CACHE; + return 0; +} + +static struct syscore_ops fw_syscore_ops = { + .suspend = fw_suspend, +}; + +static int __init register_fw_pm_ops(void) +{ + int ret; + + spin_lock_init(&fw_cache.name_lock); + INIT_LIST_HEAD(&fw_cache.fw_names); + + INIT_DELAYED_WORK(&fw_cache.work, + device_uncache_fw_images_work); + + fw_cache.pm_notify.notifier_call = fw_pm_notify; + ret = register_pm_notifier(&fw_cache.pm_notify); + if (ret) + return ret; + + register_syscore_ops(&fw_syscore_ops); + + return ret; +} + +static inline void unregister_fw_pm_ops(void) +{ + unregister_syscore_ops(&fw_syscore_ops); + unregister_pm_notifier(&fw_cache.pm_notify); +} +#else +static int fw_cache_piggyback_on_request(const char *name) +{ + return 0; +} +static inline int register_fw_pm_ops(void) +{ + return 0; +} +static inline void unregister_fw_pm_ops(void) +{ +} +#endif + +static void __init fw_cache_init(void) +{ + spin_lock_init(&fw_cache.lock); + INIT_LIST_HEAD(&fw_cache.head); + fw_cache.state = FW_LOADER_NO_CACHE; +} + +static int fw_shutdown_notify(struct notifier_block *unused1, + unsigned long unused2, void *unused3) +{ + /* + * Kill all pending fallback requests to avoid both stalling shutdown, + * and avoid a deadlock with the usermode_lock. + */ + kill_pending_fw_fallback_reqs(false); + + return NOTIFY_DONE; +} + +static struct notifier_block fw_shutdown_nb = { + .notifier_call = fw_shutdown_notify, +}; + +static int __init firmware_class_init(void) +{ + int ret; + + /* No need to unfold these on exit */ + fw_cache_init(); + + ret = register_fw_pm_ops(); + if (ret) + return ret; + + ret = register_reboot_notifier(&fw_shutdown_nb); + if (ret) + goto out; + + return register_sysfs_loader(); + +out: + unregister_fw_pm_ops(); + return ret; +} + +static void __exit firmware_class_exit(void) +{ + unregister_fw_pm_ops(); + unregister_reboot_notifier(&fw_shutdown_nb); + unregister_sysfs_loader(); +} + +fs_initcall(firmware_class_init); +module_exit(firmware_class_exit); -- cgit v1.2.3 From 9de9a449482677a75f1edd2049268a7efc40fc96 Mon Sep 17 00:00:00 2001 From: Gaku Inami Date: Tue, 13 Feb 2018 11:06:40 +0900 Subject: Revert "base: arch_topology: fix section mismatch build warnings" This reverts commit 452562abb5b7 ("base: arch_topology: fix section mismatch build warnings"). It causes the notifier call hangs in some use-cases. In some cases with using maxcpus, some of cpus are booted first and then the remaining cpus are booted. As an example, some users who want to realize fast boot up often use the following procedure. 1) Define all CPUs on device tree (CA57x4 + CA53x4) 2) Add "maxcpus=4" in bootargs 3) Kernel boot up with CA57x4 4) After kernel boot up, CA53x4 is booted from user When kernel init was finished, CPUFREQ_POLICY_NOTIFIER was not still unregisterd. This means that "__init init_cpu_capacity_callback()" will be called after kernel init sequence. To avoid this problem, it needs to remove __init{,data} annotations by reverting this commit. Also, this commit was needed to fix kernel compile issue below. However, this issue was also fixed by another patch: commit 82d8ba717ccb ("arch_topology: Fix section miss match warning due to free_raw_capacity()") in v4.15 as well. Whereas commit 452562abb5b7 added all the missing __init annotations, commit 82d8ba717ccb removed it from free_raw_capacity(). WARNING: vmlinux.o(.text+0x548f24): Section mismatch in reference from the function init_cpu_capacity_callback() to the variable .init.text:$x The function init_cpu_capacity_callback() references the variable __init $x. This is often because init_cpu_capacity_callback lacks a __init annotation or the annotation of $x is wrong. Fixes: 82d8ba717ccb ("arch_topology: Fix section miss match warning due to free_raw_capacity()") Cc: stable Signed-off-by: Gaku Inami Reviewed-by: Dietmar Eggemann Tested-by: Dietmar Eggemann Acked-by: Sudeep Holla Signed-off-by: Greg Kroah-Hartman --- drivers/base/arch_topology.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/base/arch_topology.c b/drivers/base/arch_topology.c index 52ec5174bcb1..e7cb0c6ade81 100644 --- a/drivers/base/arch_topology.c +++ b/drivers/base/arch_topology.c @@ -169,11 +169,11 @@ bool __init topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu) } #ifdef CONFIG_CPU_FREQ -static cpumask_var_t cpus_to_visit __initdata; -static void __init parsing_done_workfn(struct work_struct *work); -static __initdata DECLARE_WORK(parsing_done_work, parsing_done_workfn); +static cpumask_var_t cpus_to_visit; +static void parsing_done_workfn(struct work_struct *work); +static DECLARE_WORK(parsing_done_work, parsing_done_workfn); -static int __init +static int init_cpu_capacity_callback(struct notifier_block *nb, unsigned long val, void *data) @@ -209,7 +209,7 @@ init_cpu_capacity_callback(struct notifier_block *nb, return 0; } -static struct notifier_block init_cpu_capacity_notifier __initdata = { +static struct notifier_block init_cpu_capacity_notifier = { .notifier_call = init_cpu_capacity_callback, }; @@ -242,7 +242,7 @@ static int __init register_cpufreq_notifier(void) } core_initcall(register_cpufreq_notifier); -static void __init parsing_done_workfn(struct work_struct *work) +static void parsing_done_workfn(struct work_struct *work) { cpufreq_unregister_notifier(&init_cpu_capacity_notifier, CPUFREQ_POLICY_NOTIFIER); -- cgit v1.2.3 From ef49ec1dc35112b17f7e928aa08fcab60240bf1d Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Sun, 11 Mar 2018 11:25:48 +0530 Subject: base: soc: use put_device() instead of kfree() Never directly free @dev after calling device_register(), even if it returned an error! Always use put_device() to give up the reference initialized. Signed-off-by: Arvind Yadav Signed-off-by: Greg Kroah-Hartman --- drivers/base/soc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/base/soc.c b/drivers/base/soc.c index 4e80f48ad5d6..10b280f30217 100644 --- a/drivers/base/soc.c +++ b/drivers/base/soc.c @@ -150,6 +150,8 @@ struct soc_device *soc_device_register(struct soc_device_attribute *soc_dev_attr out3: ida_simple_remove(&soc_ida, soc_dev->soc_dev_num); + put_device(&soc_dev->dev); + soc_dev = NULL; out2: kfree(soc_dev); out1: -- cgit v1.2.3 From c8ae1674cd0d688661a8b85b8b2d940516933322 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Sun, 11 Mar 2018 11:25:49 +0530 Subject: driver core: platform: use put_device() if device_register fail if device_register() returned an error! Always use put_device() to give up the reference initialized. Signed-off-by: Arvind Yadav Signed-off-by: Greg Kroah-Hartman --- drivers/base/platform.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/base/platform.c b/drivers/base/platform.c index f1bf7b38d91c..8075ddc70a17 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -1153,8 +1153,10 @@ int __init platform_bus_init(void) early_platform_cleanup(); error = device_register(&platform_bus); - if (error) + if (error) { + put_device(&platform_bus); return error; + } error = bus_register(&platform_bus_type); if (error) device_unregister(&platform_bus); -- cgit v1.2.3 From c1cc0d51140fbcbb3c8cb08ee7e92020dda9c1af Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Sun, 11 Mar 2018 11:25:50 +0530 Subject: driver core: node: use put_device() if device_register fail if device_register() returned an error! Always use put_device() to give up the reference initialized. Signed-off-by: Arvind Yadav Signed-off-by: Greg Kroah-Hartman --- drivers/base/node.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/base/node.c b/drivers/base/node.c index ee090ab9171c..c5f81fc621ac 100644 --- a/drivers/base/node.c +++ b/drivers/base/node.c @@ -315,7 +315,9 @@ static int register_node(struct node *node, int num) node->dev.groups = node_dev_groups; error = device_register(&node->dev); - if (!error){ + if (error) + put_device(&node->dev); + else { hugetlb_register_node(node); compaction_register_node(node); -- cgit v1.2.3 From 3aaba245dfa33270a464d3098b8cce2a2af32784 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Sun, 11 Mar 2018 11:25:51 +0530 Subject: driver core: cpu: use put_device() if device_register fail if device_register() returned an error! Always use put_device() to give up the reference initialized. Signed-off-by: Arvind Yadav Signed-off-by: Greg Kroah-Hartman --- drivers/base/cpu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index d21a2d913107..2da998baa75c 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -382,8 +382,10 @@ int register_cpu(struct cpu *cpu, int num) if (cpu->hotpluggable) cpu->dev.groups = hotplugable_cpu_attr_groups; error = device_register(&cpu->dev); - if (error) + if (error) { + put_device(&cpu->dev); return error; + } per_cpu(cpu_sys_devices, num) = &cpu->dev; register_cpu_under_node(num, cpu_to_node(num)); -- cgit v1.2.3 From 82d1f1178a8578599fb52e697ee8ce8b68f1efd0 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 15 Mar 2018 15:23:43 +0200 Subject: lib/kobject: Join string literals back There is no need to split string literals. Moreover, it would be simpler to grep for an actual code line, when debugging, by using almost any part of the string literal in question. While here, replace printk(LEVEL) by pr_lvl() macros. No functional change intended. Signed-off-by: Andy Shevchenko Signed-off-by: Greg Kroah-Hartman --- lib/kobject.c | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/lib/kobject.c b/lib/kobject.c index afd5a3fc6123..e1d1f290bf35 100644 --- a/lib/kobject.c +++ b/lib/kobject.c @@ -204,8 +204,9 @@ static int kobject_add_internal(struct kobject *kobj) return -ENOENT; if (!kobj->name || !kobj->name[0]) { - WARN(1, "kobject: (%p): attempted to be registered with empty " - "name!\n", kobj); + WARN(1, + "kobject: (%p): attempted to be registered with empty name!\n", + kobj); return -EINVAL; } @@ -232,9 +233,8 @@ static int kobject_add_internal(struct kobject *kobj) /* be noisy on error issues */ if (error == -EEXIST) - WARN(1, "%s failed for %s with " - "-EEXIST, don't try to register things with " - "the same name in the same directory.\n", + WARN(1, + "%s failed for %s with -EEXIST, don't try to register things with the same name in the same directory.\n", __func__, kobject_name(kobj)); else WARN(1, "%s failed for %s (error: %d parent: %s)\n", @@ -334,8 +334,8 @@ void kobject_init(struct kobject *kobj, struct kobj_type *ktype) } if (kobj->state_initialized) { /* do not error out as sometimes we can recover */ - printk(KERN_ERR "kobject (%p): tried to init an initialized " - "object, something is seriously wrong.\n", kobj); + pr_err("kobject (%p): tried to init an initialized object, something is seriously wrong.\n", + kobj); dump_stack(); } @@ -344,7 +344,7 @@ void kobject_init(struct kobject *kobj, struct kobj_type *ktype) return; error: - printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str); + pr_err("kobject (%p): %s\n", kobj, err_str); dump_stack(); } EXPORT_SYMBOL(kobject_init); @@ -357,7 +357,7 @@ static __printf(3, 0) int kobject_add_varg(struct kobject *kobj, retval = kobject_set_name_vargs(kobj, fmt, vargs); if (retval) { - printk(KERN_ERR "kobject: can not set name properly!\n"); + pr_err("kobject: can not set name properly!\n"); return retval; } kobj->parent = parent; @@ -399,8 +399,7 @@ int kobject_add(struct kobject *kobj, struct kobject *parent, return -EINVAL; if (!kobj->state_initialized) { - printk(KERN_ERR "kobject '%s' (%p): tried to add an " - "uninitialized object, something is seriously wrong.\n", + pr_err("kobject '%s' (%p): tried to add an uninitialized object, something is seriously wrong.\n", kobject_name(kobj), kobj); dump_stack(); return -EINVAL; @@ -590,9 +589,9 @@ struct kobject *kobject_get(struct kobject *kobj) { if (kobj) { if (!kobj->state_initialized) - WARN(1, KERN_WARNING "kobject: '%s' (%p): is not " - "initialized, yet kobject_get() is being " - "called.\n", kobject_name(kobj), kobj); + WARN(1, KERN_WARNING + "kobject: '%s' (%p): is not initialized, yet kobject_get() is being called.\n", + kobject_name(kobj), kobj); kref_get(&kobj->kref); } return kobj; @@ -622,8 +621,7 @@ static void kobject_cleanup(struct kobject *kobj) kobject_name(kobj), kobj, __func__, kobj->parent); if (t && !t->release) - pr_debug("kobject: '%s' (%p): does not have a release() " - "function, it is broken and must be fixed.\n", + pr_debug("kobject: '%s' (%p): does not have a release() function, it is broken and must be fixed.\n", kobject_name(kobj), kobj); /* send "remove" if the caller did not do it but sent "add" */ @@ -686,9 +684,9 @@ void kobject_put(struct kobject *kobj) { if (kobj) { if (!kobj->state_initialized) - WARN(1, KERN_WARNING "kobject: '%s' (%p): is not " - "initialized, yet kobject_put() is being " - "called.\n", kobject_name(kobj), kobj); + WARN(1, KERN_WARNING + "kobject: '%s' (%p): is not initialized, yet kobject_put() is being called.\n", + kobject_name(kobj), kobj); kref_put(&kobj->kref, kobject_release); } } @@ -752,8 +750,7 @@ struct kobject *kobject_create_and_add(const char *name, struct kobject *parent) retval = kobject_add(kobj, parent, "%s", name); if (retval) { - printk(KERN_WARNING "%s: kobject_add error: %d\n", - __func__, retval); + pr_warn("%s: kobject_add error: %d\n", __func__, retval); kobject_put(kobj); kobj = NULL; } -- cgit v1.2.3 From 8ddfb47294c0f45a476a92ecf1e56cb5990c5468 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 9 Feb 2018 10:13:57 +0100 Subject: drivers: base: add description for .coredump() callback Commit 3c47d19ff4dc ("drivers: base: add coredump driver ops") added a new callback in struct device_driver, but not a kerneldoc description so here it is. Fixes: 3c47d19ff4dc ("drivers: base: add coredump driver ops") Signed-off-by: Arend van Spriel Signed-off-by: Greg Kroah-Hartman --- include/linux/device.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/device.h b/include/linux/device.h index b093405ed525..0b32a42db4ae 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -256,6 +256,7 @@ enum probe_type { * automatically. * @pm: Power management operations of the device which matched * this driver. + * @coredump: Called through sysfs to initiate a device coredump. * @p: Driver core's private data, no one other than the driver * core can touch this. * -- cgit v1.2.3 From b2e9a8553cebcb414753e0185ce6fa87e14ebf30 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:46 -0800 Subject: firmware: simplify CONFIG_FW_LOADER_USER_HELPER_FALLBACK further All CONFIG_FW_LOADER_USER_HELPER_FALLBACK really is, is just a bool, initailized at build time. Define it as such. This simplifies the logic even further, removing now all explicit #ifdefs around the code. Acked-by: Kees Cook Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/drivers/base/firmware_loader.c b/drivers/base/firmware_loader.c index 7dd36ace6152..59dba794ce1a 100644 --- a/drivers/base/firmware_loader.c +++ b/drivers/base/firmware_loader.c @@ -266,6 +266,22 @@ static inline bool fw_state_is_aborted(struct fw_priv *fw_priv) #ifdef CONFIG_FW_LOADER_USER_HELPER +/** + * struct firmware_fallback_config - firmware fallback configuratioon settings + * + * Helps describe and fine tune the fallback mechanism. + * + * @force_sysfs_fallback: force the sysfs fallback mechanism to be used + * as if one had enabled CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y. + */ +struct firmware_fallback_config { + bool force_sysfs_fallback; +}; + +static const struct firmware_fallback_config fw_fallback_config = { + .force_sysfs_fallback = IS_ENABLED(CONFIG_FW_LOADER_USER_HELPER_FALLBACK), +}; + static inline bool fw_sysfs_done(struct fw_priv *fw_priv) { return __fw_state_check(fw_priv, FW_STATUS_DONE); @@ -1151,19 +1167,14 @@ out_unlock: return ret; } -#ifdef CONFIG_FW_LOADER_USER_HELPER_FALLBACK -static bool fw_force_sysfs_fallback(unsigned int opt_flags) -{ - return true; -} -#else static bool fw_force_sysfs_fallback(unsigned int opt_flags) { + if (fw_fallback_config.force_sysfs_fallback) + return true; if (!(opt_flags & FW_OPT_USERHELPER)) return false; return true; } -#endif static bool fw_run_sysfs_fallback(unsigned int opt_flags) { -- cgit v1.2.3 From 5d9566b144fdbbedf64dbf0907d2933fbcfb6872 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:47 -0800 Subject: firmware: use helpers for setting up a temporary cache timeout We only use the timeout for the firmware fallback mechanism except for trying to set the timeout during the cache setup for resume/suspend. For those cases, setting the timeout should be a no-op, so just reflect this in code by adding helpers for it. This change introduces no functional changes. Acked-by: Kees Cook Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader.c | 49 ++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/drivers/base/firmware_loader.c b/drivers/base/firmware_loader.c index 59dba794ce1a..2d819875348d 100644 --- a/drivers/base/firmware_loader.c +++ b/drivers/base/firmware_loader.c @@ -191,13 +191,6 @@ static inline bool fw_is_builtin_firmware(const struct firmware *fw) } #endif -static int loading_timeout = 60; /* In seconds */ - -static inline long firmware_loading_timeout(void) -{ - return loading_timeout > 0 ? loading_timeout * HZ : MAX_JIFFY_OFFSET; -} - static void fw_state_init(struct fw_priv *fw_priv) { struct fw_state *fw_st = &fw_priv->fw_st; @@ -282,6 +275,32 @@ static const struct firmware_fallback_config fw_fallback_config = { .force_sysfs_fallback = IS_ENABLED(CONFIG_FW_LOADER_USER_HELPER_FALLBACK), }; +static int old_timeout; +static int loading_timeout = 60; /* In seconds */ + +static inline long firmware_loading_timeout(void) +{ + return loading_timeout > 0 ? loading_timeout * HZ : MAX_JIFFY_OFFSET; +} + +/* + * use small loading timeout for caching devices' firmware because all these + * firmware images have been loaded successfully at lease once, also system is + * ready for completing firmware loading now. The maximum size of firmware in + * current distributions is about 2M bytes, so 10 secs should be enough. + */ +static void fw_fallback_set_cache_timeout(void) +{ + old_timeout = loading_timeout; + loading_timeout = 10; +} + +/* Restores the timeout to the value last configured during normal operation */ +static void fw_fallback_set_default_timeout(void) +{ + loading_timeout = old_timeout; +} + static inline bool fw_sysfs_done(struct fw_priv *fw_priv) { return __fw_state_check(fw_priv, FW_STATUS_DONE); @@ -1206,6 +1225,8 @@ static int fw_sysfs_fallback(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) { @@ -1752,7 +1773,6 @@ static void __device_uncache_fw_images(void) static void device_cache_fw_images(void) { struct firmware_cache *fwc = &fw_cache; - int old_timeout; DEFINE_WAIT(wait); pr_debug("%s\n", __func__); @@ -1760,16 +1780,7 @@ static void device_cache_fw_images(void) /* cancel uncache work */ cancel_delayed_work_sync(&fwc->work); - /* - * use small loading timeout for caching devices' firmware - * because all these firmware images have been loaded - * successfully at lease once, also system is ready for - * completing firmware loading now. The maximum size of - * firmware in current distributions is about 2M bytes, - * so 10 secs should be enough. - */ - old_timeout = loading_timeout; - loading_timeout = 10; + fw_fallback_set_cache_timeout(); mutex_lock(&fw_lock); fwc->state = FW_LOADER_START_CACHE; @@ -1779,7 +1790,7 @@ static void device_cache_fw_images(void) /* wait for completion of caching firmware for all devices */ async_synchronize_full_domain(&fw_cache_domain); - loading_timeout = old_timeout; + fw_fallback_set_default_timeout(); } /** -- cgit v1.2.3 From e05cb73f83918b9bde2063de08467f2261d0f5bb Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:48 -0800 Subject: firmware: move loading timeout under struct firmware_fallback_config The timeout is a fallback construct, so we can just stuff the timeout configuration under struct firmware_fallback_config. While at it, add a few helpers which vets the use of getting or setting the timeout as an int. The main use of the timeout is to set a timeout for completion, and that is used as an unsigned long. There a few cases however where it makes sense to get or set the timeout as an int, the helpers annotate these use cases have been properly vetted for. Acked-by: Kees Cook Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader.c | 46 ++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/drivers/base/firmware_loader.c b/drivers/base/firmware_loader.c index 2d819875348d..9757f9fff01e 100644 --- a/drivers/base/firmware_loader.c +++ b/drivers/base/firmware_loader.c @@ -266,21 +266,38 @@ static inline bool fw_state_is_aborted(struct fw_priv *fw_priv) * * @force_sysfs_fallback: force the sysfs fallback mechanism to be used * as if one had enabled CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y. + * @old_timeout: for internal use + * @loading_timeout: the timeout to wait for the fallback mechanism before + * giving up, in seconds. */ struct firmware_fallback_config { - bool force_sysfs_fallback; + const bool force_sysfs_fallback; + int old_timeout; + int loading_timeout; }; -static const struct firmware_fallback_config fw_fallback_config = { +static struct firmware_fallback_config fw_fallback_config = { .force_sysfs_fallback = IS_ENABLED(CONFIG_FW_LOADER_USER_HELPER_FALLBACK), + .loading_timeout = 60, + .old_timeout = 60, }; -static int old_timeout; -static int loading_timeout = 60; /* In seconds */ +/* 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; +} static inline long firmware_loading_timeout(void) { - return loading_timeout > 0 ? loading_timeout * HZ : MAX_JIFFY_OFFSET; + return __firmware_loading_timeout() > 0 ? + __firmware_loading_timeout() * HZ : MAX_JIFFY_OFFSET; } /* @@ -291,14 +308,14 @@ static inline long firmware_loading_timeout(void) */ static void fw_fallback_set_cache_timeout(void) { - old_timeout = loading_timeout; - loading_timeout = 10; + fw_fallback_config.old_timeout = __firmware_loading_timeout(); + __fw_fallback_set_timeout(10); } /* Restores the timeout to the value last configured during normal operation */ static void fw_fallback_set_default_timeout(void) { - loading_timeout = old_timeout; + __fw_fallback_set_timeout(fw_fallback_config.old_timeout); } static inline bool fw_sysfs_done(struct fw_priv *fw_priv) @@ -677,7 +694,7 @@ static void kill_pending_fw_fallback_reqs(bool only_kill_custom) static ssize_t timeout_show(struct class *class, struct class_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", loading_timeout); + return sprintf(buf, "%d\n", __firmware_loading_timeout()); } /** @@ -696,9 +713,12 @@ static ssize_t timeout_show(struct class *class, struct class_attribute *attr, static ssize_t timeout_store(struct class *class, struct class_attribute *attr, const char *buf, size_t count) { - loading_timeout = simple_strtol(buf, NULL, 10); - if (loading_timeout < 0) - loading_timeout = 0; + 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; } @@ -721,7 +741,7 @@ static int do_firmware_uevent(struct fw_sysfs *fw_sysfs, struct kobj_uevent_env { if (add_uevent_var(env, "FIRMWARE=%s", fw_sysfs->fw_priv->fw_name)) return -ENOMEM; - if (add_uevent_var(env, "TIMEOUT=%i", loading_timeout)) + if (add_uevent_var(env, "TIMEOUT=%i", __firmware_loading_timeout())) return -ENOMEM; if (add_uevent_var(env, "ASYNC=%d", fw_sysfs->nowait)) return -ENOMEM; -- cgit v1.2.3 From d73f821c7aea16ad4f501fb87c8b5373a025e7f4 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:49 -0800 Subject: firmware: split firmware fallback functionality into its own file The firmware fallback code is optional. Split that code out to help distinguish the fallback functionlity from othere core firmware loader features. This should make it easier to maintain and review code changes. The reason for keeping the configuration onto a table which is built-in if you enable firmware loading is so that we can later enable the kernel after subsequent patches to tweak this configuration, even if the firmware loader is modular. This introduces no functional changes. Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/Makefile | 4 +- drivers/base/firmware_fallback.c | 661 +++++++++++++++++++++++++++ drivers/base/firmware_fallback.h | 61 +++ drivers/base/firmware_fallback_table.c | 29 ++ drivers/base/firmware_loader.c | 803 +-------------------------------- drivers/base/firmware_loader.h | 115 +++++ 6 files changed, 874 insertions(+), 799 deletions(-) create mode 100644 drivers/base/firmware_fallback.c create mode 100644 drivers/base/firmware_fallback.h create mode 100644 drivers/base/firmware_fallback_table.c create mode 100644 drivers/base/firmware_loader.h diff --git a/drivers/base/Makefile b/drivers/base/Makefile index f261143fafbf..b946a408256d 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -5,7 +5,8 @@ obj-y := component.o core.o bus.o dd.o syscore.o \ driver.o class.o platform.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ - topology.o container.o property.o cacheinfo.o + topology.o container.o property.o cacheinfo.o \ + firmware_fallback_table.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-$(CONFIG_DMA_CMA) += dma-contiguous.o obj-y += power/ @@ -14,6 +15,7 @@ obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.o obj-$(CONFIG_ISA_BUS_API) += isa.o obj-$(CONFIG_FW_LOADER) += firmware_class.o firmware_class-objs := firmware_loader.o +firmware_class-$(CONFIG_FW_LOADER_USER_HELPER) += firmware_fallback.o obj-$(CONFIG_NUMA) += node.o obj-$(CONFIG_MEMORY_HOTPLUG_SPARSE) += memory.o ifeq ($(CONFIG_SYSFS),y) diff --git a/drivers/base/firmware_fallback.c b/drivers/base/firmware_fallback.c new file mode 100644 index 000000000000..47690207e0ee --- /dev/null +++ b/drivers/base/firmware_fallback.c @@ -0,0 +1,661 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include + +#include "firmware_fallback.h" +#include "firmware_loader.h" + +/* + * firmware fallback mechanism + */ + +extern struct firmware_fallback_config fw_fallback_config; + +/* These getters are vetted to use int properly */ +static inline int __firmware_loading_timeout(void) +{ + return fw_fallback_config.loading_timeout; +} + +/* These setters are vetted to use int properly */ +static void __fw_fallback_set_timeout(int timeout) +{ + fw_fallback_config.loading_timeout = timeout; +} + +/* + * use small loading timeout for caching devices' firmware because all these + * firmware images have been loaded successfully at lease once, also system is + * ready for completing firmware loading now. The maximum size of firmware in + * current distributions is about 2M bytes, so 10 secs should be enough. + */ +void fw_fallback_set_cache_timeout(void) +{ + fw_fallback_config.old_timeout = __firmware_loading_timeout(); + __fw_fallback_set_timeout(10); +} + +/* Restores the timeout to the value last configured during normal operation */ +void fw_fallback_set_default_timeout(void) +{ + __fw_fallback_set_timeout(fw_fallback_config.old_timeout); +} + +static long firmware_loading_timeout(void) +{ + return __firmware_loading_timeout() > 0 ? + __firmware_loading_timeout() * HZ : MAX_JIFFY_OFFSET; +} + +static inline bool fw_sysfs_done(struct fw_priv *fw_priv) +{ + return __fw_state_check(fw_priv, FW_STATUS_DONE); +} + +static inline bool fw_sysfs_loading(struct fw_priv *fw_priv) +{ + return __fw_state_check(fw_priv, FW_STATUS_LOADING); +} + +static inline int fw_sysfs_wait_timeout(struct fw_priv *fw_priv, long timeout) +{ + return __fw_state_wait_common(fw_priv, timeout); +} + +struct fw_sysfs { + bool nowait; + struct device dev; + struct fw_priv *fw_priv; + struct firmware *fw; +}; + +static struct fw_sysfs *to_fw_sysfs(struct device *dev) +{ + return container_of(dev, struct fw_sysfs, dev); +} + +static void __fw_load_abort(struct fw_priv *fw_priv) +{ + /* + * There is a small window in which user can write to 'loading' + * between loading done and disappearance of 'loading' + */ + if (fw_sysfs_done(fw_priv)) + return; + + list_del_init(&fw_priv->pending_list); + fw_state_aborted(fw_priv); +} + +static void fw_load_abort(struct fw_sysfs *fw_sysfs) +{ + struct fw_priv *fw_priv = fw_sysfs->fw_priv; + + __fw_load_abort(fw_priv); +} + +static LIST_HEAD(pending_fw_head); + +void kill_pending_fw_fallback_reqs(bool only_kill_custom) +{ + struct fw_priv *fw_priv; + struct fw_priv *next; + + mutex_lock(&fw_lock); + list_for_each_entry_safe(fw_priv, next, &pending_fw_head, + pending_list) { + if (!fw_priv->need_uevent || !only_kill_custom) + __fw_load_abort(fw_priv); + } + mutex_unlock(&fw_lock); +} + +static ssize_t timeout_show(struct class *class, struct class_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", __firmware_loading_timeout()); +} + +/** + * firmware_timeout_store - set number of seconds to wait for firmware + * @class: device class pointer + * @attr: device attribute pointer + * @buf: buffer to scan for timeout value + * @count: number of bytes in @buf + * + * Sets the number of seconds to wait for the firmware. Once + * this expires an error will be returned to the driver and no + * firmware will be provided. + * + * Note: zero means 'wait forever'. + **/ +static ssize_t timeout_store(struct class *class, struct class_attribute *attr, + const char *buf, size_t count) +{ + int tmp_loading_timeout = simple_strtol(buf, NULL, 10); + + if (tmp_loading_timeout < 0) + tmp_loading_timeout = 0; + + __fw_fallback_set_timeout(tmp_loading_timeout); + + return count; +} +static CLASS_ATTR_RW(timeout); + +static struct attribute *firmware_class_attrs[] = { + &class_attr_timeout.attr, + NULL, +}; +ATTRIBUTE_GROUPS(firmware_class); + +static void fw_dev_release(struct device *dev) +{ + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + + kfree(fw_sysfs); +} + +static int do_firmware_uevent(struct fw_sysfs *fw_sysfs, struct kobj_uevent_env *env) +{ + if (add_uevent_var(env, "FIRMWARE=%s", fw_sysfs->fw_priv->fw_name)) + return -ENOMEM; + if (add_uevent_var(env, "TIMEOUT=%i", __firmware_loading_timeout())) + return -ENOMEM; + if (add_uevent_var(env, "ASYNC=%d", fw_sysfs->nowait)) + return -ENOMEM; + + return 0; +} + +static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + int err = 0; + + mutex_lock(&fw_lock); + if (fw_sysfs->fw_priv) + err = do_firmware_uevent(fw_sysfs, env); + mutex_unlock(&fw_lock); + return err; +} + +static struct class firmware_class = { + .name = "firmware", + .class_groups = firmware_class_groups, + .dev_uevent = firmware_uevent, + .dev_release = fw_dev_release, +}; + +int register_sysfs_loader(void) +{ + return class_register(&firmware_class); +} + +void unregister_sysfs_loader(void) +{ + class_unregister(&firmware_class); +} + +static ssize_t firmware_loading_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + int loading = 0; + + mutex_lock(&fw_lock); + if (fw_sysfs->fw_priv) + loading = fw_sysfs_loading(fw_sysfs->fw_priv); + mutex_unlock(&fw_lock); + + return sprintf(buf, "%d\n", loading); +} + +/* Some architectures don't have PAGE_KERNEL_RO */ +#ifndef PAGE_KERNEL_RO +#define PAGE_KERNEL_RO PAGE_KERNEL +#endif + +/* one pages buffer should be mapped/unmapped only once */ +static int map_fw_priv_pages(struct fw_priv *fw_priv) +{ + if (!fw_priv->is_paged_buf) + return 0; + + vunmap(fw_priv->data); + fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, 0, + PAGE_KERNEL_RO); + if (!fw_priv->data) + return -ENOMEM; + return 0; +} + +/** + * firmware_loading_store - set value in the 'loading' control file + * @dev: device pointer + * @attr: device attribute pointer + * @buf: buffer to scan for loading control value + * @count: number of bytes in @buf + * + * The relevant values are: + * + * 1: Start a load, discarding any previous partial load. + * 0: Conclude the load and hand the data to the driver code. + * -1: Conclude the load with an error and discard any written data. + **/ +static ssize_t firmware_loading_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + struct fw_priv *fw_priv; + ssize_t written = count; + int loading = simple_strtol(buf, NULL, 10); + int i; + + mutex_lock(&fw_lock); + fw_priv = fw_sysfs->fw_priv; + if (fw_state_is_aborted(fw_priv)) + goto out; + + switch (loading) { + case 1: + /* discarding any previous partial load */ + if (!fw_sysfs_done(fw_priv)) { + for (i = 0; i < fw_priv->nr_pages; i++) + __free_page(fw_priv->pages[i]); + vfree(fw_priv->pages); + fw_priv->pages = NULL; + fw_priv->page_array_size = 0; + fw_priv->nr_pages = 0; + fw_state_start(fw_priv); + } + break; + case 0: + if (fw_sysfs_loading(fw_priv)) { + int rc; + + /* + * Several loading requests may be pending on + * one same firmware buf, so let all requests + * see the mapped 'buf->data' once the loading + * is completed. + * */ + rc = map_fw_priv_pages(fw_priv); + if (rc) + dev_err(dev, "%s: map pages failed\n", + __func__); + else + rc = security_kernel_post_read_file(NULL, + fw_priv->data, fw_priv->size, + READING_FIRMWARE); + + /* + * Same logic as fw_load_abort, only the DONE bit + * is ignored and we set ABORT only on failure. + */ + list_del_init(&fw_priv->pending_list); + if (rc) { + fw_state_aborted(fw_priv); + written = rc; + } else { + fw_state_done(fw_priv); + } + break; + } + /* fallthrough */ + default: + dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); + /* fallthrough */ + case -1: + fw_load_abort(fw_sysfs); + break; + } +out: + mutex_unlock(&fw_lock); + return written; +} + +static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store); + +static void firmware_rw_data(struct fw_priv *fw_priv, char *buffer, + loff_t offset, size_t count, bool read) +{ + if (read) + memcpy(buffer, fw_priv->data + offset, count); + else + memcpy(fw_priv->data + offset, buffer, count); +} + +static void firmware_rw(struct fw_priv *fw_priv, char *buffer, + loff_t offset, size_t count, bool read) +{ + while (count) { + void *page_data; + int page_nr = offset >> PAGE_SHIFT; + int page_ofs = offset & (PAGE_SIZE-1); + int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count); + + page_data = kmap(fw_priv->pages[page_nr]); + + if (read) + memcpy(buffer, page_data + page_ofs, page_cnt); + else + memcpy(page_data + page_ofs, buffer, page_cnt); + + kunmap(fw_priv->pages[page_nr]); + buffer += page_cnt; + offset += page_cnt; + count -= page_cnt; + } +} + +static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t offset, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + struct fw_priv *fw_priv; + ssize_t ret_count; + + mutex_lock(&fw_lock); + fw_priv = fw_sysfs->fw_priv; + if (!fw_priv || fw_sysfs_done(fw_priv)) { + ret_count = -ENODEV; + goto out; + } + if (offset > fw_priv->size) { + ret_count = 0; + goto out; + } + if (count > fw_priv->size - offset) + count = fw_priv->size - offset; + + ret_count = count; + + if (fw_priv->data) + firmware_rw_data(fw_priv, buffer, offset, count, true); + else + firmware_rw(fw_priv, buffer, offset, count, true); + +out: + mutex_unlock(&fw_lock); + return ret_count; +} + +static int fw_realloc_pages(struct fw_sysfs *fw_sysfs, int min_size) +{ + struct fw_priv *fw_priv= fw_sysfs->fw_priv; + int pages_needed = PAGE_ALIGN(min_size) >> PAGE_SHIFT; + + /* If the array of pages is too small, grow it... */ + if (fw_priv->page_array_size < pages_needed) { + int new_array_size = max(pages_needed, + fw_priv->page_array_size * 2); + struct page **new_pages; + + new_pages = vmalloc(new_array_size * sizeof(void *)); + if (!new_pages) { + fw_load_abort(fw_sysfs); + return -ENOMEM; + } + memcpy(new_pages, fw_priv->pages, + fw_priv->page_array_size * sizeof(void *)); + memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) * + (new_array_size - fw_priv->page_array_size)); + vfree(fw_priv->pages); + fw_priv->pages = new_pages; + fw_priv->page_array_size = new_array_size; + } + + while (fw_priv->nr_pages < pages_needed) { + fw_priv->pages[fw_priv->nr_pages] = + alloc_page(GFP_KERNEL | __GFP_HIGHMEM); + + if (!fw_priv->pages[fw_priv->nr_pages]) { + fw_load_abort(fw_sysfs); + return -ENOMEM; + } + fw_priv->nr_pages++; + } + return 0; +} + +/** + * firmware_data_write - write method for firmware + * @filp: open sysfs file + * @kobj: kobject for the device + * @bin_attr: bin_attr structure + * @buffer: buffer being written + * @offset: buffer offset for write in total data store area + * @count: buffer size + * + * Data written to the 'data' attribute will be later handed to + * the driver as a firmware image. + **/ +static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t offset, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + struct fw_priv *fw_priv; + ssize_t retval; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + mutex_lock(&fw_lock); + fw_priv = fw_sysfs->fw_priv; + if (!fw_priv || fw_sysfs_done(fw_priv)) { + retval = -ENODEV; + goto out; + } + + if (fw_priv->data) { + if (offset + count > fw_priv->allocated_size) { + retval = -ENOMEM; + goto out; + } + firmware_rw_data(fw_priv, buffer, offset, count, false); + retval = count; + } else { + retval = fw_realloc_pages(fw_sysfs, offset + count); + if (retval) + goto out; + + retval = count; + firmware_rw(fw_priv, buffer, offset, count, false); + } + + fw_priv->size = max_t(size_t, offset + count, fw_priv->size); +out: + mutex_unlock(&fw_lock); + return retval; +} + +static struct bin_attribute firmware_attr_data = { + .attr = { .name = "data", .mode = 0644 }, + .size = 0, + .read = firmware_data_read, + .write = firmware_data_write, +}; + +static struct attribute *fw_dev_attrs[] = { + &dev_attr_loading.attr, + NULL +}; + +static struct bin_attribute *fw_dev_bin_attrs[] = { + &firmware_attr_data, + NULL +}; + +static const struct attribute_group fw_dev_attr_group = { + .attrs = fw_dev_attrs, + .bin_attrs = fw_dev_bin_attrs, +}; + +static const struct attribute_group *fw_dev_attr_groups[] = { + &fw_dev_attr_group, + NULL +}; + +static struct fw_sysfs * +fw_create_instance(struct firmware *firmware, const char *fw_name, + struct device *device, unsigned int opt_flags) +{ + struct fw_sysfs *fw_sysfs; + struct device *f_dev; + + fw_sysfs = kzalloc(sizeof(*fw_sysfs), GFP_KERNEL); + if (!fw_sysfs) { + fw_sysfs = ERR_PTR(-ENOMEM); + goto exit; + } + + fw_sysfs->nowait = !!(opt_flags & FW_OPT_NOWAIT); + fw_sysfs->fw = firmware; + f_dev = &fw_sysfs->dev; + + device_initialize(f_dev); + dev_set_name(f_dev, "%s", fw_name); + f_dev->parent = device; + f_dev->class = &firmware_class; + f_dev->groups = fw_dev_attr_groups; +exit: + return fw_sysfs; +} + +/* load a firmware via user helper */ +static int _request_firmware_load(struct fw_sysfs *fw_sysfs, + unsigned int opt_flags, long timeout) +{ + int retval = 0; + struct device *f_dev = &fw_sysfs->dev; + struct fw_priv *fw_priv = fw_sysfs->fw_priv; + + /* fall back on userspace loading */ + if (!fw_priv->data) + fw_priv->is_paged_buf = true; + + dev_set_uevent_suppress(f_dev, true); + + retval = device_add(f_dev); + if (retval) { + dev_err(f_dev, "%s: device_register failed\n", __func__); + goto err_put_dev; + } + + mutex_lock(&fw_lock); + list_add(&fw_priv->pending_list, &pending_fw_head); + mutex_unlock(&fw_lock); + + if (opt_flags & FW_OPT_UEVENT) { + fw_priv->need_uevent = true; + dev_set_uevent_suppress(f_dev, false); + dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_name); + kobject_uevent(&fw_sysfs->dev.kobj, KOBJ_ADD); + } else { + timeout = MAX_JIFFY_OFFSET; + } + + retval = fw_sysfs_wait_timeout(fw_priv, timeout); + if (retval < 0) { + mutex_lock(&fw_lock); + fw_load_abort(fw_sysfs); + mutex_unlock(&fw_lock); + } + + if (fw_state_is_aborted(fw_priv)) { + if (retval == -ERESTARTSYS) + retval = -EINTR; + else + retval = -EAGAIN; + } else if (fw_priv->is_paged_buf && !fw_priv->data) + retval = -ENOMEM; + + device_del(f_dev); +err_put_dev: + put_device(f_dev); + return retval; +} + +static int fw_load_from_user_helper(struct firmware *firmware, + const char *name, struct device *device, + unsigned int opt_flags) +{ + struct fw_sysfs *fw_sysfs; + long timeout; + int ret; + + timeout = firmware_loading_timeout(); + if (opt_flags & FW_OPT_NOWAIT) { + timeout = usermodehelper_read_lock_wait(timeout); + if (!timeout) { + dev_dbg(device, "firmware: %s loading timed out\n", + name); + return -EBUSY; + } + } else { + ret = usermodehelper_read_trylock(); + if (WARN_ON(ret)) { + dev_err(device, "firmware: %s will not be loaded\n", + name); + return ret; + } + } + + fw_sysfs = fw_create_instance(firmware, name, device, opt_flags); + if (IS_ERR(fw_sysfs)) { + ret = PTR_ERR(fw_sysfs); + goto out_unlock; + } + + fw_sysfs->fw_priv = firmware->priv; + ret = _request_firmware_load(fw_sysfs, opt_flags, timeout); + + if (!ret) + ret = assign_fw(firmware, device, opt_flags); + +out_unlock: + usermodehelper_read_unlock(); + + return ret; +} + +static bool fw_force_sysfs_fallback(unsigned int opt_flags) +{ + if (fw_fallback_config.force_sysfs_fallback) + return true; + if (!(opt_flags & FW_OPT_USERHELPER)) + return false; + return true; +} + +static bool fw_run_sysfs_fallback(unsigned int opt_flags) +{ + if ((opt_flags & FW_OPT_NOFALLBACK)) + return false; + + return fw_force_sysfs_fallback(opt_flags); +} + +int fw_sysfs_fallback(struct firmware *fw, const char *name, + struct device *device, + unsigned int opt_flags, + int ret) +{ + if (!fw_run_sysfs_fallback(opt_flags)) + return ret; + + dev_warn(device, "Falling back to user helper\n"); + return fw_load_from_user_helper(fw, name, device, opt_flags); +} diff --git a/drivers/base/firmware_fallback.h b/drivers/base/firmware_fallback.h new file mode 100644 index 000000000000..550498c7fa4c --- /dev/null +++ b/drivers/base/firmware_fallback.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __FIRMWARE_FALLBACK_H +#define __FIRMWARE_FALLBACK_H + +#include +#include + +/** + * struct firmware_fallback_config - firmware fallback configuratioon settings + * + * Helps describe and fine tune the fallback mechanism. + * + * @force_sysfs_fallback: force the sysfs fallback mechanism to be used + * as if one had enabled CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y. + * @old_timeout: for internal use + * @loading_timeout: the timeout to wait for the fallback mechanism before + * giving up, in seconds. + */ +struct firmware_fallback_config { + const bool force_sysfs_fallback; + int old_timeout; + int loading_timeout; +}; + +#ifdef CONFIG_FW_LOADER_USER_HELPER +int fw_sysfs_fallback(struct firmware *fw, const char *name, + struct device *device, + unsigned int opt_flags, + int ret); +void kill_pending_fw_fallback_reqs(bool only_kill_custom); + +void fw_fallback_set_cache_timeout(void); +void fw_fallback_set_default_timeout(void); + +int register_sysfs_loader(void); +void unregister_sysfs_loader(void); +#else /* CONFIG_FW_LOADER_USER_HELPER */ +static inline int fw_sysfs_fallback(struct firmware *fw, const char *name, + struct device *device, + unsigned int opt_flags, + int ret) +{ + /* Keep carrying over the same error */ + return ret; +} + +static inline void kill_pending_fw_fallback_reqs(bool only_kill_custom) { } +static inline void fw_fallback_set_cache_timeout(void) { } +static inline void fw_fallback_set_default_timeout(void) { } + +static inline int register_sysfs_loader(void) +{ + return 0; +} + +static inline void unregister_sysfs_loader(void) +{ +} +#endif /* CONFIG_FW_LOADER_USER_HELPER */ + +#endif /* __FIRMWARE_FALLBACK_H */ diff --git a/drivers/base/firmware_fallback_table.c b/drivers/base/firmware_fallback_table.c new file mode 100644 index 000000000000..53cc4e492520 --- /dev/null +++ b/drivers/base/firmware_fallback_table.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "firmware_fallback.h" +#include "firmware_loader.h" + +/* + * firmware fallback configuration table + */ + +/* Module or buit-in */ +#ifdef CONFIG_FW_LOADER_USER_HELPER + +struct firmware_fallback_config fw_fallback_config = { + .force_sysfs_fallback = IS_ENABLED(CONFIG_FW_LOADER_USER_HELPER_FALLBACK), + .loading_timeout = 60, + .old_timeout = 60, +}; +EXPORT_SYMBOL_GPL(fw_fallback_config); + +#endif diff --git a/drivers/base/firmware_loader.c b/drivers/base/firmware_loader.c index 9757f9fff01e..21dd31ef08ae 100644 --- a/drivers/base/firmware_loader.c +++ b/drivers/base/firmware_loader.c @@ -37,36 +37,13 @@ #include #include "base.h" +#include "firmware_loader.h" +#include "firmware_fallback.h" MODULE_AUTHOR("Manuel Estrada Sainz"); MODULE_DESCRIPTION("Multi purpose firmware loading support"); MODULE_LICENSE("GPL"); -enum fw_status { - FW_STATUS_UNKNOWN, - FW_STATUS_LOADING, - FW_STATUS_DONE, - FW_STATUS_ABORTED, -}; - -/* - * Concurrent request_firmware() for the same firmware need to be - * serialized. struct fw_state is simple state machine which hold the - * state of the firmware loading. - */ -struct fw_state { - struct completion completion; - enum fw_status status; -}; - -/* firmware behavior options */ -#define FW_OPT_UEVENT (1U << 0) -#define FW_OPT_NOWAIT (1U << 1) -#define FW_OPT_USERHELPER (1U << 2) -#define FW_OPT_NO_WARN (1U << 3) -#define FW_OPT_NOCACHE (1U << 4) -#define FW_OPT_NOFALLBACK (1U << 5) - struct firmware_cache { /* firmware_buf instance will be added into the below list */ spinlock_t lock; @@ -89,25 +66,6 @@ struct firmware_cache { #endif }; -struct fw_priv { - struct kref ref; - struct list_head list; - struct firmware_cache *fwc; - struct fw_state fw_st; - void *data; - size_t size; - size_t allocated_size; -#ifdef CONFIG_FW_LOADER_USER_HELPER - bool is_paged_buf; - bool need_uevent; - struct page **pages; - int nr_pages; - int page_array_size; - struct list_head pending_list; -#endif - const char *fw_name; -}; - struct fw_cache_entry { struct list_head list; const char *name; @@ -128,7 +86,7 @@ static inline struct fw_priv *to_fw_priv(struct kref *ref) /* fw_lock could be moved to 'struct fw_sysfs' but since it is just * guarding for corner cases a global lock should be OK */ -static DEFINE_MUTEX(fw_lock); +DEFINE_MUTEX(fw_lock); static struct firmware_cache fw_cache; @@ -199,142 +157,11 @@ static void fw_state_init(struct fw_priv *fw_priv) fw_st->status = FW_STATUS_UNKNOWN; } -static int __fw_state_wait_common(struct fw_priv *fw_priv, long timeout) -{ - struct fw_state *fw_st = &fw_priv->fw_st; - long ret; - - ret = wait_for_completion_killable_timeout(&fw_st->completion, timeout); - if (ret != 0 && fw_st->status == FW_STATUS_ABORTED) - return -ENOENT; - if (!ret) - return -ETIMEDOUT; - - return ret < 0 ? ret : 0; -} - -static void __fw_state_set(struct fw_priv *fw_priv, - enum fw_status status) -{ - struct fw_state *fw_st = &fw_priv->fw_st; - - WRITE_ONCE(fw_st->status, status); - - if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED) - complete_all(&fw_st->completion); -} - -static inline void fw_state_start(struct fw_priv *fw_priv) -{ - __fw_state_set(fw_priv, FW_STATUS_LOADING); -} - -static inline void fw_state_done(struct fw_priv *fw_priv) -{ - __fw_state_set(fw_priv, FW_STATUS_DONE); -} - -static inline void fw_state_aborted(struct fw_priv *fw_priv) -{ - __fw_state_set(fw_priv, FW_STATUS_ABORTED); -} - static inline int fw_state_wait(struct fw_priv *fw_priv) { return __fw_state_wait_common(fw_priv, MAX_SCHEDULE_TIMEOUT); } -static bool __fw_state_check(struct fw_priv *fw_priv, - enum fw_status status) -{ - struct fw_state *fw_st = &fw_priv->fw_st; - - return fw_st->status == status; -} - -static inline bool fw_state_is_aborted(struct fw_priv *fw_priv) -{ - return __fw_state_check(fw_priv, FW_STATUS_ABORTED); -} - -#ifdef CONFIG_FW_LOADER_USER_HELPER - -/** - * struct firmware_fallback_config - firmware fallback configuratioon settings - * - * Helps describe and fine tune the fallback mechanism. - * - * @force_sysfs_fallback: force the sysfs fallback mechanism to be used - * as if one had enabled CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y. - * @old_timeout: for internal use - * @loading_timeout: the timeout to wait for the fallback mechanism before - * giving up, in seconds. - */ -struct firmware_fallback_config { - const bool force_sysfs_fallback; - int old_timeout; - int loading_timeout; -}; - -static struct firmware_fallback_config fw_fallback_config = { - .force_sysfs_fallback = IS_ENABLED(CONFIG_FW_LOADER_USER_HELPER_FALLBACK), - .loading_timeout = 60, - .old_timeout = 60, -}; - -/* 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; -} - -static inline long firmware_loading_timeout(void) -{ - return __firmware_loading_timeout() > 0 ? - __firmware_loading_timeout() * HZ : MAX_JIFFY_OFFSET; -} - -/* - * use small loading timeout for caching devices' firmware because all these - * firmware images have been loaded successfully at lease once, also system is - * ready for completing firmware loading now. The maximum size of firmware in - * current distributions is about 2M bytes, so 10 secs should be enough. - */ -static void fw_fallback_set_cache_timeout(void) -{ - fw_fallback_config.old_timeout = __firmware_loading_timeout(); - __fw_fallback_set_timeout(10); -} - -/* Restores the timeout to the value last configured during normal operation */ -static void fw_fallback_set_default_timeout(void) -{ - __fw_fallback_set_timeout(fw_fallback_config.old_timeout); -} - -static inline bool fw_sysfs_done(struct fw_priv *fw_priv) -{ - return __fw_state_check(fw_priv, FW_STATUS_DONE); -} - -static inline bool fw_sysfs_loading(struct fw_priv *fw_priv) -{ - return __fw_state_check(fw_priv, FW_STATUS_LOADING); -} - -static inline int fw_sysfs_wait_timeout(struct fw_priv *fw_priv, long timeout) -{ - return __fw_state_wait_common(fw_priv, timeout); -} - -#endif /* CONFIG_FW_LOADER_USER_HELPER */ - static int fw_cache_piggyback_on_request(const char *name); static struct fw_priv *__allocate_fw_priv(const char *fw_name, @@ -600,8 +427,8 @@ static int fw_add_devm_name(struct device *dev, const char *name) } #endif -static int assign_fw(struct firmware *fw, struct device *device, - unsigned int opt_flags) +int assign_fw(struct firmware *fw, struct device *device, + unsigned int opt_flags) { struct fw_priv *fw_priv = fw->priv; @@ -639,626 +466,6 @@ static int assign_fw(struct firmware *fw, struct device *device, return 0; } -/* - * user-mode helper code - */ -#ifdef CONFIG_FW_LOADER_USER_HELPER -struct fw_sysfs { - bool nowait; - struct device dev; - struct fw_priv *fw_priv; - struct firmware *fw; -}; - -static struct fw_sysfs *to_fw_sysfs(struct device *dev) -{ - return container_of(dev, struct fw_sysfs, dev); -} - -static void __fw_load_abort(struct fw_priv *fw_priv) -{ - /* - * There is a small window in which user can write to 'loading' - * between loading done and disappearance of 'loading' - */ - if (fw_sysfs_done(fw_priv)) - return; - - list_del_init(&fw_priv->pending_list); - fw_state_aborted(fw_priv); -} - -static void fw_load_abort(struct fw_sysfs *fw_sysfs) -{ - struct fw_priv *fw_priv = fw_sysfs->fw_priv; - - __fw_load_abort(fw_priv); -} - -static LIST_HEAD(pending_fw_head); - -static void kill_pending_fw_fallback_reqs(bool only_kill_custom) -{ - struct fw_priv *fw_priv; - struct fw_priv *next; - - mutex_lock(&fw_lock); - list_for_each_entry_safe(fw_priv, next, &pending_fw_head, - pending_list) { - if (!fw_priv->need_uevent || !only_kill_custom) - __fw_load_abort(fw_priv); - } - mutex_unlock(&fw_lock); -} - -static ssize_t timeout_show(struct class *class, struct class_attribute *attr, - char *buf) -{ - return sprintf(buf, "%d\n", __firmware_loading_timeout()); -} - -/** - * firmware_timeout_store - set number of seconds to wait for firmware - * @class: device class pointer - * @attr: device attribute pointer - * @buf: buffer to scan for timeout value - * @count: number of bytes in @buf - * - * Sets the number of seconds to wait for the firmware. Once - * this expires an error will be returned to the driver and no - * firmware will be provided. - * - * Note: zero means 'wait forever'. - **/ -static ssize_t timeout_store(struct class *class, struct class_attribute *attr, - const char *buf, size_t count) -{ - int tmp_loading_timeout = simple_strtol(buf, NULL, 10); - - if (tmp_loading_timeout < 0) - tmp_loading_timeout = 0; - - __fw_fallback_set_timeout(tmp_loading_timeout); - - return count; -} -static CLASS_ATTR_RW(timeout); - -static struct attribute *firmware_class_attrs[] = { - &class_attr_timeout.attr, - NULL, -}; -ATTRIBUTE_GROUPS(firmware_class); - -static void fw_dev_release(struct device *dev) -{ - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - - kfree(fw_sysfs); -} - -static int do_firmware_uevent(struct fw_sysfs *fw_sysfs, struct kobj_uevent_env *env) -{ - if (add_uevent_var(env, "FIRMWARE=%s", fw_sysfs->fw_priv->fw_name)) - return -ENOMEM; - if (add_uevent_var(env, "TIMEOUT=%i", __firmware_loading_timeout())) - return -ENOMEM; - if (add_uevent_var(env, "ASYNC=%d", fw_sysfs->nowait)) - return -ENOMEM; - - return 0; -} - -static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env) -{ - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - int err = 0; - - mutex_lock(&fw_lock); - if (fw_sysfs->fw_priv) - err = do_firmware_uevent(fw_sysfs, env); - mutex_unlock(&fw_lock); - return err; -} - -static struct class firmware_class = { - .name = "firmware", - .class_groups = firmware_class_groups, - .dev_uevent = firmware_uevent, - .dev_release = fw_dev_release, -}; - -static inline int register_sysfs_loader(void) -{ - return class_register(&firmware_class); -} - -static inline void unregister_sysfs_loader(void) -{ - class_unregister(&firmware_class); -} - -static ssize_t firmware_loading_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - int loading = 0; - - mutex_lock(&fw_lock); - if (fw_sysfs->fw_priv) - loading = fw_sysfs_loading(fw_sysfs->fw_priv); - mutex_unlock(&fw_lock); - - return sprintf(buf, "%d\n", loading); -} - -/* Some architectures don't have PAGE_KERNEL_RO */ -#ifndef PAGE_KERNEL_RO -#define PAGE_KERNEL_RO PAGE_KERNEL -#endif - -/* one pages buffer should be mapped/unmapped only once */ -static int map_fw_priv_pages(struct fw_priv *fw_priv) -{ - if (!fw_priv->is_paged_buf) - return 0; - - vunmap(fw_priv->data); - fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, 0, - PAGE_KERNEL_RO); - if (!fw_priv->data) - return -ENOMEM; - return 0; -} - -/** - * firmware_loading_store - set value in the 'loading' control file - * @dev: device pointer - * @attr: device attribute pointer - * @buf: buffer to scan for loading control value - * @count: number of bytes in @buf - * - * The relevant values are: - * - * 1: Start a load, discarding any previous partial load. - * 0: Conclude the load and hand the data to the driver code. - * -1: Conclude the load with an error and discard any written data. - **/ -static ssize_t firmware_loading_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - struct fw_priv *fw_priv; - ssize_t written = count; - int loading = simple_strtol(buf, NULL, 10); - int i; - - mutex_lock(&fw_lock); - fw_priv = fw_sysfs->fw_priv; - if (fw_state_is_aborted(fw_priv)) - goto out; - - switch (loading) { - case 1: - /* discarding any previous partial load */ - if (!fw_sysfs_done(fw_priv)) { - for (i = 0; i < fw_priv->nr_pages; i++) - __free_page(fw_priv->pages[i]); - vfree(fw_priv->pages); - fw_priv->pages = NULL; - fw_priv->page_array_size = 0; - fw_priv->nr_pages = 0; - fw_state_start(fw_priv); - } - break; - case 0: - if (fw_sysfs_loading(fw_priv)) { - int rc; - - /* - * Several loading requests may be pending on - * one same firmware buf, so let all requests - * see the mapped 'buf->data' once the loading - * is completed. - * */ - rc = map_fw_priv_pages(fw_priv); - if (rc) - dev_err(dev, "%s: map pages failed\n", - __func__); - else - rc = security_kernel_post_read_file(NULL, - fw_priv->data, fw_priv->size, - READING_FIRMWARE); - - /* - * Same logic as fw_load_abort, only the DONE bit - * is ignored and we set ABORT only on failure. - */ - list_del_init(&fw_priv->pending_list); - if (rc) { - fw_state_aborted(fw_priv); - written = rc; - } else { - fw_state_done(fw_priv); - } - break; - } - /* fallthrough */ - default: - dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); - /* fallthrough */ - case -1: - fw_load_abort(fw_sysfs); - break; - } -out: - mutex_unlock(&fw_lock); - return written; -} - -static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store); - -static void firmware_rw_data(struct fw_priv *fw_priv, char *buffer, - loff_t offset, size_t count, bool read) -{ - if (read) - memcpy(buffer, fw_priv->data + offset, count); - else - memcpy(fw_priv->data + offset, buffer, count); -} - -static void firmware_rw(struct fw_priv *fw_priv, char *buffer, - loff_t offset, size_t count, bool read) -{ - while (count) { - void *page_data; - int page_nr = offset >> PAGE_SHIFT; - int page_ofs = offset & (PAGE_SIZE-1); - int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count); - - page_data = kmap(fw_priv->pages[page_nr]); - - if (read) - memcpy(buffer, page_data + page_ofs, page_cnt); - else - memcpy(page_data + page_ofs, buffer, page_cnt); - - kunmap(fw_priv->pages[page_nr]); - buffer += page_cnt; - offset += page_cnt; - count -= page_cnt; - } -} - -static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t offset, size_t count) -{ - struct device *dev = kobj_to_dev(kobj); - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - struct fw_priv *fw_priv; - ssize_t ret_count; - - mutex_lock(&fw_lock); - fw_priv = fw_sysfs->fw_priv; - if (!fw_priv || fw_sysfs_done(fw_priv)) { - ret_count = -ENODEV; - goto out; - } - if (offset > fw_priv->size) { - ret_count = 0; - goto out; - } - if (count > fw_priv->size - offset) - count = fw_priv->size - offset; - - ret_count = count; - - if (fw_priv->data) - firmware_rw_data(fw_priv, buffer, offset, count, true); - else - firmware_rw(fw_priv, buffer, offset, count, true); - -out: - mutex_unlock(&fw_lock); - return ret_count; -} - -static int fw_realloc_pages(struct fw_sysfs *fw_sysfs, int min_size) -{ - struct fw_priv *fw_priv= fw_sysfs->fw_priv; - int pages_needed = PAGE_ALIGN(min_size) >> PAGE_SHIFT; - - /* If the array of pages is too small, grow it... */ - if (fw_priv->page_array_size < pages_needed) { - int new_array_size = max(pages_needed, - fw_priv->page_array_size * 2); - struct page **new_pages; - - new_pages = vmalloc(new_array_size * sizeof(void *)); - if (!new_pages) { - fw_load_abort(fw_sysfs); - return -ENOMEM; - } - memcpy(new_pages, fw_priv->pages, - fw_priv->page_array_size * sizeof(void *)); - memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) * - (new_array_size - fw_priv->page_array_size)); - vfree(fw_priv->pages); - fw_priv->pages = new_pages; - fw_priv->page_array_size = new_array_size; - } - - while (fw_priv->nr_pages < pages_needed) { - fw_priv->pages[fw_priv->nr_pages] = - alloc_page(GFP_KERNEL | __GFP_HIGHMEM); - - if (!fw_priv->pages[fw_priv->nr_pages]) { - fw_load_abort(fw_sysfs); - return -ENOMEM; - } - fw_priv->nr_pages++; - } - return 0; -} - -/** - * firmware_data_write - write method for firmware - * @filp: open sysfs file - * @kobj: kobject for the device - * @bin_attr: bin_attr structure - * @buffer: buffer being written - * @offset: buffer offset for write in total data store area - * @count: buffer size - * - * Data written to the 'data' attribute will be later handed to - * the driver as a firmware image. - **/ -static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t offset, size_t count) -{ - struct device *dev = kobj_to_dev(kobj); - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - struct fw_priv *fw_priv; - ssize_t retval; - - if (!capable(CAP_SYS_RAWIO)) - return -EPERM; - - mutex_lock(&fw_lock); - fw_priv = fw_sysfs->fw_priv; - if (!fw_priv || fw_sysfs_done(fw_priv)) { - retval = -ENODEV; - goto out; - } - - if (fw_priv->data) { - if (offset + count > fw_priv->allocated_size) { - retval = -ENOMEM; - goto out; - } - firmware_rw_data(fw_priv, buffer, offset, count, false); - retval = count; - } else { - retval = fw_realloc_pages(fw_sysfs, offset + count); - if (retval) - goto out; - - retval = count; - firmware_rw(fw_priv, buffer, offset, count, false); - } - - fw_priv->size = max_t(size_t, offset + count, fw_priv->size); -out: - mutex_unlock(&fw_lock); - return retval; -} - -static struct bin_attribute firmware_attr_data = { - .attr = { .name = "data", .mode = 0644 }, - .size = 0, - .read = firmware_data_read, - .write = firmware_data_write, -}; - -static struct attribute *fw_dev_attrs[] = { - &dev_attr_loading.attr, - NULL -}; - -static struct bin_attribute *fw_dev_bin_attrs[] = { - &firmware_attr_data, - NULL -}; - -static const struct attribute_group fw_dev_attr_group = { - .attrs = fw_dev_attrs, - .bin_attrs = fw_dev_bin_attrs, -}; - -static const struct attribute_group *fw_dev_attr_groups[] = { - &fw_dev_attr_group, - NULL -}; - -static struct fw_sysfs * -fw_create_instance(struct firmware *firmware, const char *fw_name, - struct device *device, unsigned int opt_flags) -{ - struct fw_sysfs *fw_sysfs; - struct device *f_dev; - - fw_sysfs = kzalloc(sizeof(*fw_sysfs), GFP_KERNEL); - if (!fw_sysfs) { - fw_sysfs = ERR_PTR(-ENOMEM); - goto exit; - } - - fw_sysfs->nowait = !!(opt_flags & FW_OPT_NOWAIT); - fw_sysfs->fw = firmware; - f_dev = &fw_sysfs->dev; - - device_initialize(f_dev); - dev_set_name(f_dev, "%s", fw_name); - f_dev->parent = device; - f_dev->class = &firmware_class; - f_dev->groups = fw_dev_attr_groups; -exit: - return fw_sysfs; -} - -/* load a firmware via user helper */ -static int _request_firmware_load(struct fw_sysfs *fw_sysfs, - unsigned int opt_flags, long timeout) -{ - int retval = 0; - struct device *f_dev = &fw_sysfs->dev; - struct fw_priv *fw_priv = fw_sysfs->fw_priv; - - /* fall back on userspace loading */ - if (!fw_priv->data) - fw_priv->is_paged_buf = true; - - dev_set_uevent_suppress(f_dev, true); - - retval = device_add(f_dev); - if (retval) { - dev_err(f_dev, "%s: device_register failed\n", __func__); - goto err_put_dev; - } - - mutex_lock(&fw_lock); - list_add(&fw_priv->pending_list, &pending_fw_head); - mutex_unlock(&fw_lock); - - if (opt_flags & FW_OPT_UEVENT) { - fw_priv->need_uevent = true; - dev_set_uevent_suppress(f_dev, false); - dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_name); - kobject_uevent(&fw_sysfs->dev.kobj, KOBJ_ADD); - } else { - timeout = MAX_JIFFY_OFFSET; - } - - retval = fw_sysfs_wait_timeout(fw_priv, timeout); - if (retval < 0) { - mutex_lock(&fw_lock); - fw_load_abort(fw_sysfs); - mutex_unlock(&fw_lock); - } - - if (fw_state_is_aborted(fw_priv)) { - if (retval == -ERESTARTSYS) - retval = -EINTR; - else - retval = -EAGAIN; - } else if (fw_priv->is_paged_buf && !fw_priv->data) - retval = -ENOMEM; - - device_del(f_dev); -err_put_dev: - put_device(f_dev); - return retval; -} - -static int fw_load_from_user_helper(struct firmware *firmware, - const char *name, struct device *device, - unsigned int opt_flags) -{ - struct fw_sysfs *fw_sysfs; - long timeout; - int ret; - - timeout = firmware_loading_timeout(); - if (opt_flags & FW_OPT_NOWAIT) { - timeout = usermodehelper_read_lock_wait(timeout); - if (!timeout) { - dev_dbg(device, "firmware: %s loading timed out\n", - name); - return -EBUSY; - } - } else { - ret = usermodehelper_read_trylock(); - if (WARN_ON(ret)) { - dev_err(device, "firmware: %s will not be loaded\n", - name); - return ret; - } - } - - fw_sysfs = fw_create_instance(firmware, name, device, opt_flags); - if (IS_ERR(fw_sysfs)) { - ret = PTR_ERR(fw_sysfs); - goto out_unlock; - } - - fw_sysfs->fw_priv = firmware->priv; - ret = _request_firmware_load(fw_sysfs, opt_flags, timeout); - - if (!ret) - ret = assign_fw(firmware, device, opt_flags); - -out_unlock: - usermodehelper_read_unlock(); - - return ret; -} - -static bool fw_force_sysfs_fallback(unsigned int opt_flags) -{ - if (fw_fallback_config.force_sysfs_fallback) - return true; - if (!(opt_flags & FW_OPT_USERHELPER)) - return false; - return true; -} - -static bool fw_run_sysfs_fallback(unsigned int opt_flags) -{ - if ((opt_flags & FW_OPT_NOFALLBACK)) - return false; - - return fw_force_sysfs_fallback(opt_flags); -} - -static int fw_sysfs_fallback(struct firmware *fw, const char *name, - struct device *device, - unsigned int opt_flags, - int ret) -{ - if (!fw_run_sysfs_fallback(opt_flags)) - return ret; - - dev_warn(device, "Falling back to user helper\n"); - return fw_load_from_user_helper(fw, name, device, opt_flags); -} -#else /* CONFIG_FW_LOADER_USER_HELPER */ -static int fw_sysfs_fallback(struct firmware *fw, const char *name, - struct device *device, - unsigned int opt_flags, - int ret) -{ - /* Keep carrying over the same error */ - return ret; -} - -static inline void kill_pending_fw_fallback_reqs(bool only_kill_custom) { } -static inline 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 */ - /* prepare firmware and firmware_buf structs; * return 0 if a firmware is already assigned, 1 if need to load one, * or a negative error code diff --git a/drivers/base/firmware_loader.h b/drivers/base/firmware_loader.h new file mode 100644 index 000000000000..64acbb1a392c --- /dev/null +++ b/drivers/base/firmware_loader.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __FIRMWARE_LOADER_H +#define __FIRMWARE_LOADER_H + +#include +#include +#include +#include +#include + +#include + +/* firmware behavior options */ +#define FW_OPT_UEVENT (1U << 0) +#define FW_OPT_NOWAIT (1U << 1) +#define FW_OPT_USERHELPER (1U << 2) +#define FW_OPT_NO_WARN (1U << 3) +#define FW_OPT_NOCACHE (1U << 4) +#define FW_OPT_NOFALLBACK (1U << 5) + +enum fw_status { + FW_STATUS_UNKNOWN, + FW_STATUS_LOADING, + FW_STATUS_DONE, + FW_STATUS_ABORTED, +}; + +/* + * Concurrent request_firmware() for the same firmware need to be + * serialized. struct fw_state is simple state machine which hold the + * state of the firmware loading. + */ +struct fw_state { + struct completion completion; + enum fw_status status; +}; + +struct fw_priv { + struct kref ref; + struct list_head list; + struct firmware_cache *fwc; + struct fw_state fw_st; + void *data; + size_t size; + size_t allocated_size; +#ifdef CONFIG_FW_LOADER_USER_HELPER + bool is_paged_buf; + bool need_uevent; + struct page **pages; + int nr_pages; + int page_array_size; + struct list_head pending_list; +#endif + const char *fw_name; +}; + +extern struct mutex fw_lock; + +static inline bool __fw_state_check(struct fw_priv *fw_priv, + enum fw_status status) +{ + struct fw_state *fw_st = &fw_priv->fw_st; + + return fw_st->status == status; +} + +static inline int __fw_state_wait_common(struct fw_priv *fw_priv, long timeout) +{ + struct fw_state *fw_st = &fw_priv->fw_st; + long ret; + + ret = wait_for_completion_killable_timeout(&fw_st->completion, timeout); + if (ret != 0 && fw_st->status == FW_STATUS_ABORTED) + return -ENOENT; + if (!ret) + return -ETIMEDOUT; + + return ret < 0 ? ret : 0; +} + +static inline void __fw_state_set(struct fw_priv *fw_priv, + enum fw_status status) +{ + struct fw_state *fw_st = &fw_priv->fw_st; + + WRITE_ONCE(fw_st->status, status); + + if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED) + complete_all(&fw_st->completion); +} + +static inline void fw_state_aborted(struct fw_priv *fw_priv) +{ + __fw_state_set(fw_priv, FW_STATUS_ABORTED); +} + +static inline bool fw_state_is_aborted(struct fw_priv *fw_priv) +{ + return __fw_state_check(fw_priv, FW_STATUS_ABORTED); +} + +static inline void fw_state_start(struct fw_priv *fw_priv) +{ + __fw_state_set(fw_priv, FW_STATUS_LOADING); +} + +static inline void fw_state_done(struct fw_priv *fw_priv) +{ + __fw_state_set(fw_priv, FW_STATUS_DONE); +} + +int assign_fw(struct firmware *fw, struct device *device, + unsigned int opt_flags); + +#endif /* __FIRMWARE_LOADER_H */ -- cgit v1.2.3 From 5d6d1ddd27301dc85b13b794262c8bcececf88f1 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:50 -0800 Subject: firmware: move firmware loader into its own directory This will make it much easier to manage as we manage to keep trimming componnents down into their own files to more easily manage and maintain this codebase. Suggested-by: Kees Cook Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 2 +- drivers/base/Makefile | 7 +- drivers/base/firmware_fallback.c | 661 -------------- drivers/base/firmware_fallback.h | 61 -- drivers/base/firmware_fallback_table.c | 29 - drivers/base/firmware_loader.c | 1189 ------------------------- drivers/base/firmware_loader.h | 115 --- drivers/base/firmware_loader/Makefile | 7 + drivers/base/firmware_loader/fallback.c | 661 ++++++++++++++ drivers/base/firmware_loader/fallback.h | 61 ++ drivers/base/firmware_loader/fallback_table.c | 29 + drivers/base/firmware_loader/firmware.h | 115 +++ drivers/base/firmware_loader/main.c | 1189 +++++++++++++++++++++++++ 13 files changed, 2065 insertions(+), 2061 deletions(-) delete mode 100644 drivers/base/firmware_fallback.c delete mode 100644 drivers/base/firmware_fallback.h delete mode 100644 drivers/base/firmware_fallback_table.c delete mode 100644 drivers/base/firmware_loader.c delete mode 100644 drivers/base/firmware_loader.h create mode 100644 drivers/base/firmware_loader/Makefile create mode 100644 drivers/base/firmware_loader/fallback.c create mode 100644 drivers/base/firmware_loader/fallback.h create mode 100644 drivers/base/firmware_loader/fallback_table.c create mode 100644 drivers/base/firmware_loader/firmware.h create mode 100644 drivers/base/firmware_loader/main.c diff --git a/MAINTAINERS b/MAINTAINERS index 4623caf8d72d..44c6ea94d727 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5552,7 +5552,7 @@ M: Luis R. Rodriguez L: linux-kernel@vger.kernel.org S: Maintained F: Documentation/firmware_class/ -F: drivers/base/firmware*.c +F: drivers/base/firmware_loader/ F: include/linux/firmware.h FLASH ADAPTER DRIVER (IBM Flash Adapter 900GB Full Height PCI Flash Card) diff --git a/drivers/base/Makefile b/drivers/base/Makefile index b946a408256d..b9539abec675 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -5,17 +5,14 @@ obj-y := component.o core.o bus.o dd.o syscore.o \ driver.o class.o platform.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ - topology.o container.o property.o cacheinfo.o \ - firmware_fallback_table.o + topology.o container.o property.o cacheinfo.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-$(CONFIG_DMA_CMA) += dma-contiguous.o obj-y += power/ obj-$(CONFIG_HAS_DMA) += dma-mapping.o obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.o obj-$(CONFIG_ISA_BUS_API) += isa.o -obj-$(CONFIG_FW_LOADER) += firmware_class.o -firmware_class-objs := firmware_loader.o -firmware_class-$(CONFIG_FW_LOADER_USER_HELPER) += firmware_fallback.o +obj-y += firmware_loader/ obj-$(CONFIG_NUMA) += node.o obj-$(CONFIG_MEMORY_HOTPLUG_SPARSE) += memory.o ifeq ($(CONFIG_SYSFS),y) diff --git a/drivers/base/firmware_fallback.c b/drivers/base/firmware_fallback.c deleted file mode 100644 index 47690207e0ee..000000000000 --- a/drivers/base/firmware_fallback.c +++ /dev/null @@ -1,661 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#include -#include -#include -#include -#include -#include -#include - -#include "firmware_fallback.h" -#include "firmware_loader.h" - -/* - * firmware fallback mechanism - */ - -extern struct firmware_fallback_config fw_fallback_config; - -/* These getters are vetted to use int properly */ -static inline int __firmware_loading_timeout(void) -{ - return fw_fallback_config.loading_timeout; -} - -/* These setters are vetted to use int properly */ -static void __fw_fallback_set_timeout(int timeout) -{ - fw_fallback_config.loading_timeout = timeout; -} - -/* - * use small loading timeout for caching devices' firmware because all these - * firmware images have been loaded successfully at lease once, also system is - * ready for completing firmware loading now. The maximum size of firmware in - * current distributions is about 2M bytes, so 10 secs should be enough. - */ -void fw_fallback_set_cache_timeout(void) -{ - fw_fallback_config.old_timeout = __firmware_loading_timeout(); - __fw_fallback_set_timeout(10); -} - -/* Restores the timeout to the value last configured during normal operation */ -void fw_fallback_set_default_timeout(void) -{ - __fw_fallback_set_timeout(fw_fallback_config.old_timeout); -} - -static long firmware_loading_timeout(void) -{ - return __firmware_loading_timeout() > 0 ? - __firmware_loading_timeout() * HZ : MAX_JIFFY_OFFSET; -} - -static inline bool fw_sysfs_done(struct fw_priv *fw_priv) -{ - return __fw_state_check(fw_priv, FW_STATUS_DONE); -} - -static inline bool fw_sysfs_loading(struct fw_priv *fw_priv) -{ - return __fw_state_check(fw_priv, FW_STATUS_LOADING); -} - -static inline int fw_sysfs_wait_timeout(struct fw_priv *fw_priv, long timeout) -{ - return __fw_state_wait_common(fw_priv, timeout); -} - -struct fw_sysfs { - bool nowait; - struct device dev; - struct fw_priv *fw_priv; - struct firmware *fw; -}; - -static struct fw_sysfs *to_fw_sysfs(struct device *dev) -{ - return container_of(dev, struct fw_sysfs, dev); -} - -static void __fw_load_abort(struct fw_priv *fw_priv) -{ - /* - * There is a small window in which user can write to 'loading' - * between loading done and disappearance of 'loading' - */ - if (fw_sysfs_done(fw_priv)) - return; - - list_del_init(&fw_priv->pending_list); - fw_state_aborted(fw_priv); -} - -static void fw_load_abort(struct fw_sysfs *fw_sysfs) -{ - struct fw_priv *fw_priv = fw_sysfs->fw_priv; - - __fw_load_abort(fw_priv); -} - -static LIST_HEAD(pending_fw_head); - -void kill_pending_fw_fallback_reqs(bool only_kill_custom) -{ - struct fw_priv *fw_priv; - struct fw_priv *next; - - mutex_lock(&fw_lock); - list_for_each_entry_safe(fw_priv, next, &pending_fw_head, - pending_list) { - if (!fw_priv->need_uevent || !only_kill_custom) - __fw_load_abort(fw_priv); - } - mutex_unlock(&fw_lock); -} - -static ssize_t timeout_show(struct class *class, struct class_attribute *attr, - char *buf) -{ - return sprintf(buf, "%d\n", __firmware_loading_timeout()); -} - -/** - * firmware_timeout_store - set number of seconds to wait for firmware - * @class: device class pointer - * @attr: device attribute pointer - * @buf: buffer to scan for timeout value - * @count: number of bytes in @buf - * - * Sets the number of seconds to wait for the firmware. Once - * this expires an error will be returned to the driver and no - * firmware will be provided. - * - * Note: zero means 'wait forever'. - **/ -static ssize_t timeout_store(struct class *class, struct class_attribute *attr, - const char *buf, size_t count) -{ - int tmp_loading_timeout = simple_strtol(buf, NULL, 10); - - if (tmp_loading_timeout < 0) - tmp_loading_timeout = 0; - - __fw_fallback_set_timeout(tmp_loading_timeout); - - return count; -} -static CLASS_ATTR_RW(timeout); - -static struct attribute *firmware_class_attrs[] = { - &class_attr_timeout.attr, - NULL, -}; -ATTRIBUTE_GROUPS(firmware_class); - -static void fw_dev_release(struct device *dev) -{ - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - - kfree(fw_sysfs); -} - -static int do_firmware_uevent(struct fw_sysfs *fw_sysfs, struct kobj_uevent_env *env) -{ - if (add_uevent_var(env, "FIRMWARE=%s", fw_sysfs->fw_priv->fw_name)) - return -ENOMEM; - if (add_uevent_var(env, "TIMEOUT=%i", __firmware_loading_timeout())) - return -ENOMEM; - if (add_uevent_var(env, "ASYNC=%d", fw_sysfs->nowait)) - return -ENOMEM; - - return 0; -} - -static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env) -{ - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - int err = 0; - - mutex_lock(&fw_lock); - if (fw_sysfs->fw_priv) - err = do_firmware_uevent(fw_sysfs, env); - mutex_unlock(&fw_lock); - return err; -} - -static struct class firmware_class = { - .name = "firmware", - .class_groups = firmware_class_groups, - .dev_uevent = firmware_uevent, - .dev_release = fw_dev_release, -}; - -int register_sysfs_loader(void) -{ - return class_register(&firmware_class); -} - -void unregister_sysfs_loader(void) -{ - class_unregister(&firmware_class); -} - -static ssize_t firmware_loading_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - int loading = 0; - - mutex_lock(&fw_lock); - if (fw_sysfs->fw_priv) - loading = fw_sysfs_loading(fw_sysfs->fw_priv); - mutex_unlock(&fw_lock); - - return sprintf(buf, "%d\n", loading); -} - -/* Some architectures don't have PAGE_KERNEL_RO */ -#ifndef PAGE_KERNEL_RO -#define PAGE_KERNEL_RO PAGE_KERNEL -#endif - -/* one pages buffer should be mapped/unmapped only once */ -static int map_fw_priv_pages(struct fw_priv *fw_priv) -{ - if (!fw_priv->is_paged_buf) - return 0; - - vunmap(fw_priv->data); - fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, 0, - PAGE_KERNEL_RO); - if (!fw_priv->data) - return -ENOMEM; - return 0; -} - -/** - * firmware_loading_store - set value in the 'loading' control file - * @dev: device pointer - * @attr: device attribute pointer - * @buf: buffer to scan for loading control value - * @count: number of bytes in @buf - * - * The relevant values are: - * - * 1: Start a load, discarding any previous partial load. - * 0: Conclude the load and hand the data to the driver code. - * -1: Conclude the load with an error and discard any written data. - **/ -static ssize_t firmware_loading_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - struct fw_priv *fw_priv; - ssize_t written = count; - int loading = simple_strtol(buf, NULL, 10); - int i; - - mutex_lock(&fw_lock); - fw_priv = fw_sysfs->fw_priv; - if (fw_state_is_aborted(fw_priv)) - goto out; - - switch (loading) { - case 1: - /* discarding any previous partial load */ - if (!fw_sysfs_done(fw_priv)) { - for (i = 0; i < fw_priv->nr_pages; i++) - __free_page(fw_priv->pages[i]); - vfree(fw_priv->pages); - fw_priv->pages = NULL; - fw_priv->page_array_size = 0; - fw_priv->nr_pages = 0; - fw_state_start(fw_priv); - } - break; - case 0: - if (fw_sysfs_loading(fw_priv)) { - int rc; - - /* - * Several loading requests may be pending on - * one same firmware buf, so let all requests - * see the mapped 'buf->data' once the loading - * is completed. - * */ - rc = map_fw_priv_pages(fw_priv); - if (rc) - dev_err(dev, "%s: map pages failed\n", - __func__); - else - rc = security_kernel_post_read_file(NULL, - fw_priv->data, fw_priv->size, - READING_FIRMWARE); - - /* - * Same logic as fw_load_abort, only the DONE bit - * is ignored and we set ABORT only on failure. - */ - list_del_init(&fw_priv->pending_list); - if (rc) { - fw_state_aborted(fw_priv); - written = rc; - } else { - fw_state_done(fw_priv); - } - break; - } - /* fallthrough */ - default: - dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); - /* fallthrough */ - case -1: - fw_load_abort(fw_sysfs); - break; - } -out: - mutex_unlock(&fw_lock); - return written; -} - -static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store); - -static void firmware_rw_data(struct fw_priv *fw_priv, char *buffer, - loff_t offset, size_t count, bool read) -{ - if (read) - memcpy(buffer, fw_priv->data + offset, count); - else - memcpy(fw_priv->data + offset, buffer, count); -} - -static void firmware_rw(struct fw_priv *fw_priv, char *buffer, - loff_t offset, size_t count, bool read) -{ - while (count) { - void *page_data; - int page_nr = offset >> PAGE_SHIFT; - int page_ofs = offset & (PAGE_SIZE-1); - int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count); - - page_data = kmap(fw_priv->pages[page_nr]); - - if (read) - memcpy(buffer, page_data + page_ofs, page_cnt); - else - memcpy(page_data + page_ofs, buffer, page_cnt); - - kunmap(fw_priv->pages[page_nr]); - buffer += page_cnt; - offset += page_cnt; - count -= page_cnt; - } -} - -static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t offset, size_t count) -{ - struct device *dev = kobj_to_dev(kobj); - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - struct fw_priv *fw_priv; - ssize_t ret_count; - - mutex_lock(&fw_lock); - fw_priv = fw_sysfs->fw_priv; - if (!fw_priv || fw_sysfs_done(fw_priv)) { - ret_count = -ENODEV; - goto out; - } - if (offset > fw_priv->size) { - ret_count = 0; - goto out; - } - if (count > fw_priv->size - offset) - count = fw_priv->size - offset; - - ret_count = count; - - if (fw_priv->data) - firmware_rw_data(fw_priv, buffer, offset, count, true); - else - firmware_rw(fw_priv, buffer, offset, count, true); - -out: - mutex_unlock(&fw_lock); - return ret_count; -} - -static int fw_realloc_pages(struct fw_sysfs *fw_sysfs, int min_size) -{ - struct fw_priv *fw_priv= fw_sysfs->fw_priv; - int pages_needed = PAGE_ALIGN(min_size) >> PAGE_SHIFT; - - /* If the array of pages is too small, grow it... */ - if (fw_priv->page_array_size < pages_needed) { - int new_array_size = max(pages_needed, - fw_priv->page_array_size * 2); - struct page **new_pages; - - new_pages = vmalloc(new_array_size * sizeof(void *)); - if (!new_pages) { - fw_load_abort(fw_sysfs); - return -ENOMEM; - } - memcpy(new_pages, fw_priv->pages, - fw_priv->page_array_size * sizeof(void *)); - memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) * - (new_array_size - fw_priv->page_array_size)); - vfree(fw_priv->pages); - fw_priv->pages = new_pages; - fw_priv->page_array_size = new_array_size; - } - - while (fw_priv->nr_pages < pages_needed) { - fw_priv->pages[fw_priv->nr_pages] = - alloc_page(GFP_KERNEL | __GFP_HIGHMEM); - - if (!fw_priv->pages[fw_priv->nr_pages]) { - fw_load_abort(fw_sysfs); - return -ENOMEM; - } - fw_priv->nr_pages++; - } - return 0; -} - -/** - * firmware_data_write - write method for firmware - * @filp: open sysfs file - * @kobj: kobject for the device - * @bin_attr: bin_attr structure - * @buffer: buffer being written - * @offset: buffer offset for write in total data store area - * @count: buffer size - * - * Data written to the 'data' attribute will be later handed to - * the driver as a firmware image. - **/ -static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t offset, size_t count) -{ - struct device *dev = kobj_to_dev(kobj); - struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); - struct fw_priv *fw_priv; - ssize_t retval; - - if (!capable(CAP_SYS_RAWIO)) - return -EPERM; - - mutex_lock(&fw_lock); - fw_priv = fw_sysfs->fw_priv; - if (!fw_priv || fw_sysfs_done(fw_priv)) { - retval = -ENODEV; - goto out; - } - - if (fw_priv->data) { - if (offset + count > fw_priv->allocated_size) { - retval = -ENOMEM; - goto out; - } - firmware_rw_data(fw_priv, buffer, offset, count, false); - retval = count; - } else { - retval = fw_realloc_pages(fw_sysfs, offset + count); - if (retval) - goto out; - - retval = count; - firmware_rw(fw_priv, buffer, offset, count, false); - } - - fw_priv->size = max_t(size_t, offset + count, fw_priv->size); -out: - mutex_unlock(&fw_lock); - return retval; -} - -static struct bin_attribute firmware_attr_data = { - .attr = { .name = "data", .mode = 0644 }, - .size = 0, - .read = firmware_data_read, - .write = firmware_data_write, -}; - -static struct attribute *fw_dev_attrs[] = { - &dev_attr_loading.attr, - NULL -}; - -static struct bin_attribute *fw_dev_bin_attrs[] = { - &firmware_attr_data, - NULL -}; - -static const struct attribute_group fw_dev_attr_group = { - .attrs = fw_dev_attrs, - .bin_attrs = fw_dev_bin_attrs, -}; - -static const struct attribute_group *fw_dev_attr_groups[] = { - &fw_dev_attr_group, - NULL -}; - -static struct fw_sysfs * -fw_create_instance(struct firmware *firmware, const char *fw_name, - struct device *device, unsigned int opt_flags) -{ - struct fw_sysfs *fw_sysfs; - struct device *f_dev; - - fw_sysfs = kzalloc(sizeof(*fw_sysfs), GFP_KERNEL); - if (!fw_sysfs) { - fw_sysfs = ERR_PTR(-ENOMEM); - goto exit; - } - - fw_sysfs->nowait = !!(opt_flags & FW_OPT_NOWAIT); - fw_sysfs->fw = firmware; - f_dev = &fw_sysfs->dev; - - device_initialize(f_dev); - dev_set_name(f_dev, "%s", fw_name); - f_dev->parent = device; - f_dev->class = &firmware_class; - f_dev->groups = fw_dev_attr_groups; -exit: - return fw_sysfs; -} - -/* load a firmware via user helper */ -static int _request_firmware_load(struct fw_sysfs *fw_sysfs, - unsigned int opt_flags, long timeout) -{ - int retval = 0; - struct device *f_dev = &fw_sysfs->dev; - struct fw_priv *fw_priv = fw_sysfs->fw_priv; - - /* fall back on userspace loading */ - if (!fw_priv->data) - fw_priv->is_paged_buf = true; - - dev_set_uevent_suppress(f_dev, true); - - retval = device_add(f_dev); - if (retval) { - dev_err(f_dev, "%s: device_register failed\n", __func__); - goto err_put_dev; - } - - mutex_lock(&fw_lock); - list_add(&fw_priv->pending_list, &pending_fw_head); - mutex_unlock(&fw_lock); - - if (opt_flags & FW_OPT_UEVENT) { - fw_priv->need_uevent = true; - dev_set_uevent_suppress(f_dev, false); - dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_name); - kobject_uevent(&fw_sysfs->dev.kobj, KOBJ_ADD); - } else { - timeout = MAX_JIFFY_OFFSET; - } - - retval = fw_sysfs_wait_timeout(fw_priv, timeout); - if (retval < 0) { - mutex_lock(&fw_lock); - fw_load_abort(fw_sysfs); - mutex_unlock(&fw_lock); - } - - if (fw_state_is_aborted(fw_priv)) { - if (retval == -ERESTARTSYS) - retval = -EINTR; - else - retval = -EAGAIN; - } else if (fw_priv->is_paged_buf && !fw_priv->data) - retval = -ENOMEM; - - device_del(f_dev); -err_put_dev: - put_device(f_dev); - return retval; -} - -static int fw_load_from_user_helper(struct firmware *firmware, - const char *name, struct device *device, - unsigned int opt_flags) -{ - struct fw_sysfs *fw_sysfs; - long timeout; - int ret; - - timeout = firmware_loading_timeout(); - if (opt_flags & FW_OPT_NOWAIT) { - timeout = usermodehelper_read_lock_wait(timeout); - if (!timeout) { - dev_dbg(device, "firmware: %s loading timed out\n", - name); - return -EBUSY; - } - } else { - ret = usermodehelper_read_trylock(); - if (WARN_ON(ret)) { - dev_err(device, "firmware: %s will not be loaded\n", - name); - return ret; - } - } - - fw_sysfs = fw_create_instance(firmware, name, device, opt_flags); - if (IS_ERR(fw_sysfs)) { - ret = PTR_ERR(fw_sysfs); - goto out_unlock; - } - - fw_sysfs->fw_priv = firmware->priv; - ret = _request_firmware_load(fw_sysfs, opt_flags, timeout); - - if (!ret) - ret = assign_fw(firmware, device, opt_flags); - -out_unlock: - usermodehelper_read_unlock(); - - return ret; -} - -static bool fw_force_sysfs_fallback(unsigned int opt_flags) -{ - if (fw_fallback_config.force_sysfs_fallback) - return true; - if (!(opt_flags & FW_OPT_USERHELPER)) - return false; - return true; -} - -static bool fw_run_sysfs_fallback(unsigned int opt_flags) -{ - if ((opt_flags & FW_OPT_NOFALLBACK)) - return false; - - return fw_force_sysfs_fallback(opt_flags); -} - -int fw_sysfs_fallback(struct firmware *fw, const char *name, - struct device *device, - unsigned int opt_flags, - int ret) -{ - if (!fw_run_sysfs_fallback(opt_flags)) - return ret; - - dev_warn(device, "Falling back to user helper\n"); - return fw_load_from_user_helper(fw, name, device, opt_flags); -} diff --git a/drivers/base/firmware_fallback.h b/drivers/base/firmware_fallback.h deleted file mode 100644 index 550498c7fa4c..000000000000 --- a/drivers/base/firmware_fallback.h +++ /dev/null @@ -1,61 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __FIRMWARE_FALLBACK_H -#define __FIRMWARE_FALLBACK_H - -#include -#include - -/** - * struct firmware_fallback_config - firmware fallback configuratioon settings - * - * Helps describe and fine tune the fallback mechanism. - * - * @force_sysfs_fallback: force the sysfs fallback mechanism to be used - * as if one had enabled CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y. - * @old_timeout: for internal use - * @loading_timeout: the timeout to wait for the fallback mechanism before - * giving up, in seconds. - */ -struct firmware_fallback_config { - const bool force_sysfs_fallback; - int old_timeout; - int loading_timeout; -}; - -#ifdef CONFIG_FW_LOADER_USER_HELPER -int fw_sysfs_fallback(struct firmware *fw, const char *name, - struct device *device, - unsigned int opt_flags, - int ret); -void kill_pending_fw_fallback_reqs(bool only_kill_custom); - -void fw_fallback_set_cache_timeout(void); -void fw_fallback_set_default_timeout(void); - -int register_sysfs_loader(void); -void unregister_sysfs_loader(void); -#else /* CONFIG_FW_LOADER_USER_HELPER */ -static inline int fw_sysfs_fallback(struct firmware *fw, const char *name, - struct device *device, - unsigned int opt_flags, - int ret) -{ - /* Keep carrying over the same error */ - return ret; -} - -static inline void kill_pending_fw_fallback_reqs(bool only_kill_custom) { } -static inline void fw_fallback_set_cache_timeout(void) { } -static inline void fw_fallback_set_default_timeout(void) { } - -static inline int register_sysfs_loader(void) -{ - return 0; -} - -static inline void unregister_sysfs_loader(void) -{ -} -#endif /* CONFIG_FW_LOADER_USER_HELPER */ - -#endif /* __FIRMWARE_FALLBACK_H */ diff --git a/drivers/base/firmware_fallback_table.c b/drivers/base/firmware_fallback_table.c deleted file mode 100644 index 53cc4e492520..000000000000 --- a/drivers/base/firmware_fallback_table.c +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "firmware_fallback.h" -#include "firmware_loader.h" - -/* - * firmware fallback configuration table - */ - -/* Module or buit-in */ -#ifdef CONFIG_FW_LOADER_USER_HELPER - -struct firmware_fallback_config fw_fallback_config = { - .force_sysfs_fallback = IS_ENABLED(CONFIG_FW_LOADER_USER_HELPER_FALLBACK), - .loading_timeout = 60, - .old_timeout = 60, -}; -EXPORT_SYMBOL_GPL(fw_fallback_config); - -#endif diff --git a/drivers/base/firmware_loader.c b/drivers/base/firmware_loader.c deleted file mode 100644 index 21dd31ef08ae..000000000000 --- a/drivers/base/firmware_loader.c +++ /dev/null @@ -1,1189 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * firmware_class.c - Multi purpose firmware loading support - * - * Copyright (c) 2003 Manuel Estrada Sainz - * - * Please see Documentation/firmware_class/ for more information. - * - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "base.h" -#include "firmware_loader.h" -#include "firmware_fallback.h" - -MODULE_AUTHOR("Manuel Estrada Sainz"); -MODULE_DESCRIPTION("Multi purpose firmware loading support"); -MODULE_LICENSE("GPL"); - -struct firmware_cache { - /* firmware_buf instance will be added into the below list */ - spinlock_t lock; - struct list_head head; - int state; - -#ifdef CONFIG_PM_SLEEP - /* - * Names of firmware images which have been cached successfully - * will be added into the below list so that device uncache - * helper can trace which firmware images have been cached - * before. - */ - spinlock_t name_lock; - struct list_head fw_names; - - struct delayed_work work; - - struct notifier_block pm_notify; -#endif -}; - -struct fw_cache_entry { - struct list_head list; - const char *name; -}; - -struct fw_name_devm { - unsigned long magic; - const char *name; -}; - -static inline struct fw_priv *to_fw_priv(struct kref *ref) -{ - return container_of(ref, struct fw_priv, ref); -} - -#define FW_LOADER_NO_CACHE 0 -#define FW_LOADER_START_CACHE 1 - -/* fw_lock could be moved to 'struct fw_sysfs' but since it is just - * guarding for corner cases a global lock should be OK */ -DEFINE_MUTEX(fw_lock); - -static struct firmware_cache fw_cache; - -/* Builtin firmware support */ - -#ifdef CONFIG_FW_LOADER - -extern struct builtin_fw __start_builtin_fw[]; -extern struct builtin_fw __end_builtin_fw[]; - -static void fw_copy_to_prealloc_buf(struct firmware *fw, - void *buf, size_t size) -{ - if (!buf || size < fw->size) - return; - memcpy(buf, fw->data, fw->size); -} - -static bool fw_get_builtin_firmware(struct firmware *fw, const char *name, - void *buf, size_t size) -{ - struct builtin_fw *b_fw; - - for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++) { - if (strcmp(name, b_fw->name) == 0) { - fw->size = b_fw->size; - fw->data = b_fw->data; - fw_copy_to_prealloc_buf(fw, buf, size); - - return true; - } - } - - return false; -} - -static bool fw_is_builtin_firmware(const struct firmware *fw) -{ - struct builtin_fw *b_fw; - - for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++) - if (fw->data == b_fw->data) - return true; - - return false; -} - -#else /* Module case - no builtin firmware support */ - -static inline bool fw_get_builtin_firmware(struct firmware *fw, - const char *name, void *buf, - size_t size) -{ - return false; -} - -static inline bool fw_is_builtin_firmware(const struct firmware *fw) -{ - return false; -} -#endif - -static void fw_state_init(struct fw_priv *fw_priv) -{ - struct fw_state *fw_st = &fw_priv->fw_st; - - init_completion(&fw_st->completion); - fw_st->status = FW_STATUS_UNKNOWN; -} - -static inline int fw_state_wait(struct fw_priv *fw_priv) -{ - return __fw_state_wait_common(fw_priv, MAX_SCHEDULE_TIMEOUT); -} - -static int fw_cache_piggyback_on_request(const char *name); - -static struct fw_priv *__allocate_fw_priv(const char *fw_name, - struct firmware_cache *fwc, - void *dbuf, size_t size) -{ - struct fw_priv *fw_priv; - - fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC); - if (!fw_priv) - return NULL; - - fw_priv->fw_name = kstrdup_const(fw_name, GFP_ATOMIC); - if (!fw_priv->fw_name) { - kfree(fw_priv); - return NULL; - } - - kref_init(&fw_priv->ref); - fw_priv->fwc = fwc; - fw_priv->data = dbuf; - fw_priv->allocated_size = size; - fw_state_init(fw_priv); -#ifdef CONFIG_FW_LOADER_USER_HELPER - INIT_LIST_HEAD(&fw_priv->pending_list); -#endif - - pr_debug("%s: fw-%s fw_priv=%p\n", __func__, fw_name, fw_priv); - - return fw_priv; -} - -static struct fw_priv *__lookup_fw_priv(const char *fw_name) -{ - struct fw_priv *tmp; - struct firmware_cache *fwc = &fw_cache; - - list_for_each_entry(tmp, &fwc->head, list) - if (!strcmp(tmp->fw_name, fw_name)) - return tmp; - return NULL; -} - -/* 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) -{ - struct fw_priv *tmp; - - spin_lock(&fwc->lock); - tmp = __lookup_fw_priv(fw_name); - if (tmp) { - kref_get(&tmp->ref); - spin_unlock(&fwc->lock); - *fw_priv = tmp; - pr_debug("batched request - sharing the same struct fw_priv and lookup for multiple requests\n"); - return 1; - } - tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size); - if (tmp) - list_add(&tmp->list, &fwc->head); - spin_unlock(&fwc->lock); - - *fw_priv = tmp; - - return tmp ? 0 : -ENOMEM; -} - -static void __free_fw_priv(struct kref *ref) - __releases(&fwc->lock) -{ - struct fw_priv *fw_priv = to_fw_priv(ref); - struct firmware_cache *fwc = fw_priv->fwc; - - 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); - - list_del(&fw_priv->list); - spin_unlock(&fwc->lock); - -#ifdef CONFIG_FW_LOADER_USER_HELPER - if (fw_priv->is_paged_buf) { - int i; - vunmap(fw_priv->data); - for (i = 0; i < fw_priv->nr_pages; i++) - __free_page(fw_priv->pages[i]); - vfree(fw_priv->pages); - } else -#endif - if (!fw_priv->allocated_size) - vfree(fw_priv->data); - kfree_const(fw_priv->fw_name); - kfree(fw_priv); -} - -static void free_fw_priv(struct fw_priv *fw_priv) -{ - struct firmware_cache *fwc = fw_priv->fwc; - spin_lock(&fwc->lock); - if (!kref_put(&fw_priv->ref, __free_fw_priv)) - spin_unlock(&fwc->lock); -} - -/* direct firmware loading support */ -static char fw_path_para[256]; -static const char * const fw_path[] = { - fw_path_para, - "/lib/firmware/updates/" UTS_RELEASE, - "/lib/firmware/updates", - "/lib/firmware/" UTS_RELEASE, - "/lib/firmware" -}; - -/* - * Typical usage is that passing 'firmware_class.path=$CUSTOMIZED_PATH' - * from kernel command line because firmware_class is generally built in - * kernel instead of module. - */ -module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644); -MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path"); - -static int -fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) -{ - loff_t size; - int i, len; - int rc = -ENOENT; - char *path; - enum kernel_read_file_id id = READING_FIRMWARE; - size_t msize = INT_MAX; - - /* Already populated data member means we're loading into a buffer */ - if (fw_priv->data) { - id = READING_FIRMWARE_PREALLOC_BUFFER; - msize = fw_priv->allocated_size; - } - - path = __getname(); - if (!path) - return -ENOMEM; - - for (i = 0; i < ARRAY_SIZE(fw_path); i++) { - /* skip the unset customized path */ - if (!fw_path[i][0]) - continue; - - len = snprintf(path, PATH_MAX, "%s/%s", - fw_path[i], fw_priv->fw_name); - if (len >= PATH_MAX) { - rc = -ENAMETOOLONG; - break; - } - - fw_priv->size = 0; - rc = kernel_read_file_from_path(path, &fw_priv->data, &size, - msize, id); - if (rc) { - if (rc == -ENOENT) - dev_dbg(device, "loading %s failed with error %d\n", - path, rc); - else - dev_warn(device, "loading %s failed with error %d\n", - path, rc); - continue; - } - dev_dbg(device, "direct-loading %s\n", fw_priv->fw_name); - fw_priv->size = size; - fw_state_done(fw_priv); - break; - } - __putname(path); - - return rc; -} - -/* firmware holds the ownership of pages */ -static void firmware_free_data(const struct firmware *fw) -{ - /* Loaded directly? */ - if (!fw->priv) { - vfree(fw->data); - return; - } - free_fw_priv(fw->priv); -} - -/* store the pages buffer info firmware from buf */ -static void fw_set_page_data(struct fw_priv *fw_priv, struct firmware *fw) -{ - fw->priv = fw_priv; -#ifdef CONFIG_FW_LOADER_USER_HELPER - fw->pages = fw_priv->pages; -#endif - fw->size = fw_priv->size; - fw->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); -} - -#ifdef CONFIG_PM_SLEEP -static void fw_name_devm_release(struct device *dev, void *res) -{ - struct fw_name_devm *fwn = res; - - if (fwn->magic == (unsigned long)&fw_cache) - pr_debug("%s: fw_name-%s devm-%p released\n", - __func__, fwn->name, res); - kfree_const(fwn->name); -} - -static int fw_devm_match(struct device *dev, void *res, - void *match_data) -{ - struct fw_name_devm *fwn = res; - - return (fwn->magic == (unsigned long)&fw_cache) && - !strcmp(fwn->name, match_data); -} - -static struct fw_name_devm *fw_find_devm_name(struct device *dev, - const char *name) -{ - struct fw_name_devm *fwn; - - fwn = devres_find(dev, fw_name_devm_release, - fw_devm_match, (void *)name); - return fwn; -} - -/* add firmware name into devres list */ -static int fw_add_devm_name(struct device *dev, const char *name) -{ - struct fw_name_devm *fwn; - - fwn = fw_find_devm_name(dev, name); - if (fwn) - return 1; - - fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm), - GFP_KERNEL); - if (!fwn) - return -ENOMEM; - fwn->name = kstrdup_const(name, GFP_KERNEL); - if (!fwn->name) { - devres_free(fwn); - return -ENOMEM; - } - - fwn->magic = (unsigned long)&fw_cache; - devres_add(dev, fwn); - - return 0; -} -#else -static int fw_add_devm_name(struct device *dev, const char *name) -{ - return 0; -} -#endif - -int assign_fw(struct firmware *fw, struct device *device, - unsigned int opt_flags) -{ - struct fw_priv *fw_priv = fw->priv; - - mutex_lock(&fw_lock); - if (!fw_priv->size || fw_state_is_aborted(fw_priv)) { - mutex_unlock(&fw_lock); - return -ENOENT; - } - - /* - * add firmware name into devres list so that we can auto cache - * and uncache firmware for device. - * - * device may has been deleted already, but the problem - * should be fixed in devres or driver core. - */ - /* don't cache firmware handled without uevent */ - if (device && (opt_flags & FW_OPT_UEVENT) && - !(opt_flags & FW_OPT_NOCACHE)) - fw_add_devm_name(device, fw_priv->fw_name); - - /* - * After caching firmware image is started, let it piggyback - * on request firmware. - */ - if (!(opt_flags & FW_OPT_NOCACHE) && - fw_priv->fwc->state == FW_LOADER_START_CACHE) { - if (fw_cache_piggyback_on_request(fw_priv->fw_name)) - kref_get(&fw_priv->ref); - } - - /* pass the pages buffer to driver at the last minute */ - fw_set_page_data(fw_priv, fw); - mutex_unlock(&fw_lock); - return 0; -} - -/* prepare firmware and firmware_buf structs; - * return 0 if a firmware is already assigned, 1 if need to load one, - * or a negative error code - */ -static int -_request_firmware_prepare(struct firmware **firmware_p, const char *name, - struct device *device, void *dbuf, size_t size) -{ - struct firmware *firmware; - struct fw_priv *fw_priv; - int ret; - - *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); - if (!firmware) { - dev_err(device, "%s: kmalloc(struct firmware) failed\n", - __func__); - return -ENOMEM; - } - - if (fw_get_builtin_firmware(firmware, name, dbuf, size)) { - dev_dbg(device, "using built-in %s\n", name); - return 0; /* assigned */ - } - - ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, dbuf, size); - - /* - * bind with 'priv' now to avoid warning in failure path - * of requesting firmware. - */ - firmware->priv = fw_priv; - - if (ret > 0) { - ret = fw_state_wait(fw_priv); - if (!ret) { - fw_set_page_data(fw_priv, firmware); - return 0; /* assigned */ - } - } - - if (ret < 0) - return ret; - return 1; /* need to load */ -} - -/* - * Batched requests need only one wake, we need to do this step last due to the - * fallback mechanism. The buf is protected with kref_get(), and it won't be - * released until the last user calls release_firmware(). - * - * Failed batched requests are possible as well, in such cases we just share - * the struct fw_priv and won't release it until all requests are woken - * and have gone through this same path. - */ -static void fw_abort_batch_reqs(struct firmware *fw) -{ - struct fw_priv *fw_priv; - - /* Loaded directly? */ - if (!fw || !fw->priv) - return; - - fw_priv = fw->priv; - if (!fw_state_is_aborted(fw_priv)) - fw_state_aborted(fw_priv); -} - -/* called from request_firmware() and request_firmware_work_func() */ -static int -_request_firmware(const struct firmware **firmware_p, const char *name, - struct device *device, void *buf, size_t size, - unsigned int opt_flags) -{ - struct firmware *fw = NULL; - int ret; - - if (!firmware_p) - return -EINVAL; - - if (!name || name[0] == '\0') { - ret = -EINVAL; - goto out; - } - - ret = _request_firmware_prepare(&fw, name, device, buf, size); - if (ret <= 0) /* error or already assigned */ - goto out; - - ret = fw_get_filesystem_firmware(device, fw->priv); - if (ret) { - if (!(opt_flags & FW_OPT_NO_WARN)) - dev_warn(device, - "Direct firmware load for %s failed with error %d\n", - name, ret); - ret = fw_sysfs_fallback(fw, name, device, opt_flags, ret); - } else - ret = assign_fw(fw, device, opt_flags); - - out: - if (ret < 0) { - fw_abort_batch_reqs(fw); - release_firmware(fw); - fw = NULL; - } - - *firmware_p = fw; - return ret; -} - -/** - * request_firmware: - send firmware request and wait for it - * @firmware_p: pointer to firmware image - * @name: name of firmware file - * @device: device for which firmware is being loaded - * - * @firmware_p will be used to return a firmware image by the name - * of @name for device @device. - * - * Should be called from user context where sleeping is allowed. - * - * @name will be used as $FIRMWARE in the uevent environment and - * should be distinctive enough not to be confused with any other - * firmware image for this or any other device. - * - * Caller must hold the reference count of @device. - * - * The function can be called safely inside device's suspend and - * resume callback. - **/ -int -request_firmware(const struct firmware **firmware_p, const char *name, - struct device *device) -{ - int ret; - - /* Need to pin this module until return */ - __module_get(THIS_MODULE); - ret = _request_firmware(firmware_p, name, device, NULL, 0, - FW_OPT_UEVENT); - module_put(THIS_MODULE); - return ret; -} -EXPORT_SYMBOL(request_firmware); - -/** - * request_firmware_direct: - load firmware directly without usermode helper - * @firmware_p: pointer to firmware image - * @name: name of firmware file - * @device: device for which firmware is being loaded - * - * This function works pretty much like request_firmware(), but this doesn't - * fall back to usermode helper even if the firmware couldn't be loaded - * directly from fs. Hence it's useful for loading optional firmwares, which - * aren't always present, without extra long timeouts of udev. - **/ -int request_firmware_direct(const struct firmware **firmware_p, - const char *name, struct device *device) -{ - int ret; - - __module_get(THIS_MODULE); - ret = _request_firmware(firmware_p, name, device, NULL, 0, - FW_OPT_UEVENT | FW_OPT_NO_WARN | - FW_OPT_NOFALLBACK); - module_put(THIS_MODULE); - return ret; -} -EXPORT_SYMBOL_GPL(request_firmware_direct); - -/** - * request_firmware_into_buf - load firmware into a previously allocated buffer - * @firmware_p: pointer to firmware image - * @name: name of firmware file - * @device: device for which firmware is being loaded and DMA region allocated - * @buf: address of buffer to load firmware into - * @size: size of buffer - * - * This function works pretty much like request_firmware(), but it doesn't - * allocate a buffer to hold the firmware data. Instead, the firmware - * is loaded directly into the buffer pointed to by @buf and the @firmware_p - * data member is pointed at @buf. - * - * This function doesn't cache firmware either. - */ -int -request_firmware_into_buf(const struct firmware **firmware_p, const char *name, - struct device *device, void *buf, size_t size) -{ - int ret; - - __module_get(THIS_MODULE); - ret = _request_firmware(firmware_p, name, device, buf, size, - FW_OPT_UEVENT | FW_OPT_NOCACHE); - module_put(THIS_MODULE); - return ret; -} -EXPORT_SYMBOL(request_firmware_into_buf); - -/** - * release_firmware: - release the resource associated with a firmware image - * @fw: firmware resource to release - **/ -void release_firmware(const struct firmware *fw) -{ - if (fw) { - if (!fw_is_builtin_firmware(fw)) - firmware_free_data(fw); - kfree(fw); - } -} -EXPORT_SYMBOL(release_firmware); - -/* Async support */ -struct firmware_work { - struct work_struct work; - struct module *module; - const char *name; - struct device *device; - void *context; - void (*cont)(const struct firmware *fw, void *context); - unsigned int opt_flags; -}; - -static void request_firmware_work_func(struct work_struct *work) -{ - struct firmware_work *fw_work; - const struct firmware *fw; - - fw_work = container_of(work, struct firmware_work, work); - - _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0, - fw_work->opt_flags); - fw_work->cont(fw, fw_work->context); - put_device(fw_work->device); /* taken in request_firmware_nowait() */ - - module_put(fw_work->module); - kfree_const(fw_work->name); - kfree(fw_work); -} - -/** - * request_firmware_nowait - asynchronous version of request_firmware - * @module: module requesting the firmware - * @uevent: sends uevent to copy the firmware image if this flag - * is non-zero else the firmware copy must be done manually. - * @name: name of firmware file - * @device: device for which firmware is being loaded - * @gfp: allocation flags - * @context: will be passed over to @cont, and - * @fw may be %NULL if firmware request fails. - * @cont: function will be called asynchronously when the firmware - * request is over. - * - * Caller must hold the reference count of @device. - * - * Asynchronous variant of request_firmware() for user contexts: - * - sleep for as small periods as possible since it may - * increase kernel boot time of built-in device drivers - * requesting firmware in their ->probe() methods, if - * @gfp is GFP_KERNEL. - * - * - can't sleep at all if @gfp is GFP_ATOMIC. - **/ -int -request_firmware_nowait( - struct module *module, bool uevent, - const char *name, struct device *device, gfp_t gfp, void *context, - void (*cont)(const struct firmware *fw, void *context)) -{ - struct firmware_work *fw_work; - - fw_work = kzalloc(sizeof(struct firmware_work), gfp); - if (!fw_work) - return -ENOMEM; - - fw_work->module = module; - fw_work->name = kstrdup_const(name, gfp); - if (!fw_work->name) { - kfree(fw_work); - return -ENOMEM; - } - fw_work->device = device; - fw_work->context = context; - fw_work->cont = cont; - fw_work->opt_flags = FW_OPT_NOWAIT | - (uevent ? FW_OPT_UEVENT : FW_OPT_USERHELPER); - - if (!try_module_get(module)) { - kfree_const(fw_work->name); - kfree(fw_work); - return -EFAULT; - } - - get_device(fw_work->device); - INIT_WORK(&fw_work->work, request_firmware_work_func); - schedule_work(&fw_work->work); - return 0; -} -EXPORT_SYMBOL(request_firmware_nowait); - -#ifdef CONFIG_PM_SLEEP -static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain); - -/** - * cache_firmware - cache one firmware image in kernel memory space - * @fw_name: the firmware image name - * - * Cache firmware in kernel memory so that drivers can use it when - * system isn't ready for them to request firmware image from userspace. - * Once it returns successfully, driver can use request_firmware or its - * nowait version to get the cached firmware without any interacting - * with userspace - * - * Return 0 if the firmware image has been cached successfully - * Return !0 otherwise - * - */ -static int cache_firmware(const char *fw_name) -{ - int ret; - const struct firmware *fw; - - pr_debug("%s: %s\n", __func__, fw_name); - - ret = request_firmware(&fw, fw_name, NULL); - if (!ret) - kfree(fw); - - pr_debug("%s: %s ret=%d\n", __func__, fw_name, ret); - - return ret; -} - -static struct fw_priv *lookup_fw_priv(const char *fw_name) -{ - struct fw_priv *tmp; - struct firmware_cache *fwc = &fw_cache; - - spin_lock(&fwc->lock); - tmp = __lookup_fw_priv(fw_name); - spin_unlock(&fwc->lock); - - return tmp; -} - -/** - * uncache_firmware - remove one cached firmware image - * @fw_name: the firmware image name - * - * Uncache one firmware image which has been cached successfully - * before. - * - * Return 0 if the firmware cache has been removed successfully - * Return !0 otherwise - * - */ -static int uncache_firmware(const char *fw_name) -{ - struct fw_priv *fw_priv; - struct firmware fw; - - pr_debug("%s: %s\n", __func__, fw_name); - - if (fw_get_builtin_firmware(&fw, fw_name, NULL, 0)) - return 0; - - fw_priv = lookup_fw_priv(fw_name); - if (fw_priv) { - free_fw_priv(fw_priv); - return 0; - } - - return -EINVAL; -} - -static struct fw_cache_entry *alloc_fw_cache_entry(const char *name) -{ - struct fw_cache_entry *fce; - - fce = kzalloc(sizeof(*fce), GFP_ATOMIC); - if (!fce) - goto exit; - - fce->name = kstrdup_const(name, GFP_ATOMIC); - if (!fce->name) { - kfree(fce); - fce = NULL; - goto exit; - } -exit: - return fce; -} - -static int __fw_entry_found(const char *name) -{ - struct firmware_cache *fwc = &fw_cache; - struct fw_cache_entry *fce; - - list_for_each_entry(fce, &fwc->fw_names, list) { - if (!strcmp(fce->name, name)) - return 1; - } - return 0; -} - -static int fw_cache_piggyback_on_request(const char *name) -{ - struct firmware_cache *fwc = &fw_cache; - struct fw_cache_entry *fce; - int ret = 0; - - spin_lock(&fwc->name_lock); - if (__fw_entry_found(name)) - goto found; - - fce = alloc_fw_cache_entry(name); - if (fce) { - ret = 1; - list_add(&fce->list, &fwc->fw_names); - pr_debug("%s: fw: %s\n", __func__, name); - } -found: - spin_unlock(&fwc->name_lock); - return ret; -} - -static void free_fw_cache_entry(struct fw_cache_entry *fce) -{ - kfree_const(fce->name); - kfree(fce); -} - -static void __async_dev_cache_fw_image(void *fw_entry, - async_cookie_t cookie) -{ - struct fw_cache_entry *fce = fw_entry; - struct firmware_cache *fwc = &fw_cache; - int ret; - - ret = cache_firmware(fce->name); - if (ret) { - spin_lock(&fwc->name_lock); - list_del(&fce->list); - spin_unlock(&fwc->name_lock); - - free_fw_cache_entry(fce); - } -} - -/* called with dev->devres_lock held */ -static void dev_create_fw_entry(struct device *dev, void *res, - void *data) -{ - struct fw_name_devm *fwn = res; - const char *fw_name = fwn->name; - struct list_head *head = data; - struct fw_cache_entry *fce; - - fce = alloc_fw_cache_entry(fw_name); - if (fce) - list_add(&fce->list, head); -} - -static int devm_name_match(struct device *dev, void *res, - void *match_data) -{ - struct fw_name_devm *fwn = res; - return (fwn->magic == (unsigned long)match_data); -} - -static void dev_cache_fw_image(struct device *dev, void *data) -{ - LIST_HEAD(todo); - struct fw_cache_entry *fce; - struct fw_cache_entry *fce_next; - struct firmware_cache *fwc = &fw_cache; - - devres_for_each_res(dev, fw_name_devm_release, - devm_name_match, &fw_cache, - dev_create_fw_entry, &todo); - - list_for_each_entry_safe(fce, fce_next, &todo, list) { - list_del(&fce->list); - - spin_lock(&fwc->name_lock); - /* only one cache entry for one firmware */ - if (!__fw_entry_found(fce->name)) { - list_add(&fce->list, &fwc->fw_names); - } else { - free_fw_cache_entry(fce); - fce = NULL; - } - spin_unlock(&fwc->name_lock); - - if (fce) - async_schedule_domain(__async_dev_cache_fw_image, - (void *)fce, - &fw_cache_domain); - } -} - -static void __device_uncache_fw_images(void) -{ - struct firmware_cache *fwc = &fw_cache; - struct fw_cache_entry *fce; - - spin_lock(&fwc->name_lock); - while (!list_empty(&fwc->fw_names)) { - fce = list_entry(fwc->fw_names.next, - struct fw_cache_entry, list); - list_del(&fce->list); - spin_unlock(&fwc->name_lock); - - uncache_firmware(fce->name); - free_fw_cache_entry(fce); - - spin_lock(&fwc->name_lock); - } - spin_unlock(&fwc->name_lock); -} - -/** - * device_cache_fw_images - cache devices' firmware - * - * If one device called request_firmware or its nowait version - * successfully before, the firmware names are recored into the - * device's devres link list, so device_cache_fw_images can call - * cache_firmware() to cache these firmwares for the device, - * then the device driver can load its firmwares easily at - * time when system is not ready to complete loading firmware. - */ -static void device_cache_fw_images(void) -{ - struct firmware_cache *fwc = &fw_cache; - DEFINE_WAIT(wait); - - pr_debug("%s\n", __func__); - - /* cancel uncache work */ - cancel_delayed_work_sync(&fwc->work); - - fw_fallback_set_cache_timeout(); - - mutex_lock(&fw_lock); - fwc->state = FW_LOADER_START_CACHE; - dpm_for_each_dev(NULL, dev_cache_fw_image); - mutex_unlock(&fw_lock); - - /* wait for completion of caching firmware for all devices */ - async_synchronize_full_domain(&fw_cache_domain); - - fw_fallback_set_default_timeout(); -} - -/** - * device_uncache_fw_images - uncache devices' firmware - * - * uncache all firmwares which have been cached successfully - * by device_uncache_fw_images earlier - */ -static void device_uncache_fw_images(void) -{ - pr_debug("%s\n", __func__); - __device_uncache_fw_images(); -} - -static void device_uncache_fw_images_work(struct work_struct *work) -{ - device_uncache_fw_images(); -} - -/** - * device_uncache_fw_images_delay - uncache devices firmwares - * @delay: number of milliseconds to delay uncache device firmwares - * - * uncache all devices's firmwares which has been cached successfully - * by device_cache_fw_images after @delay milliseconds. - */ -static void device_uncache_fw_images_delay(unsigned long delay) -{ - queue_delayed_work(system_power_efficient_wq, &fw_cache.work, - msecs_to_jiffies(delay)); -} - -static int fw_pm_notify(struct notifier_block *notify_block, - unsigned long mode, void *unused) -{ - switch (mode) { - case PM_HIBERNATION_PREPARE: - case PM_SUSPEND_PREPARE: - case PM_RESTORE_PREPARE: - /* - * kill pending fallback requests with a custom fallback - * to avoid stalling suspend. - */ - kill_pending_fw_fallback_reqs(true); - device_cache_fw_images(); - break; - - case PM_POST_SUSPEND: - case PM_POST_HIBERNATION: - case PM_POST_RESTORE: - /* - * In case that system sleep failed and syscore_suspend is - * not called. - */ - mutex_lock(&fw_lock); - fw_cache.state = FW_LOADER_NO_CACHE; - mutex_unlock(&fw_lock); - - device_uncache_fw_images_delay(10 * MSEC_PER_SEC); - break; - } - - return 0; -} - -/* stop caching firmware once syscore_suspend is reached */ -static int fw_suspend(void) -{ - fw_cache.state = FW_LOADER_NO_CACHE; - return 0; -} - -static struct syscore_ops fw_syscore_ops = { - .suspend = fw_suspend, -}; - -static int __init register_fw_pm_ops(void) -{ - int ret; - - spin_lock_init(&fw_cache.name_lock); - INIT_LIST_HEAD(&fw_cache.fw_names); - - INIT_DELAYED_WORK(&fw_cache.work, - device_uncache_fw_images_work); - - fw_cache.pm_notify.notifier_call = fw_pm_notify; - ret = register_pm_notifier(&fw_cache.pm_notify); - if (ret) - return ret; - - register_syscore_ops(&fw_syscore_ops); - - return ret; -} - -static inline void unregister_fw_pm_ops(void) -{ - unregister_syscore_ops(&fw_syscore_ops); - unregister_pm_notifier(&fw_cache.pm_notify); -} -#else -static int fw_cache_piggyback_on_request(const char *name) -{ - return 0; -} -static inline int register_fw_pm_ops(void) -{ - return 0; -} -static inline void unregister_fw_pm_ops(void) -{ -} -#endif - -static void __init fw_cache_init(void) -{ - spin_lock_init(&fw_cache.lock); - INIT_LIST_HEAD(&fw_cache.head); - fw_cache.state = FW_LOADER_NO_CACHE; -} - -static int fw_shutdown_notify(struct notifier_block *unused1, - unsigned long unused2, void *unused3) -{ - /* - * Kill all pending fallback requests to avoid both stalling shutdown, - * and avoid a deadlock with the usermode_lock. - */ - kill_pending_fw_fallback_reqs(false); - - return NOTIFY_DONE; -} - -static struct notifier_block fw_shutdown_nb = { - .notifier_call = fw_shutdown_notify, -}; - -static int __init firmware_class_init(void) -{ - int ret; - - /* No need to unfold these on exit */ - fw_cache_init(); - - ret = register_fw_pm_ops(); - if (ret) - return ret; - - ret = register_reboot_notifier(&fw_shutdown_nb); - if (ret) - goto out; - - return register_sysfs_loader(); - -out: - unregister_fw_pm_ops(); - return ret; -} - -static void __exit firmware_class_exit(void) -{ - unregister_fw_pm_ops(); - unregister_reboot_notifier(&fw_shutdown_nb); - unregister_sysfs_loader(); -} - -fs_initcall(firmware_class_init); -module_exit(firmware_class_exit); diff --git a/drivers/base/firmware_loader.h b/drivers/base/firmware_loader.h deleted file mode 100644 index 64acbb1a392c..000000000000 --- a/drivers/base/firmware_loader.h +++ /dev/null @@ -1,115 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __FIRMWARE_LOADER_H -#define __FIRMWARE_LOADER_H - -#include -#include -#include -#include -#include - -#include - -/* firmware behavior options */ -#define FW_OPT_UEVENT (1U << 0) -#define FW_OPT_NOWAIT (1U << 1) -#define FW_OPT_USERHELPER (1U << 2) -#define FW_OPT_NO_WARN (1U << 3) -#define FW_OPT_NOCACHE (1U << 4) -#define FW_OPT_NOFALLBACK (1U << 5) - -enum fw_status { - FW_STATUS_UNKNOWN, - FW_STATUS_LOADING, - FW_STATUS_DONE, - FW_STATUS_ABORTED, -}; - -/* - * Concurrent request_firmware() for the same firmware need to be - * serialized. struct fw_state is simple state machine which hold the - * state of the firmware loading. - */ -struct fw_state { - struct completion completion; - enum fw_status status; -}; - -struct fw_priv { - struct kref ref; - struct list_head list; - struct firmware_cache *fwc; - struct fw_state fw_st; - void *data; - size_t size; - size_t allocated_size; -#ifdef CONFIG_FW_LOADER_USER_HELPER - bool is_paged_buf; - bool need_uevent; - struct page **pages; - int nr_pages; - int page_array_size; - struct list_head pending_list; -#endif - const char *fw_name; -}; - -extern struct mutex fw_lock; - -static inline bool __fw_state_check(struct fw_priv *fw_priv, - enum fw_status status) -{ - struct fw_state *fw_st = &fw_priv->fw_st; - - return fw_st->status == status; -} - -static inline int __fw_state_wait_common(struct fw_priv *fw_priv, long timeout) -{ - struct fw_state *fw_st = &fw_priv->fw_st; - long ret; - - ret = wait_for_completion_killable_timeout(&fw_st->completion, timeout); - if (ret != 0 && fw_st->status == FW_STATUS_ABORTED) - return -ENOENT; - if (!ret) - return -ETIMEDOUT; - - return ret < 0 ? ret : 0; -} - -static inline void __fw_state_set(struct fw_priv *fw_priv, - enum fw_status status) -{ - struct fw_state *fw_st = &fw_priv->fw_st; - - WRITE_ONCE(fw_st->status, status); - - if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED) - complete_all(&fw_st->completion); -} - -static inline void fw_state_aborted(struct fw_priv *fw_priv) -{ - __fw_state_set(fw_priv, FW_STATUS_ABORTED); -} - -static inline bool fw_state_is_aborted(struct fw_priv *fw_priv) -{ - return __fw_state_check(fw_priv, FW_STATUS_ABORTED); -} - -static inline void fw_state_start(struct fw_priv *fw_priv) -{ - __fw_state_set(fw_priv, FW_STATUS_LOADING); -} - -static inline void fw_state_done(struct fw_priv *fw_priv) -{ - __fw_state_set(fw_priv, FW_STATUS_DONE); -} - -int assign_fw(struct firmware *fw, struct device *device, - unsigned int opt_flags); - -#endif /* __FIRMWARE_LOADER_H */ diff --git a/drivers/base/firmware_loader/Makefile b/drivers/base/firmware_loader/Makefile new file mode 100644 index 000000000000..a97eeb0be1d8 --- /dev/null +++ b/drivers/base/firmware_loader/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for the Linux firmware loader + +obj-y := fallback_table.o +obj-$(CONFIG_FW_LOADER) += firmware_class.o +firmware_class-objs := main.o +firmware_class-$(CONFIG_FW_LOADER_USER_HELPER) += fallback.o diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c new file mode 100644 index 000000000000..9b65837256d6 --- /dev/null +++ b/drivers/base/firmware_loader/fallback.c @@ -0,0 +1,661 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include + +#include "fallback.h" +#include "firmware.h" + +/* + * firmware fallback mechanism + */ + +extern struct firmware_fallback_config fw_fallback_config; + +/* These getters are vetted to use int properly */ +static inline int __firmware_loading_timeout(void) +{ + return fw_fallback_config.loading_timeout; +} + +/* These setters are vetted to use int properly */ +static void __fw_fallback_set_timeout(int timeout) +{ + fw_fallback_config.loading_timeout = timeout; +} + +/* + * use small loading timeout for caching devices' firmware because all these + * firmware images have been loaded successfully at lease once, also system is + * ready for completing firmware loading now. The maximum size of firmware in + * current distributions is about 2M bytes, so 10 secs should be enough. + */ +void fw_fallback_set_cache_timeout(void) +{ + fw_fallback_config.old_timeout = __firmware_loading_timeout(); + __fw_fallback_set_timeout(10); +} + +/* Restores the timeout to the value last configured during normal operation */ +void fw_fallback_set_default_timeout(void) +{ + __fw_fallback_set_timeout(fw_fallback_config.old_timeout); +} + +static long firmware_loading_timeout(void) +{ + return __firmware_loading_timeout() > 0 ? + __firmware_loading_timeout() * HZ : MAX_JIFFY_OFFSET; +} + +static inline bool fw_sysfs_done(struct fw_priv *fw_priv) +{ + return __fw_state_check(fw_priv, FW_STATUS_DONE); +} + +static inline bool fw_sysfs_loading(struct fw_priv *fw_priv) +{ + return __fw_state_check(fw_priv, FW_STATUS_LOADING); +} + +static inline int fw_sysfs_wait_timeout(struct fw_priv *fw_priv, long timeout) +{ + return __fw_state_wait_common(fw_priv, timeout); +} + +struct fw_sysfs { + bool nowait; + struct device dev; + struct fw_priv *fw_priv; + struct firmware *fw; +}; + +static struct fw_sysfs *to_fw_sysfs(struct device *dev) +{ + return container_of(dev, struct fw_sysfs, dev); +} + +static void __fw_load_abort(struct fw_priv *fw_priv) +{ + /* + * There is a small window in which user can write to 'loading' + * between loading done and disappearance of 'loading' + */ + if (fw_sysfs_done(fw_priv)) + return; + + list_del_init(&fw_priv->pending_list); + fw_state_aborted(fw_priv); +} + +static void fw_load_abort(struct fw_sysfs *fw_sysfs) +{ + struct fw_priv *fw_priv = fw_sysfs->fw_priv; + + __fw_load_abort(fw_priv); +} + +static LIST_HEAD(pending_fw_head); + +void kill_pending_fw_fallback_reqs(bool only_kill_custom) +{ + struct fw_priv *fw_priv; + struct fw_priv *next; + + mutex_lock(&fw_lock); + list_for_each_entry_safe(fw_priv, next, &pending_fw_head, + pending_list) { + if (!fw_priv->need_uevent || !only_kill_custom) + __fw_load_abort(fw_priv); + } + mutex_unlock(&fw_lock); +} + +static ssize_t timeout_show(struct class *class, struct class_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", __firmware_loading_timeout()); +} + +/** + * firmware_timeout_store - set number of seconds to wait for firmware + * @class: device class pointer + * @attr: device attribute pointer + * @buf: buffer to scan for timeout value + * @count: number of bytes in @buf + * + * Sets the number of seconds to wait for the firmware. Once + * this expires an error will be returned to the driver and no + * firmware will be provided. + * + * Note: zero means 'wait forever'. + **/ +static ssize_t timeout_store(struct class *class, struct class_attribute *attr, + const char *buf, size_t count) +{ + int tmp_loading_timeout = simple_strtol(buf, NULL, 10); + + if (tmp_loading_timeout < 0) + tmp_loading_timeout = 0; + + __fw_fallback_set_timeout(tmp_loading_timeout); + + return count; +} +static CLASS_ATTR_RW(timeout); + +static struct attribute *firmware_class_attrs[] = { + &class_attr_timeout.attr, + NULL, +}; +ATTRIBUTE_GROUPS(firmware_class); + +static void fw_dev_release(struct device *dev) +{ + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + + kfree(fw_sysfs); +} + +static int do_firmware_uevent(struct fw_sysfs *fw_sysfs, struct kobj_uevent_env *env) +{ + if (add_uevent_var(env, "FIRMWARE=%s", fw_sysfs->fw_priv->fw_name)) + return -ENOMEM; + if (add_uevent_var(env, "TIMEOUT=%i", __firmware_loading_timeout())) + return -ENOMEM; + if (add_uevent_var(env, "ASYNC=%d", fw_sysfs->nowait)) + return -ENOMEM; + + return 0; +} + +static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + int err = 0; + + mutex_lock(&fw_lock); + if (fw_sysfs->fw_priv) + err = do_firmware_uevent(fw_sysfs, env); + mutex_unlock(&fw_lock); + return err; +} + +static struct class firmware_class = { + .name = "firmware", + .class_groups = firmware_class_groups, + .dev_uevent = firmware_uevent, + .dev_release = fw_dev_release, +}; + +int register_sysfs_loader(void) +{ + return class_register(&firmware_class); +} + +void unregister_sysfs_loader(void) +{ + class_unregister(&firmware_class); +} + +static ssize_t firmware_loading_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + int loading = 0; + + mutex_lock(&fw_lock); + if (fw_sysfs->fw_priv) + loading = fw_sysfs_loading(fw_sysfs->fw_priv); + mutex_unlock(&fw_lock); + + return sprintf(buf, "%d\n", loading); +} + +/* Some architectures don't have PAGE_KERNEL_RO */ +#ifndef PAGE_KERNEL_RO +#define PAGE_KERNEL_RO PAGE_KERNEL +#endif + +/* one pages buffer should be mapped/unmapped only once */ +static int map_fw_priv_pages(struct fw_priv *fw_priv) +{ + if (!fw_priv->is_paged_buf) + return 0; + + vunmap(fw_priv->data); + fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, 0, + PAGE_KERNEL_RO); + if (!fw_priv->data) + return -ENOMEM; + return 0; +} + +/** + * firmware_loading_store - set value in the 'loading' control file + * @dev: device pointer + * @attr: device attribute pointer + * @buf: buffer to scan for loading control value + * @count: number of bytes in @buf + * + * The relevant values are: + * + * 1: Start a load, discarding any previous partial load. + * 0: Conclude the load and hand the data to the driver code. + * -1: Conclude the load with an error and discard any written data. + **/ +static ssize_t firmware_loading_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + struct fw_priv *fw_priv; + ssize_t written = count; + int loading = simple_strtol(buf, NULL, 10); + int i; + + mutex_lock(&fw_lock); + fw_priv = fw_sysfs->fw_priv; + if (fw_state_is_aborted(fw_priv)) + goto out; + + switch (loading) { + case 1: + /* discarding any previous partial load */ + if (!fw_sysfs_done(fw_priv)) { + for (i = 0; i < fw_priv->nr_pages; i++) + __free_page(fw_priv->pages[i]); + vfree(fw_priv->pages); + fw_priv->pages = NULL; + fw_priv->page_array_size = 0; + fw_priv->nr_pages = 0; + fw_state_start(fw_priv); + } + break; + case 0: + if (fw_sysfs_loading(fw_priv)) { + int rc; + + /* + * Several loading requests may be pending on + * one same firmware buf, so let all requests + * see the mapped 'buf->data' once the loading + * is completed. + * */ + rc = map_fw_priv_pages(fw_priv); + if (rc) + dev_err(dev, "%s: map pages failed\n", + __func__); + else + rc = security_kernel_post_read_file(NULL, + fw_priv->data, fw_priv->size, + READING_FIRMWARE); + + /* + * Same logic as fw_load_abort, only the DONE bit + * is ignored and we set ABORT only on failure. + */ + list_del_init(&fw_priv->pending_list); + if (rc) { + fw_state_aborted(fw_priv); + written = rc; + } else { + fw_state_done(fw_priv); + } + break; + } + /* fallthrough */ + default: + dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); + /* fallthrough */ + case -1: + fw_load_abort(fw_sysfs); + break; + } +out: + mutex_unlock(&fw_lock); + return written; +} + +static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store); + +static void firmware_rw_data(struct fw_priv *fw_priv, char *buffer, + loff_t offset, size_t count, bool read) +{ + if (read) + memcpy(buffer, fw_priv->data + offset, count); + else + memcpy(fw_priv->data + offset, buffer, count); +} + +static void firmware_rw(struct fw_priv *fw_priv, char *buffer, + loff_t offset, size_t count, bool read) +{ + while (count) { + void *page_data; + int page_nr = offset >> PAGE_SHIFT; + int page_ofs = offset & (PAGE_SIZE-1); + int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count); + + page_data = kmap(fw_priv->pages[page_nr]); + + if (read) + memcpy(buffer, page_data + page_ofs, page_cnt); + else + memcpy(page_data + page_ofs, buffer, page_cnt); + + kunmap(fw_priv->pages[page_nr]); + buffer += page_cnt; + offset += page_cnt; + count -= page_cnt; + } +} + +static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t offset, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + struct fw_priv *fw_priv; + ssize_t ret_count; + + mutex_lock(&fw_lock); + fw_priv = fw_sysfs->fw_priv; + if (!fw_priv || fw_sysfs_done(fw_priv)) { + ret_count = -ENODEV; + goto out; + } + if (offset > fw_priv->size) { + ret_count = 0; + goto out; + } + if (count > fw_priv->size - offset) + count = fw_priv->size - offset; + + ret_count = count; + + if (fw_priv->data) + firmware_rw_data(fw_priv, buffer, offset, count, true); + else + firmware_rw(fw_priv, buffer, offset, count, true); + +out: + mutex_unlock(&fw_lock); + return ret_count; +} + +static int fw_realloc_pages(struct fw_sysfs *fw_sysfs, int min_size) +{ + struct fw_priv *fw_priv= fw_sysfs->fw_priv; + int pages_needed = PAGE_ALIGN(min_size) >> PAGE_SHIFT; + + /* If the array of pages is too small, grow it... */ + if (fw_priv->page_array_size < pages_needed) { + int new_array_size = max(pages_needed, + fw_priv->page_array_size * 2); + struct page **new_pages; + + new_pages = vmalloc(new_array_size * sizeof(void *)); + if (!new_pages) { + fw_load_abort(fw_sysfs); + return -ENOMEM; + } + memcpy(new_pages, fw_priv->pages, + fw_priv->page_array_size * sizeof(void *)); + memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) * + (new_array_size - fw_priv->page_array_size)); + vfree(fw_priv->pages); + fw_priv->pages = new_pages; + fw_priv->page_array_size = new_array_size; + } + + while (fw_priv->nr_pages < pages_needed) { + fw_priv->pages[fw_priv->nr_pages] = + alloc_page(GFP_KERNEL | __GFP_HIGHMEM); + + if (!fw_priv->pages[fw_priv->nr_pages]) { + fw_load_abort(fw_sysfs); + return -ENOMEM; + } + fw_priv->nr_pages++; + } + return 0; +} + +/** + * firmware_data_write - write method for firmware + * @filp: open sysfs file + * @kobj: kobject for the device + * @bin_attr: bin_attr structure + * @buffer: buffer being written + * @offset: buffer offset for write in total data store area + * @count: buffer size + * + * Data written to the 'data' attribute will be later handed to + * the driver as a firmware image. + **/ +static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t offset, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); + struct fw_priv *fw_priv; + ssize_t retval; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + mutex_lock(&fw_lock); + fw_priv = fw_sysfs->fw_priv; + if (!fw_priv || fw_sysfs_done(fw_priv)) { + retval = -ENODEV; + goto out; + } + + if (fw_priv->data) { + if (offset + count > fw_priv->allocated_size) { + retval = -ENOMEM; + goto out; + } + firmware_rw_data(fw_priv, buffer, offset, count, false); + retval = count; + } else { + retval = fw_realloc_pages(fw_sysfs, offset + count); + if (retval) + goto out; + + retval = count; + firmware_rw(fw_priv, buffer, offset, count, false); + } + + fw_priv->size = max_t(size_t, offset + count, fw_priv->size); +out: + mutex_unlock(&fw_lock); + return retval; +} + +static struct bin_attribute firmware_attr_data = { + .attr = { .name = "data", .mode = 0644 }, + .size = 0, + .read = firmware_data_read, + .write = firmware_data_write, +}; + +static struct attribute *fw_dev_attrs[] = { + &dev_attr_loading.attr, + NULL +}; + +static struct bin_attribute *fw_dev_bin_attrs[] = { + &firmware_attr_data, + NULL +}; + +static const struct attribute_group fw_dev_attr_group = { + .attrs = fw_dev_attrs, + .bin_attrs = fw_dev_bin_attrs, +}; + +static const struct attribute_group *fw_dev_attr_groups[] = { + &fw_dev_attr_group, + NULL +}; + +static struct fw_sysfs * +fw_create_instance(struct firmware *firmware, const char *fw_name, + struct device *device, unsigned int opt_flags) +{ + struct fw_sysfs *fw_sysfs; + struct device *f_dev; + + fw_sysfs = kzalloc(sizeof(*fw_sysfs), GFP_KERNEL); + if (!fw_sysfs) { + fw_sysfs = ERR_PTR(-ENOMEM); + goto exit; + } + + fw_sysfs->nowait = !!(opt_flags & FW_OPT_NOWAIT); + fw_sysfs->fw = firmware; + f_dev = &fw_sysfs->dev; + + device_initialize(f_dev); + dev_set_name(f_dev, "%s", fw_name); + f_dev->parent = device; + f_dev->class = &firmware_class; + f_dev->groups = fw_dev_attr_groups; +exit: + return fw_sysfs; +} + +/* load a firmware via user helper */ +static int _request_firmware_load(struct fw_sysfs *fw_sysfs, + unsigned int opt_flags, long timeout) +{ + int retval = 0; + struct device *f_dev = &fw_sysfs->dev; + struct fw_priv *fw_priv = fw_sysfs->fw_priv; + + /* fall back on userspace loading */ + if (!fw_priv->data) + fw_priv->is_paged_buf = true; + + dev_set_uevent_suppress(f_dev, true); + + retval = device_add(f_dev); + if (retval) { + dev_err(f_dev, "%s: device_register failed\n", __func__); + goto err_put_dev; + } + + mutex_lock(&fw_lock); + list_add(&fw_priv->pending_list, &pending_fw_head); + mutex_unlock(&fw_lock); + + if (opt_flags & FW_OPT_UEVENT) { + fw_priv->need_uevent = true; + dev_set_uevent_suppress(f_dev, false); + dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_name); + kobject_uevent(&fw_sysfs->dev.kobj, KOBJ_ADD); + } else { + timeout = MAX_JIFFY_OFFSET; + } + + retval = fw_sysfs_wait_timeout(fw_priv, timeout); + if (retval < 0) { + mutex_lock(&fw_lock); + fw_load_abort(fw_sysfs); + mutex_unlock(&fw_lock); + } + + if (fw_state_is_aborted(fw_priv)) { + if (retval == -ERESTARTSYS) + retval = -EINTR; + else + retval = -EAGAIN; + } else if (fw_priv->is_paged_buf && !fw_priv->data) + retval = -ENOMEM; + + device_del(f_dev); +err_put_dev: + put_device(f_dev); + return retval; +} + +static int fw_load_from_user_helper(struct firmware *firmware, + const char *name, struct device *device, + unsigned int opt_flags) +{ + struct fw_sysfs *fw_sysfs; + long timeout; + int ret; + + timeout = firmware_loading_timeout(); + if (opt_flags & FW_OPT_NOWAIT) { + timeout = usermodehelper_read_lock_wait(timeout); + if (!timeout) { + dev_dbg(device, "firmware: %s loading timed out\n", + name); + return -EBUSY; + } + } else { + ret = usermodehelper_read_trylock(); + if (WARN_ON(ret)) { + dev_err(device, "firmware: %s will not be loaded\n", + name); + return ret; + } + } + + fw_sysfs = fw_create_instance(firmware, name, device, opt_flags); + if (IS_ERR(fw_sysfs)) { + ret = PTR_ERR(fw_sysfs); + goto out_unlock; + } + + fw_sysfs->fw_priv = firmware->priv; + ret = _request_firmware_load(fw_sysfs, opt_flags, timeout); + + if (!ret) + ret = assign_fw(firmware, device, opt_flags); + +out_unlock: + usermodehelper_read_unlock(); + + return ret; +} + +static bool fw_force_sysfs_fallback(unsigned int opt_flags) +{ + if (fw_fallback_config.force_sysfs_fallback) + return true; + if (!(opt_flags & FW_OPT_USERHELPER)) + return false; + return true; +} + +static bool fw_run_sysfs_fallback(unsigned int opt_flags) +{ + if ((opt_flags & FW_OPT_NOFALLBACK)) + return false; + + return fw_force_sysfs_fallback(opt_flags); +} + +int fw_sysfs_fallback(struct firmware *fw, const char *name, + struct device *device, + unsigned int opt_flags, + int ret) +{ + if (!fw_run_sysfs_fallback(opt_flags)) + return ret; + + dev_warn(device, "Falling back to user helper\n"); + return fw_load_from_user_helper(fw, name, device, opt_flags); +} diff --git a/drivers/base/firmware_loader/fallback.h b/drivers/base/firmware_loader/fallback.h new file mode 100644 index 000000000000..550498c7fa4c --- /dev/null +++ b/drivers/base/firmware_loader/fallback.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __FIRMWARE_FALLBACK_H +#define __FIRMWARE_FALLBACK_H + +#include +#include + +/** + * struct firmware_fallback_config - firmware fallback configuratioon settings + * + * Helps describe and fine tune the fallback mechanism. + * + * @force_sysfs_fallback: force the sysfs fallback mechanism to be used + * as if one had enabled CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y. + * @old_timeout: for internal use + * @loading_timeout: the timeout to wait for the fallback mechanism before + * giving up, in seconds. + */ +struct firmware_fallback_config { + const bool force_sysfs_fallback; + int old_timeout; + int loading_timeout; +}; + +#ifdef CONFIG_FW_LOADER_USER_HELPER +int fw_sysfs_fallback(struct firmware *fw, const char *name, + struct device *device, + unsigned int opt_flags, + int ret); +void kill_pending_fw_fallback_reqs(bool only_kill_custom); + +void fw_fallback_set_cache_timeout(void); +void fw_fallback_set_default_timeout(void); + +int register_sysfs_loader(void); +void unregister_sysfs_loader(void); +#else /* CONFIG_FW_LOADER_USER_HELPER */ +static inline int fw_sysfs_fallback(struct firmware *fw, const char *name, + struct device *device, + unsigned int opt_flags, + int ret) +{ + /* Keep carrying over the same error */ + return ret; +} + +static inline void kill_pending_fw_fallback_reqs(bool only_kill_custom) { } +static inline void fw_fallback_set_cache_timeout(void) { } +static inline void fw_fallback_set_default_timeout(void) { } + +static inline int register_sysfs_loader(void) +{ + return 0; +} + +static inline void unregister_sysfs_loader(void) +{ +} +#endif /* CONFIG_FW_LOADER_USER_HELPER */ + +#endif /* __FIRMWARE_FALLBACK_H */ diff --git a/drivers/base/firmware_loader/fallback_table.c b/drivers/base/firmware_loader/fallback_table.c new file mode 100644 index 000000000000..981419044c7e --- /dev/null +++ b/drivers/base/firmware_loader/fallback_table.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fallback.h" +#include "firmware.h" + +/* + * firmware fallback configuration table + */ + +/* Module or buit-in */ +#ifdef CONFIG_FW_LOADER_USER_HELPER + +struct firmware_fallback_config fw_fallback_config = { + .force_sysfs_fallback = IS_ENABLED(CONFIG_FW_LOADER_USER_HELPER_FALLBACK), + .loading_timeout = 60, + .old_timeout = 60, +}; +EXPORT_SYMBOL_GPL(fw_fallback_config); + +#endif diff --git a/drivers/base/firmware_loader/firmware.h b/drivers/base/firmware_loader/firmware.h new file mode 100644 index 000000000000..64acbb1a392c --- /dev/null +++ b/drivers/base/firmware_loader/firmware.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __FIRMWARE_LOADER_H +#define __FIRMWARE_LOADER_H + +#include +#include +#include +#include +#include + +#include + +/* firmware behavior options */ +#define FW_OPT_UEVENT (1U << 0) +#define FW_OPT_NOWAIT (1U << 1) +#define FW_OPT_USERHELPER (1U << 2) +#define FW_OPT_NO_WARN (1U << 3) +#define FW_OPT_NOCACHE (1U << 4) +#define FW_OPT_NOFALLBACK (1U << 5) + +enum fw_status { + FW_STATUS_UNKNOWN, + FW_STATUS_LOADING, + FW_STATUS_DONE, + FW_STATUS_ABORTED, +}; + +/* + * Concurrent request_firmware() for the same firmware need to be + * serialized. struct fw_state is simple state machine which hold the + * state of the firmware loading. + */ +struct fw_state { + struct completion completion; + enum fw_status status; +}; + +struct fw_priv { + struct kref ref; + struct list_head list; + struct firmware_cache *fwc; + struct fw_state fw_st; + void *data; + size_t size; + size_t allocated_size; +#ifdef CONFIG_FW_LOADER_USER_HELPER + bool is_paged_buf; + bool need_uevent; + struct page **pages; + int nr_pages; + int page_array_size; + struct list_head pending_list; +#endif + const char *fw_name; +}; + +extern struct mutex fw_lock; + +static inline bool __fw_state_check(struct fw_priv *fw_priv, + enum fw_status status) +{ + struct fw_state *fw_st = &fw_priv->fw_st; + + return fw_st->status == status; +} + +static inline int __fw_state_wait_common(struct fw_priv *fw_priv, long timeout) +{ + struct fw_state *fw_st = &fw_priv->fw_st; + long ret; + + ret = wait_for_completion_killable_timeout(&fw_st->completion, timeout); + if (ret != 0 && fw_st->status == FW_STATUS_ABORTED) + return -ENOENT; + if (!ret) + return -ETIMEDOUT; + + return ret < 0 ? ret : 0; +} + +static inline void __fw_state_set(struct fw_priv *fw_priv, + enum fw_status status) +{ + struct fw_state *fw_st = &fw_priv->fw_st; + + WRITE_ONCE(fw_st->status, status); + + if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED) + complete_all(&fw_st->completion); +} + +static inline void fw_state_aborted(struct fw_priv *fw_priv) +{ + __fw_state_set(fw_priv, FW_STATUS_ABORTED); +} + +static inline bool fw_state_is_aborted(struct fw_priv *fw_priv) +{ + return __fw_state_check(fw_priv, FW_STATUS_ABORTED); +} + +static inline void fw_state_start(struct fw_priv *fw_priv) +{ + __fw_state_set(fw_priv, FW_STATUS_LOADING); +} + +static inline void fw_state_done(struct fw_priv *fw_priv) +{ + __fw_state_set(fw_priv, FW_STATUS_DONE); +} + +int assign_fw(struct firmware *fw, struct device *device, + unsigned int opt_flags); + +#endif /* __FIRMWARE_LOADER_H */ diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c new file mode 100644 index 000000000000..c8966c84bd44 --- /dev/null +++ b/drivers/base/firmware_loader/main.c @@ -0,0 +1,1189 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * main.c - Multi purpose firmware loading support + * + * Copyright (c) 2003 Manuel Estrada Sainz + * + * Please see Documentation/firmware_class/ for more information. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../base.h" +#include "firmware.h" +#include "fallback.h" + +MODULE_AUTHOR("Manuel Estrada Sainz"); +MODULE_DESCRIPTION("Multi purpose firmware loading support"); +MODULE_LICENSE("GPL"); + +struct firmware_cache { + /* firmware_buf instance will be added into the below list */ + spinlock_t lock; + struct list_head head; + int state; + +#ifdef CONFIG_PM_SLEEP + /* + * Names of firmware images which have been cached successfully + * will be added into the below list so that device uncache + * helper can trace which firmware images have been cached + * before. + */ + spinlock_t name_lock; + struct list_head fw_names; + + struct delayed_work work; + + struct notifier_block pm_notify; +#endif +}; + +struct fw_cache_entry { + struct list_head list; + const char *name; +}; + +struct fw_name_devm { + unsigned long magic; + const char *name; +}; + +static inline struct fw_priv *to_fw_priv(struct kref *ref) +{ + return container_of(ref, struct fw_priv, ref); +} + +#define FW_LOADER_NO_CACHE 0 +#define FW_LOADER_START_CACHE 1 + +/* fw_lock could be moved to 'struct fw_sysfs' but since it is just + * guarding for corner cases a global lock should be OK */ +DEFINE_MUTEX(fw_lock); + +static struct firmware_cache fw_cache; + +/* Builtin firmware support */ + +#ifdef CONFIG_FW_LOADER + +extern struct builtin_fw __start_builtin_fw[]; +extern struct builtin_fw __end_builtin_fw[]; + +static void fw_copy_to_prealloc_buf(struct firmware *fw, + void *buf, size_t size) +{ + if (!buf || size < fw->size) + return; + memcpy(buf, fw->data, fw->size); +} + +static bool fw_get_builtin_firmware(struct firmware *fw, const char *name, + void *buf, size_t size) +{ + struct builtin_fw *b_fw; + + for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++) { + if (strcmp(name, b_fw->name) == 0) { + fw->size = b_fw->size; + fw->data = b_fw->data; + fw_copy_to_prealloc_buf(fw, buf, size); + + return true; + } + } + + return false; +} + +static bool fw_is_builtin_firmware(const struct firmware *fw) +{ + struct builtin_fw *b_fw; + + for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++) + if (fw->data == b_fw->data) + return true; + + return false; +} + +#else /* Module case - no builtin firmware support */ + +static inline bool fw_get_builtin_firmware(struct firmware *fw, + const char *name, void *buf, + size_t size) +{ + return false; +} + +static inline bool fw_is_builtin_firmware(const struct firmware *fw) +{ + return false; +} +#endif + +static void fw_state_init(struct fw_priv *fw_priv) +{ + struct fw_state *fw_st = &fw_priv->fw_st; + + init_completion(&fw_st->completion); + fw_st->status = FW_STATUS_UNKNOWN; +} + +static inline int fw_state_wait(struct fw_priv *fw_priv) +{ + return __fw_state_wait_common(fw_priv, MAX_SCHEDULE_TIMEOUT); +} + +static int fw_cache_piggyback_on_request(const char *name); + +static struct fw_priv *__allocate_fw_priv(const char *fw_name, + struct firmware_cache *fwc, + void *dbuf, size_t size) +{ + struct fw_priv *fw_priv; + + fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC); + if (!fw_priv) + return NULL; + + fw_priv->fw_name = kstrdup_const(fw_name, GFP_ATOMIC); + if (!fw_priv->fw_name) { + kfree(fw_priv); + return NULL; + } + + kref_init(&fw_priv->ref); + fw_priv->fwc = fwc; + fw_priv->data = dbuf; + fw_priv->allocated_size = size; + fw_state_init(fw_priv); +#ifdef CONFIG_FW_LOADER_USER_HELPER + INIT_LIST_HEAD(&fw_priv->pending_list); +#endif + + pr_debug("%s: fw-%s fw_priv=%p\n", __func__, fw_name, fw_priv); + + return fw_priv; +} + +static struct fw_priv *__lookup_fw_priv(const char *fw_name) +{ + struct fw_priv *tmp; + struct firmware_cache *fwc = &fw_cache; + + list_for_each_entry(tmp, &fwc->head, list) + if (!strcmp(tmp->fw_name, fw_name)) + return tmp; + return NULL; +} + +/* 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) +{ + struct fw_priv *tmp; + + spin_lock(&fwc->lock); + tmp = __lookup_fw_priv(fw_name); + if (tmp) { + kref_get(&tmp->ref); + spin_unlock(&fwc->lock); + *fw_priv = tmp; + pr_debug("batched request - sharing the same struct fw_priv and lookup for multiple requests\n"); + return 1; + } + tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size); + if (tmp) + list_add(&tmp->list, &fwc->head); + spin_unlock(&fwc->lock); + + *fw_priv = tmp; + + return tmp ? 0 : -ENOMEM; +} + +static void __free_fw_priv(struct kref *ref) + __releases(&fwc->lock) +{ + struct fw_priv *fw_priv = to_fw_priv(ref); + struct firmware_cache *fwc = fw_priv->fwc; + + 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); + + list_del(&fw_priv->list); + spin_unlock(&fwc->lock); + +#ifdef CONFIG_FW_LOADER_USER_HELPER + if (fw_priv->is_paged_buf) { + int i; + vunmap(fw_priv->data); + for (i = 0; i < fw_priv->nr_pages; i++) + __free_page(fw_priv->pages[i]); + vfree(fw_priv->pages); + } else +#endif + if (!fw_priv->allocated_size) + vfree(fw_priv->data); + kfree_const(fw_priv->fw_name); + kfree(fw_priv); +} + +static void free_fw_priv(struct fw_priv *fw_priv) +{ + struct firmware_cache *fwc = fw_priv->fwc; + spin_lock(&fwc->lock); + if (!kref_put(&fw_priv->ref, __free_fw_priv)) + spin_unlock(&fwc->lock); +} + +/* direct firmware loading support */ +static char fw_path_para[256]; +static const char * const fw_path[] = { + fw_path_para, + "/lib/firmware/updates/" UTS_RELEASE, + "/lib/firmware/updates", + "/lib/firmware/" UTS_RELEASE, + "/lib/firmware" +}; + +/* + * Typical usage is that passing 'firmware_class.path=$CUSTOMIZED_PATH' + * from kernel command line because firmware_class is generally built in + * kernel instead of module. + */ +module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644); +MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path"); + +static int +fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) +{ + loff_t size; + int i, len; + int rc = -ENOENT; + char *path; + enum kernel_read_file_id id = READING_FIRMWARE; + size_t msize = INT_MAX; + + /* Already populated data member means we're loading into a buffer */ + if (fw_priv->data) { + id = READING_FIRMWARE_PREALLOC_BUFFER; + msize = fw_priv->allocated_size; + } + + path = __getname(); + if (!path) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(fw_path); i++) { + /* skip the unset customized path */ + if (!fw_path[i][0]) + continue; + + len = snprintf(path, PATH_MAX, "%s/%s", + fw_path[i], fw_priv->fw_name); + if (len >= PATH_MAX) { + rc = -ENAMETOOLONG; + break; + } + + fw_priv->size = 0; + rc = kernel_read_file_from_path(path, &fw_priv->data, &size, + msize, id); + if (rc) { + if (rc == -ENOENT) + dev_dbg(device, "loading %s failed with error %d\n", + path, rc); + else + dev_warn(device, "loading %s failed with error %d\n", + path, rc); + continue; + } + dev_dbg(device, "direct-loading %s\n", fw_priv->fw_name); + fw_priv->size = size; + fw_state_done(fw_priv); + break; + } + __putname(path); + + return rc; +} + +/* firmware holds the ownership of pages */ +static void firmware_free_data(const struct firmware *fw) +{ + /* Loaded directly? */ + if (!fw->priv) { + vfree(fw->data); + return; + } + free_fw_priv(fw->priv); +} + +/* store the pages buffer info firmware from buf */ +static void fw_set_page_data(struct fw_priv *fw_priv, struct firmware *fw) +{ + fw->priv = fw_priv; +#ifdef CONFIG_FW_LOADER_USER_HELPER + fw->pages = fw_priv->pages; +#endif + fw->size = fw_priv->size; + fw->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); +} + +#ifdef CONFIG_PM_SLEEP +static void fw_name_devm_release(struct device *dev, void *res) +{ + struct fw_name_devm *fwn = res; + + if (fwn->magic == (unsigned long)&fw_cache) + pr_debug("%s: fw_name-%s devm-%p released\n", + __func__, fwn->name, res); + kfree_const(fwn->name); +} + +static int fw_devm_match(struct device *dev, void *res, + void *match_data) +{ + struct fw_name_devm *fwn = res; + + return (fwn->magic == (unsigned long)&fw_cache) && + !strcmp(fwn->name, match_data); +} + +static struct fw_name_devm *fw_find_devm_name(struct device *dev, + const char *name) +{ + struct fw_name_devm *fwn; + + fwn = devres_find(dev, fw_name_devm_release, + fw_devm_match, (void *)name); + return fwn; +} + +/* add firmware name into devres list */ +static int fw_add_devm_name(struct device *dev, const char *name) +{ + struct fw_name_devm *fwn; + + fwn = fw_find_devm_name(dev, name); + if (fwn) + return 1; + + fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm), + GFP_KERNEL); + if (!fwn) + return -ENOMEM; + fwn->name = kstrdup_const(name, GFP_KERNEL); + if (!fwn->name) { + devres_free(fwn); + return -ENOMEM; + } + + fwn->magic = (unsigned long)&fw_cache; + devres_add(dev, fwn); + + return 0; +} +#else +static int fw_add_devm_name(struct device *dev, const char *name) +{ + return 0; +} +#endif + +int assign_fw(struct firmware *fw, struct device *device, + unsigned int opt_flags) +{ + struct fw_priv *fw_priv = fw->priv; + + mutex_lock(&fw_lock); + if (!fw_priv->size || fw_state_is_aborted(fw_priv)) { + mutex_unlock(&fw_lock); + return -ENOENT; + } + + /* + * add firmware name into devres list so that we can auto cache + * and uncache firmware for device. + * + * device may has been deleted already, but the problem + * should be fixed in devres or driver core. + */ + /* don't cache firmware handled without uevent */ + if (device && (opt_flags & FW_OPT_UEVENT) && + !(opt_flags & FW_OPT_NOCACHE)) + fw_add_devm_name(device, fw_priv->fw_name); + + /* + * After caching firmware image is started, let it piggyback + * on request firmware. + */ + if (!(opt_flags & FW_OPT_NOCACHE) && + fw_priv->fwc->state == FW_LOADER_START_CACHE) { + if (fw_cache_piggyback_on_request(fw_priv->fw_name)) + kref_get(&fw_priv->ref); + } + + /* pass the pages buffer to driver at the last minute */ + fw_set_page_data(fw_priv, fw); + mutex_unlock(&fw_lock); + return 0; +} + +/* prepare firmware and firmware_buf structs; + * return 0 if a firmware is already assigned, 1 if need to load one, + * or a negative error code + */ +static int +_request_firmware_prepare(struct firmware **firmware_p, const char *name, + struct device *device, void *dbuf, size_t size) +{ + struct firmware *firmware; + struct fw_priv *fw_priv; + int ret; + + *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); + if (!firmware) { + dev_err(device, "%s: kmalloc(struct firmware) failed\n", + __func__); + return -ENOMEM; + } + + if (fw_get_builtin_firmware(firmware, name, dbuf, size)) { + dev_dbg(device, "using built-in %s\n", name); + return 0; /* assigned */ + } + + ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, dbuf, size); + + /* + * bind with 'priv' now to avoid warning in failure path + * of requesting firmware. + */ + firmware->priv = fw_priv; + + if (ret > 0) { + ret = fw_state_wait(fw_priv); + if (!ret) { + fw_set_page_data(fw_priv, firmware); + return 0; /* assigned */ + } + } + + if (ret < 0) + return ret; + return 1; /* need to load */ +} + +/* + * Batched requests need only one wake, we need to do this step last due to the + * fallback mechanism. The buf is protected with kref_get(), and it won't be + * released until the last user calls release_firmware(). + * + * Failed batched requests are possible as well, in such cases we just share + * the struct fw_priv and won't release it until all requests are woken + * and have gone through this same path. + */ +static void fw_abort_batch_reqs(struct firmware *fw) +{ + struct fw_priv *fw_priv; + + /* Loaded directly? */ + if (!fw || !fw->priv) + return; + + fw_priv = fw->priv; + if (!fw_state_is_aborted(fw_priv)) + fw_state_aborted(fw_priv); +} + +/* called from request_firmware() and request_firmware_work_func() */ +static int +_request_firmware(const struct firmware **firmware_p, const char *name, + struct device *device, void *buf, size_t size, + unsigned int opt_flags) +{ + struct firmware *fw = NULL; + int ret; + + if (!firmware_p) + return -EINVAL; + + if (!name || name[0] == '\0') { + ret = -EINVAL; + goto out; + } + + ret = _request_firmware_prepare(&fw, name, device, buf, size); + if (ret <= 0) /* error or already assigned */ + goto out; + + ret = fw_get_filesystem_firmware(device, fw->priv); + if (ret) { + if (!(opt_flags & FW_OPT_NO_WARN)) + dev_warn(device, + "Direct firmware load for %s failed with error %d\n", + name, ret); + ret = fw_sysfs_fallback(fw, name, device, opt_flags, ret); + } else + ret = assign_fw(fw, device, opt_flags); + + out: + if (ret < 0) { + fw_abort_batch_reqs(fw); + release_firmware(fw); + fw = NULL; + } + + *firmware_p = fw; + return ret; +} + +/** + * request_firmware: - send firmware request and wait for it + * @firmware_p: pointer to firmware image + * @name: name of firmware file + * @device: device for which firmware is being loaded + * + * @firmware_p will be used to return a firmware image by the name + * of @name for device @device. + * + * Should be called from user context where sleeping is allowed. + * + * @name will be used as $FIRMWARE in the uevent environment and + * should be distinctive enough not to be confused with any other + * firmware image for this or any other device. + * + * Caller must hold the reference count of @device. + * + * The function can be called safely inside device's suspend and + * resume callback. + **/ +int +request_firmware(const struct firmware **firmware_p, const char *name, + struct device *device) +{ + int ret; + + /* Need to pin this module until return */ + __module_get(THIS_MODULE); + ret = _request_firmware(firmware_p, name, device, NULL, 0, + FW_OPT_UEVENT); + module_put(THIS_MODULE); + return ret; +} +EXPORT_SYMBOL(request_firmware); + +/** + * request_firmware_direct: - load firmware directly without usermode helper + * @firmware_p: pointer to firmware image + * @name: name of firmware file + * @device: device for which firmware is being loaded + * + * This function works pretty much like request_firmware(), but this doesn't + * fall back to usermode helper even if the firmware couldn't be loaded + * directly from fs. Hence it's useful for loading optional firmwares, which + * aren't always present, without extra long timeouts of udev. + **/ +int request_firmware_direct(const struct firmware **firmware_p, + const char *name, struct device *device) +{ + int ret; + + __module_get(THIS_MODULE); + ret = _request_firmware(firmware_p, name, device, NULL, 0, + FW_OPT_UEVENT | FW_OPT_NO_WARN | + FW_OPT_NOFALLBACK); + module_put(THIS_MODULE); + return ret; +} +EXPORT_SYMBOL_GPL(request_firmware_direct); + +/** + * request_firmware_into_buf - load firmware into a previously allocated buffer + * @firmware_p: pointer to firmware image + * @name: name of firmware file + * @device: device for which firmware is being loaded and DMA region allocated + * @buf: address of buffer to load firmware into + * @size: size of buffer + * + * This function works pretty much like request_firmware(), but it doesn't + * allocate a buffer to hold the firmware data. Instead, the firmware + * is loaded directly into the buffer pointed to by @buf and the @firmware_p + * data member is pointed at @buf. + * + * This function doesn't cache firmware either. + */ +int +request_firmware_into_buf(const struct firmware **firmware_p, const char *name, + struct device *device, void *buf, size_t size) +{ + int ret; + + __module_get(THIS_MODULE); + ret = _request_firmware(firmware_p, name, device, buf, size, + FW_OPT_UEVENT | FW_OPT_NOCACHE); + module_put(THIS_MODULE); + return ret; +} +EXPORT_SYMBOL(request_firmware_into_buf); + +/** + * release_firmware: - release the resource associated with a firmware image + * @fw: firmware resource to release + **/ +void release_firmware(const struct firmware *fw) +{ + if (fw) { + if (!fw_is_builtin_firmware(fw)) + firmware_free_data(fw); + kfree(fw); + } +} +EXPORT_SYMBOL(release_firmware); + +/* Async support */ +struct firmware_work { + struct work_struct work; + struct module *module; + const char *name; + struct device *device; + void *context; + void (*cont)(const struct firmware *fw, void *context); + unsigned int opt_flags; +}; + +static void request_firmware_work_func(struct work_struct *work) +{ + struct firmware_work *fw_work; + const struct firmware *fw; + + fw_work = container_of(work, struct firmware_work, work); + + _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0, + fw_work->opt_flags); + fw_work->cont(fw, fw_work->context); + put_device(fw_work->device); /* taken in request_firmware_nowait() */ + + module_put(fw_work->module); + kfree_const(fw_work->name); + kfree(fw_work); +} + +/** + * request_firmware_nowait - asynchronous version of request_firmware + * @module: module requesting the firmware + * @uevent: sends uevent to copy the firmware image if this flag + * is non-zero else the firmware copy must be done manually. + * @name: name of firmware file + * @device: device for which firmware is being loaded + * @gfp: allocation flags + * @context: will be passed over to @cont, and + * @fw may be %NULL if firmware request fails. + * @cont: function will be called asynchronously when the firmware + * request is over. + * + * Caller must hold the reference count of @device. + * + * Asynchronous variant of request_firmware() for user contexts: + * - sleep for as small periods as possible since it may + * increase kernel boot time of built-in device drivers + * requesting firmware in their ->probe() methods, if + * @gfp is GFP_KERNEL. + * + * - can't sleep at all if @gfp is GFP_ATOMIC. + **/ +int +request_firmware_nowait( + struct module *module, bool uevent, + const char *name, struct device *device, gfp_t gfp, void *context, + void (*cont)(const struct firmware *fw, void *context)) +{ + struct firmware_work *fw_work; + + fw_work = kzalloc(sizeof(struct firmware_work), gfp); + if (!fw_work) + return -ENOMEM; + + fw_work->module = module; + fw_work->name = kstrdup_const(name, gfp); + if (!fw_work->name) { + kfree(fw_work); + return -ENOMEM; + } + fw_work->device = device; + fw_work->context = context; + fw_work->cont = cont; + fw_work->opt_flags = FW_OPT_NOWAIT | + (uevent ? FW_OPT_UEVENT : FW_OPT_USERHELPER); + + if (!try_module_get(module)) { + kfree_const(fw_work->name); + kfree(fw_work); + return -EFAULT; + } + + get_device(fw_work->device); + INIT_WORK(&fw_work->work, request_firmware_work_func); + schedule_work(&fw_work->work); + return 0; +} +EXPORT_SYMBOL(request_firmware_nowait); + +#ifdef CONFIG_PM_SLEEP +static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain); + +/** + * cache_firmware - cache one firmware image in kernel memory space + * @fw_name: the firmware image name + * + * Cache firmware in kernel memory so that drivers can use it when + * system isn't ready for them to request firmware image from userspace. + * Once it returns successfully, driver can use request_firmware or its + * nowait version to get the cached firmware without any interacting + * with userspace + * + * Return 0 if the firmware image has been cached successfully + * Return !0 otherwise + * + */ +static int cache_firmware(const char *fw_name) +{ + int ret; + const struct firmware *fw; + + pr_debug("%s: %s\n", __func__, fw_name); + + ret = request_firmware(&fw, fw_name, NULL); + if (!ret) + kfree(fw); + + pr_debug("%s: %s ret=%d\n", __func__, fw_name, ret); + + return ret; +} + +static struct fw_priv *lookup_fw_priv(const char *fw_name) +{ + struct fw_priv *tmp; + struct firmware_cache *fwc = &fw_cache; + + spin_lock(&fwc->lock); + tmp = __lookup_fw_priv(fw_name); + spin_unlock(&fwc->lock); + + return tmp; +} + +/** + * uncache_firmware - remove one cached firmware image + * @fw_name: the firmware image name + * + * Uncache one firmware image which has been cached successfully + * before. + * + * Return 0 if the firmware cache has been removed successfully + * Return !0 otherwise + * + */ +static int uncache_firmware(const char *fw_name) +{ + struct fw_priv *fw_priv; + struct firmware fw; + + pr_debug("%s: %s\n", __func__, fw_name); + + if (fw_get_builtin_firmware(&fw, fw_name, NULL, 0)) + return 0; + + fw_priv = lookup_fw_priv(fw_name); + if (fw_priv) { + free_fw_priv(fw_priv); + return 0; + } + + return -EINVAL; +} + +static struct fw_cache_entry *alloc_fw_cache_entry(const char *name) +{ + struct fw_cache_entry *fce; + + fce = kzalloc(sizeof(*fce), GFP_ATOMIC); + if (!fce) + goto exit; + + fce->name = kstrdup_const(name, GFP_ATOMIC); + if (!fce->name) { + kfree(fce); + fce = NULL; + goto exit; + } +exit: + return fce; +} + +static int __fw_entry_found(const char *name) +{ + struct firmware_cache *fwc = &fw_cache; + struct fw_cache_entry *fce; + + list_for_each_entry(fce, &fwc->fw_names, list) { + if (!strcmp(fce->name, name)) + return 1; + } + return 0; +} + +static int fw_cache_piggyback_on_request(const char *name) +{ + struct firmware_cache *fwc = &fw_cache; + struct fw_cache_entry *fce; + int ret = 0; + + spin_lock(&fwc->name_lock); + if (__fw_entry_found(name)) + goto found; + + fce = alloc_fw_cache_entry(name); + if (fce) { + ret = 1; + list_add(&fce->list, &fwc->fw_names); + pr_debug("%s: fw: %s\n", __func__, name); + } +found: + spin_unlock(&fwc->name_lock); + return ret; +} + +static void free_fw_cache_entry(struct fw_cache_entry *fce) +{ + kfree_const(fce->name); + kfree(fce); +} + +static void __async_dev_cache_fw_image(void *fw_entry, + async_cookie_t cookie) +{ + struct fw_cache_entry *fce = fw_entry; + struct firmware_cache *fwc = &fw_cache; + int ret; + + ret = cache_firmware(fce->name); + if (ret) { + spin_lock(&fwc->name_lock); + list_del(&fce->list); + spin_unlock(&fwc->name_lock); + + free_fw_cache_entry(fce); + } +} + +/* called with dev->devres_lock held */ +static void dev_create_fw_entry(struct device *dev, void *res, + void *data) +{ + struct fw_name_devm *fwn = res; + const char *fw_name = fwn->name; + struct list_head *head = data; + struct fw_cache_entry *fce; + + fce = alloc_fw_cache_entry(fw_name); + if (fce) + list_add(&fce->list, head); +} + +static int devm_name_match(struct device *dev, void *res, + void *match_data) +{ + struct fw_name_devm *fwn = res; + return (fwn->magic == (unsigned long)match_data); +} + +static void dev_cache_fw_image(struct device *dev, void *data) +{ + LIST_HEAD(todo); + struct fw_cache_entry *fce; + struct fw_cache_entry *fce_next; + struct firmware_cache *fwc = &fw_cache; + + devres_for_each_res(dev, fw_name_devm_release, + devm_name_match, &fw_cache, + dev_create_fw_entry, &todo); + + list_for_each_entry_safe(fce, fce_next, &todo, list) { + list_del(&fce->list); + + spin_lock(&fwc->name_lock); + /* only one cache entry for one firmware */ + if (!__fw_entry_found(fce->name)) { + list_add(&fce->list, &fwc->fw_names); + } else { + free_fw_cache_entry(fce); + fce = NULL; + } + spin_unlock(&fwc->name_lock); + + if (fce) + async_schedule_domain(__async_dev_cache_fw_image, + (void *)fce, + &fw_cache_domain); + } +} + +static void __device_uncache_fw_images(void) +{ + struct firmware_cache *fwc = &fw_cache; + struct fw_cache_entry *fce; + + spin_lock(&fwc->name_lock); + while (!list_empty(&fwc->fw_names)) { + fce = list_entry(fwc->fw_names.next, + struct fw_cache_entry, list); + list_del(&fce->list); + spin_unlock(&fwc->name_lock); + + uncache_firmware(fce->name); + free_fw_cache_entry(fce); + + spin_lock(&fwc->name_lock); + } + spin_unlock(&fwc->name_lock); +} + +/** + * device_cache_fw_images - cache devices' firmware + * + * If one device called request_firmware or its nowait version + * successfully before, the firmware names are recored into the + * device's devres link list, so device_cache_fw_images can call + * cache_firmware() to cache these firmwares for the device, + * then the device driver can load its firmwares easily at + * time when system is not ready to complete loading firmware. + */ +static void device_cache_fw_images(void) +{ + struct firmware_cache *fwc = &fw_cache; + DEFINE_WAIT(wait); + + pr_debug("%s\n", __func__); + + /* cancel uncache work */ + cancel_delayed_work_sync(&fwc->work); + + fw_fallback_set_cache_timeout(); + + mutex_lock(&fw_lock); + fwc->state = FW_LOADER_START_CACHE; + dpm_for_each_dev(NULL, dev_cache_fw_image); + mutex_unlock(&fw_lock); + + /* wait for completion of caching firmware for all devices */ + async_synchronize_full_domain(&fw_cache_domain); + + fw_fallback_set_default_timeout(); +} + +/** + * device_uncache_fw_images - uncache devices' firmware + * + * uncache all firmwares which have been cached successfully + * by device_uncache_fw_images earlier + */ +static void device_uncache_fw_images(void) +{ + pr_debug("%s\n", __func__); + __device_uncache_fw_images(); +} + +static void device_uncache_fw_images_work(struct work_struct *work) +{ + device_uncache_fw_images(); +} + +/** + * device_uncache_fw_images_delay - uncache devices firmwares + * @delay: number of milliseconds to delay uncache device firmwares + * + * uncache all devices's firmwares which has been cached successfully + * by device_cache_fw_images after @delay milliseconds. + */ +static void device_uncache_fw_images_delay(unsigned long delay) +{ + queue_delayed_work(system_power_efficient_wq, &fw_cache.work, + msecs_to_jiffies(delay)); +} + +static int fw_pm_notify(struct notifier_block *notify_block, + unsigned long mode, void *unused) +{ + switch (mode) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + case PM_RESTORE_PREPARE: + /* + * kill pending fallback requests with a custom fallback + * to avoid stalling suspend. + */ + kill_pending_fw_fallback_reqs(true); + device_cache_fw_images(); + break; + + case PM_POST_SUSPEND: + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + /* + * In case that system sleep failed and syscore_suspend is + * not called. + */ + mutex_lock(&fw_lock); + fw_cache.state = FW_LOADER_NO_CACHE; + mutex_unlock(&fw_lock); + + device_uncache_fw_images_delay(10 * MSEC_PER_SEC); + break; + } + + return 0; +} + +/* stop caching firmware once syscore_suspend is reached */ +static int fw_suspend(void) +{ + fw_cache.state = FW_LOADER_NO_CACHE; + return 0; +} + +static struct syscore_ops fw_syscore_ops = { + .suspend = fw_suspend, +}; + +static int __init register_fw_pm_ops(void) +{ + int ret; + + spin_lock_init(&fw_cache.name_lock); + INIT_LIST_HEAD(&fw_cache.fw_names); + + INIT_DELAYED_WORK(&fw_cache.work, + device_uncache_fw_images_work); + + fw_cache.pm_notify.notifier_call = fw_pm_notify; + ret = register_pm_notifier(&fw_cache.pm_notify); + if (ret) + return ret; + + register_syscore_ops(&fw_syscore_ops); + + return ret; +} + +static inline void unregister_fw_pm_ops(void) +{ + unregister_syscore_ops(&fw_syscore_ops); + unregister_pm_notifier(&fw_cache.pm_notify); +} +#else +static int fw_cache_piggyback_on_request(const char *name) +{ + return 0; +} +static inline int register_fw_pm_ops(void) +{ + return 0; +} +static inline void unregister_fw_pm_ops(void) +{ +} +#endif + +static void __init fw_cache_init(void) +{ + spin_lock_init(&fw_cache.lock); + INIT_LIST_HEAD(&fw_cache.head); + fw_cache.state = FW_LOADER_NO_CACHE; +} + +static int fw_shutdown_notify(struct notifier_block *unused1, + unsigned long unused2, void *unused3) +{ + /* + * Kill all pending fallback requests to avoid both stalling shutdown, + * and avoid a deadlock with the usermode_lock. + */ + kill_pending_fw_fallback_reqs(false); + + return NOTIFY_DONE; +} + +static struct notifier_block fw_shutdown_nb = { + .notifier_call = fw_shutdown_notify, +}; + +static int __init firmware_class_init(void) +{ + int ret; + + /* No need to unfold these on exit */ + fw_cache_init(); + + ret = register_fw_pm_ops(); + if (ret) + return ret; + + ret = register_reboot_notifier(&fw_shutdown_nb); + if (ret) + goto out; + + return register_sysfs_loader(); + +out: + unregister_fw_pm_ops(); + return ret; +} + +static void __exit firmware_class_exit(void) +{ + unregister_fw_pm_ops(); + unregister_reboot_notifier(&fw_shutdown_nb); + unregister_sysfs_loader(); +} + +fs_initcall(firmware_class_init); +module_exit(firmware_class_exit); -- cgit v1.2.3 From ceb18132248d95b2c68e30c3df78e69175c4452f Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:51 -0800 Subject: firmware: enable run time change of forcing fallback loader Currently one requires to test four kernel configurations to test the firmware API completely: 0) CONFIG_FW_LOADER=y 1) o CONFIG_FW_LOADER=y o CONFIG_FW_LOADER_USER_HELPER=y 2) o CONFIG_FW_LOADER=y o CONFIG_FW_LOADER_USER_HELPER=y o CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y 3) When CONFIG_FW_LOADER=m the built-in stuff is disabled, we have no current tests for this. We can reduce the requirements to three kernel configurations by making fw_config.force_sysfs_fallback a proc knob we flip on off. For kernels that disable CONFIG_IKCONFIG_PROC this can also enable one to inspect if CONFIG_FW_LOADER_USER_HELPER_FALLBACK was enabled at build time by checking the proc value at boot time. Acked-by: Kees Cook Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/fallback.c | 1 + drivers/base/firmware_loader/fallback.h | 4 +++- drivers/base/firmware_loader/fallback_table.c | 17 +++++++++++++++++ kernel/sysctl.c | 11 +++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c index 9b65837256d6..45cc40933a47 100644 --- a/drivers/base/firmware_loader/fallback.c +++ b/drivers/base/firmware_loader/fallback.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "fallback.h" #include "firmware.h" diff --git a/drivers/base/firmware_loader/fallback.h b/drivers/base/firmware_loader/fallback.h index 550498c7fa4c..ca7e69a8417b 100644 --- a/drivers/base/firmware_loader/fallback.h +++ b/drivers/base/firmware_loader/fallback.h @@ -12,12 +12,14 @@ * * @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. * @old_timeout: for internal use * @loading_timeout: the timeout to wait for the fallback mechanism before * giving up, in seconds. */ struct firmware_fallback_config { - const bool force_sysfs_fallback; + unsigned int force_sysfs_fallback; int old_timeout; int loading_timeout; }; diff --git a/drivers/base/firmware_loader/fallback_table.c b/drivers/base/firmware_loader/fallback_table.c index 981419044c7e..92365e053e30 100644 --- a/drivers/base/firmware_loader/fallback_table.c +++ b/drivers/base/firmware_loader/fallback_table.c @@ -19,6 +19,9 @@ /* Module or buit-in */ #ifdef CONFIG_FW_LOADER_USER_HELPER +static unsigned int zero; +static unsigned int one = 1; + struct firmware_fallback_config fw_fallback_config = { .force_sysfs_fallback = IS_ENABLED(CONFIG_FW_LOADER_USER_HELPER_FALLBACK), .loading_timeout = 60, @@ -26,4 +29,18 @@ struct firmware_fallback_config fw_fallback_config = { }; EXPORT_SYMBOL_GPL(fw_fallback_config); +struct ctl_table firmware_config_table[] = { + { + .procname = "force_sysfs_fallback", + .data = &fw_fallback_config.force_sysfs_fallback, + .maxlen = sizeof(unsigned int), + .mode = 0644, + .proc_handler = proc_douintvec_minmax, + .extra1 = &zero, + .extra2 = &one, + }, + { } +}; +EXPORT_SYMBOL_GPL(firmware_config_table); + #endif diff --git a/kernel/sysctl.c b/kernel/sysctl.c index f98f28c12020..bdf7090b106d 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -253,6 +253,10 @@ extern struct ctl_table random_table[]; extern struct ctl_table epoll_table[]; #endif +#ifdef CONFIG_FW_LOADER_USER_HELPER +extern struct ctl_table firmware_config_table[]; +#endif + #ifdef HAVE_ARCH_PICK_MMAP_LAYOUT int sysctl_legacy_va_layout; #endif @@ -748,6 +752,13 @@ static struct ctl_table kern_table[] = { .mode = 0555, .child = usermodehelper_table, }, +#ifdef CONFIG_FW_LOADER_USER_HELPER + { + .procname = "firmware_config", + .mode = 0555, + .child = firmware_config_table, + }, +#endif { .procname = "overflowuid", .data = &overflowuid, -- cgit v1.2.3 From 2cd7a1c6dcd33e7c1a82b254871230f29866d4e9 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:52 -0800 Subject: firmware: enable to force disable the fallback mechanism at run time You currently need four different kernel builds to test the firmware API fully. By adding a proc knob to force disable the fallback mechanism completely we are able to reduce the amount of kernels you need built to test the firmware API down to two. Acked-by: Kees Cook Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/fallback.c | 5 +++++ drivers/base/firmware_loader/fallback.h | 4 ++++ drivers/base/firmware_loader/fallback_table.c | 9 +++++++++ 3 files changed, 18 insertions(+) diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c index 45cc40933a47..d6838e7ec00c 100644 --- a/drivers/base/firmware_loader/fallback.c +++ b/drivers/base/firmware_loader/fallback.c @@ -643,6 +643,11 @@ static bool fw_force_sysfs_fallback(unsigned int opt_flags) static bool fw_run_sysfs_fallback(unsigned int opt_flags) { + if (fw_fallback_config.ignore_sysfs_fallback) { + pr_info_once("Ignoring firmware sysfs fallback due to debugfs knob\n"); + return false; + } + if ((opt_flags & FW_OPT_NOFALLBACK)) return false; diff --git a/drivers/base/firmware_loader/fallback.h b/drivers/base/firmware_loader/fallback.h index ca7e69a8417b..dfebc644ed35 100644 --- a/drivers/base/firmware_loader/fallback.h +++ b/drivers/base/firmware_loader/fallback.h @@ -14,12 +14,16 @@ * 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; }; diff --git a/drivers/base/firmware_loader/fallback_table.c b/drivers/base/firmware_loader/fallback_table.c index 92365e053e30..7428659d8df9 100644 --- a/drivers/base/firmware_loader/fallback_table.c +++ b/drivers/base/firmware_loader/fallback_table.c @@ -39,6 +39,15 @@ struct ctl_table firmware_config_table[] = { .extra1 = &zero, .extra2 = &one, }, + { + .procname = "ignore_sysfs_fallback", + .data = &fw_fallback_config.ignore_sysfs_fallback, + .maxlen = sizeof(unsigned int), + .mode = 0644, + .proc_handler = proc_douintvec_minmax, + .extra1 = &zero, + .extra2 = &one, + }, { } }; EXPORT_SYMBOL_GPL(firmware_config_table); -- cgit v1.2.3 From f5a614519e67b429faf01ca36db90030976449cc Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:53 -0800 Subject: test_firmware: expand on library with shared helpers This expands our library with as many things we could find which both scripts we use share. Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/firmware/fw_fallback.sh | 31 +++----------- tools/testing/selftests/firmware/fw_filesystem.sh | 41 +++--------------- tools/testing/selftests/firmware/fw_lib.sh | 52 +++++++++++++++++++++++ 3 files changed, 63 insertions(+), 61 deletions(-) diff --git a/tools/testing/selftests/firmware/fw_fallback.sh b/tools/testing/selftests/firmware/fw_fallback.sh index 323c921a2469..9337a0328627 100755 --- a/tools/testing/selftests/firmware/fw_fallback.sh +++ b/tools/testing/selftests/firmware/fw_fallback.sh @@ -6,30 +6,17 @@ # won't find so that we can do the load ourself manually. set -e +TEST_REQS_FW_SYSFS_FALLBACK="yes" +TEST_REQS_FW_SET_CUSTOM_PATH="no" TEST_DIR=$(dirname $0) source $TEST_DIR/fw_lib.sh check_mods +check_setup +verify_reqs +setup_tmp_file -HAS_FW_LOADER_USER_HELPER=$(kconfig_has CONFIG_FW_LOADER_USER_HELPER=y) -HAS_FW_LOADER_USER_HELPER_FALLBACK=$(kconfig_has CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y) - -if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then - OLD_TIMEOUT=$(cat /sys/class/firmware/timeout) -else - echo "usermode helper disabled so ignoring test" - exit 0 -fi - -FWPATH=$(mktemp -d) -FW="$FWPATH/test-firmware.bin" - -test_finish() -{ - echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout - rm -f "$FW" - rmdir "$FWPATH" -} +trap "test_finish" EXIT load_fw() { @@ -168,12 +155,6 @@ load_fw_fallback_with_child() return $RET } -trap "test_finish" EXIT - -# This is an unlikely real-world firmware content. :) -echo "ABCD0123" >"$FW" -NAME=$(basename "$FW") - test_syfs_timeout() { DEVPATH="$DIR"/"nope-$NAME"/loading diff --git a/tools/testing/selftests/firmware/fw_filesystem.sh b/tools/testing/selftests/firmware/fw_filesystem.sh index 748f2f5737e9..7f47877fa7fa 100755 --- a/tools/testing/selftests/firmware/fw_filesystem.sh +++ b/tools/testing/selftests/firmware/fw_filesystem.sh @@ -6,38 +6,15 @@ # know so we can be sure we're not accidentally testing the user helper. set -e +TEST_REQS_FW_SYSFS_FALLBACK="no" +TEST_REQS_FW_SET_CUSTOM_PATH="yes" TEST_DIR=$(dirname $0) source $TEST_DIR/fw_lib.sh check_mods - -# CONFIG_FW_LOADER_USER_HELPER has a sysfs class under /sys/class/firmware/ -# These days most distros enable CONFIG_FW_LOADER_USER_HELPER but disable -# CONFIG_FW_LOADER_USER_HELPER_FALLBACK. We use /sys/class/firmware/ as an -# indicator for CONFIG_FW_LOADER_USER_HELPER. -HAS_FW_LOADER_USER_HELPER=$(if [ -d /sys/class/firmware/ ]; then echo yes; else echo no; fi) - -if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then - OLD_TIMEOUT=$(cat /sys/class/firmware/timeout) -fi - -OLD_FWPATH=$(cat /sys/module/firmware_class/parameters/path) - -FWPATH=$(mktemp -d) -FW="$FWPATH/test-firmware.bin" - -test_finish() -{ - if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then - echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout - fi - if [ "$OLD_FWPATH" = "" ]; then - OLD_FWPATH=" " - fi - echo -n "$OLD_FWPATH" >/sys/module/firmware_class/parameters/path - rm -f "$FW" - rmdir "$FWPATH" -} +check_setup +verify_reqs +setup_tmp_file trap "test_finish" EXIT @@ -46,14 +23,6 @@ if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then echo 1 >/sys/class/firmware/timeout fi -# Set the kernel search path. -echo -n "$FWPATH" >/sys/module/firmware_class/parameters/path - -# This is an unlikely real-world firmware content. :) -echo "ABCD0123" >"$FW" - -NAME=$(basename "$FW") - if printf '\000' >"$DIR"/trigger_request 2> /dev/null; then echo "$0: empty filename should not succeed" >&2 exit 1 diff --git a/tools/testing/selftests/firmware/fw_lib.sh b/tools/testing/selftests/firmware/fw_lib.sh index 467567c758b9..e3cc4483fdba 100755 --- a/tools/testing/selftests/firmware/fw_lib.sh +++ b/tools/testing/selftests/firmware/fw_lib.sh @@ -43,6 +43,58 @@ check_mods() fi } +check_setup() +{ + HAS_FW_LOADER_USER_HELPER="$(kconfig_has CONFIG_FW_LOADER_USER_HELPER=y)" + HAS_FW_LOADER_USER_HELPER_FALLBACK="$(kconfig_has CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y)" + + if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then + OLD_TIMEOUT="$(cat /sys/class/firmware/timeout)" + fi + + OLD_FWPATH="$(cat /sys/module/firmware_class/parameters/path)" +} + +verify_reqs() +{ + if [ "$TEST_REQS_FW_SYSFS_FALLBACK" = "yes" ]; then + if [ ! "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then + echo "usermode helper disabled so ignoring test" + exit 0 + fi + fi +} + +setup_tmp_file() +{ + FWPATH=$(mktemp -d) + FW="$FWPATH/test-firmware.bin" + echo "ABCD0123" >"$FW" + NAME=$(basename "$FW") + if [ "$TEST_REQS_FW_SET_CUSTOM_PATH" = "yes" ]; then + echo -n "$FWPATH" >/sys/module/firmware_class/parameters/path + fi +} + +test_finish() +{ + if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then + echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout + fi + if [ "$OLD_FWPATH" = "" ]; then + OLD_FWPATH=" " + fi + if [ "$TEST_REQS_FW_SET_CUSTOM_PATH" = "yes" ]; then + echo -n "$OLD_FWPATH" >/sys/module/firmware_class/parameters/path + fi + if [ -f $FW ]; then + rm -f "$FW" + fi + if [ -d $FWPATH ]; then + rm -rf "$FWPATH" + fi +} + kconfig_has() { if [ -f $PROC_CONFIG ]; then -- cgit v1.2.3 From b3cf21fae1fe0b3a3e8ada0a60d3fb6217ebce5b Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:54 -0800 Subject: test_firmware: test three firmware kernel configs using a proc knob Since we now have knobs to twiddle what used to be set on kernel configurations we can build one base kernel configuration and modify behaviour to mimic such kernel configurations to test them. Provided you build a kernel with: CONFIG_TEST_FIRMWARE=y CONFIG_FW_LOADER=y CONFIG_FW_LOADER_USER_HELPER=y CONFIG_IKCONFIG=y CONFIG_IKCONFIG_PROC=y We should now be able test all possible kernel configurations when FW_LOADER=y. Note that when FW_LOADER=m we just don't provide the built-in functionality of the built-in firmware. If you're on an old kernel and either don't have /proc/config.gz (CONFIG_IKCONFIG_PROC) or haven't enabled CONFIG_FW_LOADER_USER_HELPER we cannot run these dynamic tests, so just run both scripts just as we used to before making blunt assumptions about your setup and requirements exactly as we did before. Acked-by: Kees Cook Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/firmware/Makefile | 2 +- tools/testing/selftests/firmware/fw_lib.sh | 51 +++++++++++++++++ tools/testing/selftests/firmware/fw_run_tests.sh | 70 ++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100755 tools/testing/selftests/firmware/fw_run_tests.sh diff --git a/tools/testing/selftests/firmware/Makefile b/tools/testing/selftests/firmware/Makefile index 1894d625af2d..826f38d5dd19 100644 --- a/tools/testing/selftests/firmware/Makefile +++ b/tools/testing/selftests/firmware/Makefile @@ -3,7 +3,7 @@ # No binaries, but make sure arg-less "make" doesn't trigger "run_tests" all: -TEST_PROGS := fw_filesystem.sh fw_fallback.sh +TEST_PROGS := fw_run_tests.sh include ../lib.mk diff --git a/tools/testing/selftests/firmware/fw_lib.sh b/tools/testing/selftests/firmware/fw_lib.sh index e3cc4483fdba..98dceb847ba0 100755 --- a/tools/testing/selftests/firmware/fw_lib.sh +++ b/tools/testing/selftests/firmware/fw_lib.sh @@ -47,6 +47,34 @@ check_setup() { HAS_FW_LOADER_USER_HELPER="$(kconfig_has CONFIG_FW_LOADER_USER_HELPER=y)" HAS_FW_LOADER_USER_HELPER_FALLBACK="$(kconfig_has CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y)" + PROC_FW_IGNORE_SYSFS_FALLBACK="0" + PROC_FW_FORCE_SYSFS_FALLBACK="0" + + if [ -z $PROC_SYS_DIR ]; then + PROC_SYS_DIR="/proc/sys/kernel" + fi + + FW_PROC="${PROC_SYS_DIR}/firmware_config" + FW_FORCE_SYSFS_FALLBACK="$FW_PROC/force_sysfs_fallback" + FW_IGNORE_SYSFS_FALLBACK="$FW_PROC/ignore_sysfs_fallback" + + if [ -f $FW_FORCE_SYSFS_FALLBACK ]; then + PROC_FW_FORCE_SYSFS_FALLBACK="$(cat $FW_FORCE_SYSFS_FALLBACK)" + fi + + if [ -f $FW_IGNORE_SYSFS_FALLBACK ]; then + PROC_FW_IGNORE_SYSFS_FALLBACK="$(cat $FW_IGNORE_SYSFS_FALLBACK)" + fi + + if [ "$PROC_FW_FORCE_SYSFS_FALLBACK" = "1" ]; then + HAS_FW_LOADER_USER_HELPER="yes" + HAS_FW_LOADER_USER_HELPER_FALLBACK="yes" + fi + + if [ "$PROC_FW_IGNORE_SYSFS_FALLBACK" = "1" ]; then + HAS_FW_LOADER_USER_HELPER_FALLBACK="no" + HAS_FW_LOADER_USER_HELPER="no" + fi if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then OLD_TIMEOUT="$(cat /sys/class/firmware/timeout)" @@ -76,6 +104,28 @@ setup_tmp_file() fi } +proc_set_force_sysfs_fallback() +{ + if [ -f $FW_FORCE_SYSFS_FALLBACK ]; then + echo -n $1 > $FW_FORCE_SYSFS_FALLBACK + check_setup + fi +} + +proc_set_ignore_sysfs_fallback() +{ + if [ -f $FW_IGNORE_SYSFS_FALLBACK ]; then + echo -n $1 > $FW_IGNORE_SYSFS_FALLBACK + check_setup + fi +} + +proc_restore_defaults() +{ + proc_set_force_sysfs_fallback 0 + proc_set_ignore_sysfs_fallback 0 +} + test_finish() { if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then @@ -93,6 +143,7 @@ test_finish() if [ -d $FWPATH ]; then rm -rf "$FWPATH" fi + proc_restore_defaults } kconfig_has() diff --git a/tools/testing/selftests/firmware/fw_run_tests.sh b/tools/testing/selftests/firmware/fw_run_tests.sh new file mode 100755 index 000000000000..06d638e9dc62 --- /dev/null +++ b/tools/testing/selftests/firmware/fw_run_tests.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# This runs all known tests across all known possible configurations we could +# emulate in one run. + +set -e + +TEST_DIR=$(dirname $0) +source $TEST_DIR/fw_lib.sh + +export HAS_FW_LOADER_USER_HELPER="" +export HAS_FW_LOADER_USER_HELPER_FALLBACK="" + +run_tests() +{ + proc_set_force_sysfs_fallback $1 + proc_set_ignore_sysfs_fallback $2 + $TEST_DIR/fw_filesystem.sh + + proc_set_force_sysfs_fallback $1 + proc_set_ignore_sysfs_fallback $2 + $TEST_DIR/fw_fallback.sh +} + +run_test_config_0001() +{ + echo "-----------------------------------------------------" + echo "Running kernel configuration test 1 -- rare" + echo "Emulates:" + echo "CONFIG_FW_LOADER=y" + echo "CONFIG_FW_LOADER_USER_HELPER=n" + echo "CONFIG_FW_LOADER_USER_HELPER_FALLBACK=n" + run_tests 0 1 +} + +run_test_config_0002() +{ + echo "-----------------------------------------------------" + echo "Running kernel configuration test 2 -- distro" + echo "Emulates:" + echo "CONFIG_FW_LOADER=y" + echo "CONFIG_FW_LOADER_USER_HELPER=y" + echo "CONFIG_FW_LOADER_USER_HELPER_FALLBACK=n" + proc_set_ignore_sysfs_fallback 0 + run_tests 0 0 +} + +run_test_config_0003() +{ + echo "-----------------------------------------------------" + echo "Running kernel configuration test 3 -- android" + echo "Emulates:" + echo "CONFIG_FW_LOADER=y" + echo "CONFIG_FW_LOADER_USER_HELPER=y" + echo "CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y" + run_tests 1 0 +} + +check_mods +check_setup + +if [ -f $FW_FORCE_SYSFS_FALLBACK ]; then + run_test_config_0001 + run_test_config_0002 + run_test_config_0003 +else + echo "Running basic kernel configuration, working with your config" + run_test +fi -- cgit v1.2.3 From 60fa74263cbeae1704908c403aba2bfd62c798e8 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:55 -0800 Subject: rename: _request_firmware_load() fw_load_sysfs_fallback() This reflects much clearer what is being done. While at it, kdoc'ify it. Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/firmware/fallback-mechanisms.rst | 2 +- drivers/base/firmware_loader/fallback.c | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Documentation/driver-api/firmware/fallback-mechanisms.rst b/Documentation/driver-api/firmware/fallback-mechanisms.rst index 4055ac76b288..f353783ae0be 100644 --- a/Documentation/driver-api/firmware/fallback-mechanisms.rst +++ b/Documentation/driver-api/firmware/fallback-mechanisms.rst @@ -112,7 +112,7 @@ Since a device is created for the sysfs interface to help load firmware as a fallback mechanism userspace can be informed of the addition of the device by relying on kobject uevents. The addition of the device into the device hierarchy means the fallback mechanism for firmware loading has been initiated. -For details of implementation refer to _request_firmware_load(), in particular +For details of implementation refer to fw_load_sysfs_fallback(), in particular on the use of dev_set_uevent_suppress() and kobject_uevent(). The kernel's kobject uevent mechanism is implemented in lib/kobject_uevent.c, diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c index d6838e7ec00c..0a8ec7fec585 100644 --- a/drivers/base/firmware_loader/fallback.c +++ b/drivers/base/firmware_loader/fallback.c @@ -535,8 +535,15 @@ exit: return fw_sysfs; } -/* load a firmware via user helper */ -static int _request_firmware_load(struct fw_sysfs *fw_sysfs, +/** + * fw_load_sysfs_fallback - load a firmware via the syfs fallback mechanism + * @fw_sysfs: firmware syfs information for the firmware to load + * @opt_flags: flags of options, FW_OPT_* + * @timeout: timeout to wait for the load + * + * In charge of constructing a sysfs fallback interface for firmware loading. + **/ +static int fw_load_sysfs_fallback(struct fw_sysfs *fw_sysfs, unsigned int opt_flags, long timeout) { int retval = 0; @@ -621,7 +628,7 @@ static int fw_load_from_user_helper(struct firmware *firmware, } fw_sysfs->fw_priv = firmware->priv; - ret = _request_firmware_load(fw_sysfs, opt_flags, timeout); + ret = fw_load_sysfs_fallback(fw_sysfs, opt_flags, timeout); if (!ret) ret = assign_fw(firmware, device, opt_flags); -- cgit v1.2.3 From d15d7311550983be97dca44ad68cbc2ca001297b Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:56 -0800 Subject: firmware: fix checking for return values for fw_add_devm_name() Currently fw_add_devm_name() returns 1 if the firmware cache was already set. This makes it complicated for us to check for correctness. It is actually non-fatal if the firmware cache is already setup, so just return 0, and simplify the checkers. fw_add_devm_name() adds device's name onto the devres for the device so that prior to suspend we cache the firmware onto memory, so that on resume the firmware is reliably available. We never were checking for success for this call though, meaning in some really rare cases we my have never setup the firmware cache for a device, which could in turn make resume fail. This is all theoretical, no known issues have been reported. This small issue has been present way since the addition of the devres firmware cache names on v3.7. Fixes: f531f05ae9437 ("firmware loader: store firmware name into devres list") Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/main.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index c8966c84bd44..f5046887e362 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -403,7 +403,7 @@ static int fw_add_devm_name(struct device *dev, const char *name) fwn = fw_find_devm_name(dev, name); if (fwn) - return 1; + return 0; fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm), GFP_KERNEL); @@ -431,6 +431,7 @@ int assign_fw(struct firmware *fw, struct device *device, unsigned int opt_flags) { struct fw_priv *fw_priv = fw->priv; + int ret; mutex_lock(&fw_lock); if (!fw_priv->size || fw_state_is_aborted(fw_priv)) { @@ -447,8 +448,13 @@ int assign_fw(struct firmware *fw, struct device *device, */ /* don't cache firmware handled without uevent */ if (device && (opt_flags & FW_OPT_UEVENT) && - !(opt_flags & FW_OPT_NOCACHE)) - fw_add_devm_name(device, fw_priv->fw_name); + !(opt_flags & FW_OPT_NOCACHE)) { + ret = fw_add_devm_name(device, fw_priv->fw_name); + if (ret) { + mutex_unlock(&fw_lock); + return ret; + } + } /* * After caching firmware image is started, let it piggyback -- cgit v1.2.3 From 3194d06a7e41d31ea4d8771b1ff3d93445e27403 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:57 -0800 Subject: firmware: add helper to check to see if fw cache is setup Add a helper to check if the firmware cache is already setup for a device. This will be used later. Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/main.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index f5046887e362..b569d8a09392 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -396,13 +396,23 @@ static struct fw_name_devm *fw_find_devm_name(struct device *dev, return fwn; } -/* add firmware name into devres list */ -static int fw_add_devm_name(struct device *dev, const char *name) +static bool fw_cache_is_setup(struct device *dev, const char *name) { struct fw_name_devm *fwn; fwn = fw_find_devm_name(dev, name); if (fwn) + return true; + + return false; +} + +/* add firmware name into devres list */ +static int fw_add_devm_name(struct device *dev, const char *name) +{ + struct fw_name_devm *fwn; + + if (fw_cache_is_setup(dev, name)) return 0; fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm), -- cgit v1.2.3 From 9952db75e0659d9db62d76d9a351fdae06898642 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:58 -0800 Subject: test_firmware: modify custom fallback tests to use unique files Users of the custom firmware fallback interface is are not supposed to use the firmware cache interface, this can happen if for instance the one of the APIs which use the firmware cache is used first with one firmware file and then the request_firmware_nowait(uevent=false) API is used with the same file. We'll soon become strict about this on the firmware interface to reject such calls later, so correct the test scripts to avoid such uses as well. We address this on the tests scripts by simply using unique names when testing the custom fallback interface. Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/firmware/fw_fallback.sh | 20 ++++++++++++++------ tools/testing/selftests/firmware/fw_filesystem.sh | 11 +++++++++-- tools/testing/selftests/firmware/fw_lib.sh | 23 +++++++++++++++++++++++ 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/tools/testing/selftests/firmware/fw_fallback.sh b/tools/testing/selftests/firmware/fw_fallback.sh index 9337a0328627..8e2e34a2ca69 100755 --- a/tools/testing/selftests/firmware/fw_fallback.sh +++ b/tools/testing/selftests/firmware/fw_fallback.sh @@ -238,8 +238,10 @@ run_sysfs_main_tests() run_sysfs_custom_load_tests() { - if load_fw_custom "$NAME" "$FW" ; then - if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then + RANDOM_FILE_PATH=$(setup_random_file) + RANDOM_FILE="$(basename $RANDOM_FILE_PATH)" + if load_fw_custom "$RANDOM_FILE" "$RANDOM_FILE_PATH" ; then + if ! diff -q "$RANDOM_FILE_PATH" /dev/test_firmware >/dev/null ; then echo "$0: firmware was not loaded" >&2 exit 1 else @@ -247,8 +249,10 @@ run_sysfs_custom_load_tests() fi fi - if load_fw_custom "$NAME" "$FW" ; then - if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then + RANDOM_FILE_PATH=$(setup_random_file) + RANDOM_FILE="$(basename $RANDOM_FILE_PATH)" + if load_fw_custom "$RANDOM_FILE" "$RANDOM_FILE_PATH" ; then + if ! diff -q "$RANDOM_FILE_PATH" /dev/test_firmware >/dev/null ; then echo "$0: firmware was not loaded" >&2 exit 1 else @@ -256,8 +260,12 @@ run_sysfs_custom_load_tests() fi fi - if load_fw_custom_cancel "nope-$NAME" "$FW" ; then - if diff -q "$FW" /dev/test_firmware >/dev/null ; then + RANDOM_FILE_REAL="$RANDOM_FILE_PATH" + FAKE_RANDOM_FILE_PATH=$(setup_random_file_fake) + FAKE_RANDOM_FILE="$(basename $FAKE_RANDOM_FILE_PATH)" + + if load_fw_custom_cancel "$FAKE_RANDOM_FILE" "$RANDOM_FILE_REAL" ; then + if diff -q "$RANDOM_FILE_PATH" /dev/test_firmware >/dev/null ; then echo "$0: firmware was expected to be cancelled" >&2 exit 1 else diff --git a/tools/testing/selftests/firmware/fw_filesystem.sh b/tools/testing/selftests/firmware/fw_filesystem.sh index 7f47877fa7fa..6452d2129cd9 100755 --- a/tools/testing/selftests/firmware/fw_filesystem.sh +++ b/tools/testing/selftests/firmware/fw_filesystem.sh @@ -230,10 +230,13 @@ test_wait_and_cancel_custom_load() test_request_firmware_nowait_custom_nofile() { echo -n "Batched request_firmware_nowait(uevent=false) nofile try #$1: " + config_reset config_unset_uevent - config_set_name nope-test-firmware.bin + RANDOM_FILE_PATH=$(setup_random_file_fake) + RANDOM_FILE="$(basename $RANDOM_FILE_PATH)" + config_set_name $RANDOM_FILE config_trigger_async & - test_wait_and_cancel_custom_load nope-test-firmware.bin + test_wait_and_cancel_custom_load $RANDOM_FILE wait release_all_firmware echo "OK" @@ -271,7 +274,11 @@ test_request_firmware_nowait_uevent() test_request_firmware_nowait_custom() { echo -n "Batched request_firmware_nowait(uevent=false) try #$1: " + config_reset config_unset_uevent + RANDOM_FILE_PATH=$(setup_random_file) + RANDOM_FILE="$(basename $RANDOM_FILE_PATH)" + config_set_name $RANDOM_FILE config_trigger_async release_all_firmware echo "OK" diff --git a/tools/testing/selftests/firmware/fw_lib.sh b/tools/testing/selftests/firmware/fw_lib.sh index 98dceb847ba0..9ea31b57d71a 100755 --- a/tools/testing/selftests/firmware/fw_lib.sh +++ b/tools/testing/selftests/firmware/fw_lib.sh @@ -104,6 +104,29 @@ setup_tmp_file() fi } +__setup_random_file() +{ + RANDOM_FILE_PATH="$(mktemp -p $FWPATH)" + # mktemp says dry-run -n is unsafe, so... + if [[ "$1" = "fake" ]]; then + rm -rf $RANDOM_FILE_PATH + sync + else + echo "ABCD0123" >"$RANDOM_FILE_PATH" + fi + echo $RANDOM_FILE_PATH +} + +setup_random_file() +{ + echo $(__setup_random_file) +} + +setup_random_file_fake() +{ + echo $(__setup_random_file fake) +} + proc_set_force_sysfs_fallback() { if [ -f $FW_FORCE_SYSFS_FALLBACK ]; then -- cgit v1.2.3 From 995e8695f65db7a8b465b5c27887b32e8e5bb66e Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Sat, 10 Mar 2018 06:14:59 -0800 Subject: firmware: ensure the firmware cache is not used on incompatible calls request_firmware_into_buf() explicitly disables the firmware cache, meanwhile the firmware cache cannot be used when request_firmware_nowait() is used without the uevent. Enforce a sanity check for this to avoid future issues undocumented behaviours should misuses of the firmware cache happen later. One of the reasons we want to enforce this is the firmware cache is used for helping with suspend/resume, and if incompatible calls use it they can stall suspend. Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/main.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index b569d8a09392..2913bb0e5e7b 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -431,6 +431,11 @@ static int fw_add_devm_name(struct device *dev, const char *name) return 0; } #else +static bool fw_cache_is_setup(struct device *dev, const char *name) +{ + return false; +} + static int fw_add_devm_name(struct device *dev, const char *name) { return 0; @@ -672,6 +677,9 @@ request_firmware_into_buf(const struct firmware **firmware_p, const char *name, { int ret; + if (fw_cache_is_setup(device, name)) + return -EOPNOTSUPP; + __module_get(THIS_MODULE); ret = _request_firmware(firmware_p, name, device, buf, size, FW_OPT_UEVENT | FW_OPT_NOCACHE); @@ -769,6 +777,12 @@ request_firmware_nowait( fw_work->opt_flags = FW_OPT_NOWAIT | (uevent ? FW_OPT_UEVENT : FW_OPT_USERHELPER); + if (!uevent && fw_cache_is_setup(device, name)) { + kfree_const(fw_work->name); + kfree(fw_work); + return -EOPNOTSUPP; + } + if (!try_module_get(module)) { kfree_const(fw_work->name); kfree(fw_work); -- cgit v1.2.3 From ccce305bd411f1c3a15eb8ca3b3cf827e38bd47c Mon Sep 17 00:00:00 2001 From: Stephen Rothwell Date: Wed, 21 Mar 2018 19:06:35 +1100 Subject: firmware: explicitly include vmalloc.h After some other include file changes, fixes: drivers/base/firmware_loader/fallback.c: In function 'map_fw_priv_pages': drivers/base/firmware_loader/fallback.c:232:2: error: implicit declaration of function 'vunmap'; did you mean 'kunmap'? [-Werror=implicit-function-declaration] vunmap(fw_priv->data); ^~~~~~ kunmap drivers/base/firmware_loader/fallback.c:233:18: error: implicit declaration of function 'vmap'; did you mean 'kmap'? [-Werror=implicit-function-declaration] fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, 0, ^~~~ kmap drivers/base/firmware_loader/fallback.c:233:16: warning: assignment makes pointer from integer without a cast [-Wint-conversion] fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, 0, ^ drivers/base/firmware_loader/fallback.c: In function 'firmware_loading_store': drivers/base/firmware_loader/fallback.c:274:4: error: implicit declaration of function 'vfree'; did you mean 'kvfree'? [-Werror=implicit-function-declaration] vfree(fw_priv->pages); ^~~~~ kvfree drivers/base/firmware_loader/fallback.c: In function 'fw_realloc_pages': drivers/base/firmware_loader/fallback.c:405:15: error: implicit declaration of function 'vmalloc'; did you mean 'kvmalloc'? [-Werror=implicit-function-declaration] new_pages = vmalloc(new_array_size * sizeof(void *)); ^~~~~~~ kvmalloc drivers/base/firmware_loader/fallback.c:405:13: warning: assignment makes pointer from integer without a cast [-Wint-conversion] new_pages = vmalloc(new_array_size * sizeof(void *)); ^ Signed-off-by: Stephen Rothwell Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/fallback.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c index 0a8ec7fec585..d231bbcb95d7 100644 --- a/drivers/base/firmware_loader/fallback.c +++ b/drivers/base/firmware_loader/fallback.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "fallback.h" #include "firmware.h" -- cgit v1.2.3 From c6263a48459934c9d07153e6d7f0fa43462d8dd2 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Wed, 21 Mar 2018 15:34:28 -0700 Subject: firmware: fix typo on pr_info_once() when ignore_sysfs_fallback is used When the sysctl knob is used ignore the fallback mechanism we pr_info_once() to ensure its noted the knob was used. The print incorrectly states its a debugfs knob, its a sysctl knob, so correct this typo. Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/fallback.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c index d231bbcb95d7..31b5015b59fe 100644 --- a/drivers/base/firmware_loader/fallback.c +++ b/drivers/base/firmware_loader/fallback.c @@ -652,7 +652,7 @@ static bool fw_force_sysfs_fallback(unsigned int opt_flags) static bool fw_run_sysfs_fallback(unsigned int opt_flags) { if (fw_fallback_config.ignore_sysfs_fallback) { - pr_info_once("Ignoring firmware sysfs fallback due to debugfs knob\n"); + pr_info_once("Ignoring firmware sysfs fallback due to sysctl knob\n"); return false; } -- cgit v1.2.3 From 5d42c96e1cf98bdfea18e7d32e5f6cf75aac93b9 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Wed, 21 Mar 2018 15:34:29 -0700 Subject: firmware: add firmware_request_cache() to help with cache on reboot Some devices have an optimization in place to enable the firmware to be retaineed during a system reboot, so after reboot the device can skip requesting and loading the firmware. This can save up to 1s in load time. The mt7601u 802.11 device happens to be such a device. When these devices retain the firmware on a reboot and then suspend they can miss looking for the firmware on resume. To help with this we need a way to cache the firmware when such an optimization has taken place. Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- .../driver-api/firmware/request_firmware.rst | 14 +++++++++++++ drivers/base/firmware_loader/main.c | 24 ++++++++++++++++++++++ include/linux/firmware.h | 3 +++ 3 files changed, 41 insertions(+) diff --git a/Documentation/driver-api/firmware/request_firmware.rst b/Documentation/driver-api/firmware/request_firmware.rst index cc0aea880824..cf4516dfbf96 100644 --- a/Documentation/driver-api/firmware/request_firmware.rst +++ b/Documentation/driver-api/firmware/request_firmware.rst @@ -44,6 +44,20 @@ request_firmware_nowait .. kernel-doc:: drivers/base/firmware_class.c :functions: request_firmware_nowait +Special optimizations on reboot +=============================== + +Some devices have an optimization in place to enable the firmware to be +retained during system reboot. When such optimizations are used the driver +author must ensure the firmware is still available on resume from suspend, +this can be done with firmware_request_cache() insted of requesting for the +firmare to be loaded. + +firmware_request_cache() +----------------------- +.. kernel-doc:: drivers/base/firmware_class.c + :functions: firmware_request_cache + request firmware API expected driver use ======================================== diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 2913bb0e5e7b..eb34089e4299 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -656,6 +656,30 @@ int request_firmware_direct(const struct firmware **firmware_p, } EXPORT_SYMBOL_GPL(request_firmware_direct); +/** + * firmware_request_cache: - cache firmware for suspend so resume can use it + * @name: name of firmware file + * @device: device for which firmware should be cached for + * + * There are some devices with an optimization that enables the device to not + * require loading firmware on system reboot. This optimization may still + * require the firmware present on resume from suspend. This routine can be + * used to ensure the firmware is present on resume from suspend in these + * situations. This helper is not compatible with drivers which use + * request_firmware_into_buf() or request_firmware_nowait() with no uevent set. + **/ +int firmware_request_cache(struct device *device, const char *name) +{ + int ret; + + mutex_lock(&fw_lock); + ret = fw_add_devm_name(device, name); + mutex_unlock(&fw_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(firmware_request_cache); + /** * request_firmware_into_buf - load firmware into a previously allocated buffer * @firmware_p: pointer to firmware image diff --git a/include/linux/firmware.h b/include/linux/firmware.h index d4508080348d..41050417cafb 100644 --- a/include/linux/firmware.h +++ b/include/linux/firmware.h @@ -85,4 +85,7 @@ static inline int request_firmware_into_buf(const struct firmware **firmware_p, } #endif + +int firmware_request_cache(struct device *device, const char *name); + #endif -- cgit v1.2.3 From d723522b0be497dc836b759552a1c97a814d9e86 Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Wed, 21 Mar 2018 15:34:30 -0700 Subject: mt7601u: use firmware_request_cache() to address cache on reboot request_firmware_cache() will ensure the firmware is available on resume from suspend if on reboot the device retains the firmware. This optimization is in place given otherwise on reboot we have to reload the firmware, the opmization saves us about max 1s, minimum 10ms. Cantabile has reported back this fixes his woes with both suspend and hibernation. Reported-by: Cantabile Tested-by: Cantabile Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/net/wireless/mediatek/mt7601u/mcu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt7601u/mcu.c b/drivers/net/wireless/mediatek/mt7601u/mcu.c index 65a8004418ea..8e2e1e571878 100644 --- a/drivers/net/wireless/mediatek/mt7601u/mcu.c +++ b/drivers/net/wireless/mediatek/mt7601u/mcu.c @@ -421,7 +421,7 @@ static int mt7601u_load_firmware(struct mt7601u_dev *dev) MT_USB_DMA_CFG_TX_BULK_EN)); if (firmware_running(dev)) - return 0; + return firmware_request_cache(dev->dev, MT7601U_FIRMWARE); ret = request_firmware(&fw, MT7601U_FIRMWARE, dev->dev); if (ret) -- cgit v1.2.3 From 1fe56e0cafd7e4cf26f3582aad0c7705fceff498 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Thu, 15 Mar 2018 10:55:25 +0100 Subject: drivers: base: remove check for callback in coredump_store() The check for the .coredump() callback in coredump_store() is redundant. It is already assured the device driver implements the callback upon creating the coredump sysfs entry. Signed-off-by: Arend van Spriel Signed-off-by: Greg Kroah-Hartman --- drivers/base/dd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/base/dd.c b/drivers/base/dd.c index de6fd092bf2f..c9f54089429b 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -292,8 +292,7 @@ static ssize_t coredump_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { device_lock(dev); - if (dev->driver->coredump) - dev->driver->coredump(dev); + dev->driver->coredump(dev); device_unlock(dev); return count; -- cgit v1.2.3