diff options
Diffstat (limited to 'drivers/base/firmware_loader/firmware.h')
-rw-r--r-- | drivers/base/firmware_loader/firmware.h | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/drivers/base/firmware_loader/firmware.h b/drivers/base/firmware_loader/firmware.h new file mode 100644 index 000000000..e2000b4ad --- /dev/null +++ b/drivers/base/firmware_loader/firmware.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __FIRMWARE_LOADER_H +#define __FIRMWARE_LOADER_H + +#include <linux/bitops.h> +#include <linux/firmware.h> +#include <linux/types.h> +#include <linux/kref.h> +#include <linux/list.h> +#include <linux/completion.h> + +#include <generated/utsrelease.h> + +/** + * enum fw_opt - options to control firmware loading behaviour + * + * @FW_OPT_UEVENT: Enables the fallback mechanism to send a kobject uevent + * when the firmware is not found. Userspace is in charge to load the + * firmware using the sysfs loading facility. + * @FW_OPT_NOWAIT: Used to describe the firmware request is asynchronous. + * @FW_OPT_USERHELPER: Enable the fallback mechanism, in case the direct + * filesystem lookup fails at finding the firmware. For details refer to + * firmware_fallback_sysfs(). + * @FW_OPT_NO_WARN: Quiet, avoid printing warning messages. + * @FW_OPT_NOCACHE: Disables firmware caching. Firmware caching is used to + * cache the firmware upon suspend, so that upon resume races against the + * firmware file lookup on storage is avoided. Used for calls where the + * file may be too big, or where the driver takes charge of its own + * firmware caching mechanism. + * @FW_OPT_NOFALLBACK: Disable the fallback mechanism. Takes precedence over + * &FW_OPT_UEVENT and &FW_OPT_USERHELPER. + */ +enum fw_opt { + FW_OPT_UEVENT = BIT(0), + FW_OPT_NOWAIT = BIT(1), + FW_OPT_USERHELPER = BIT(2), + FW_OPT_NO_WARN = BIT(3), + FW_OPT_NOCACHE = BIT(4), + FW_OPT_NOFALLBACK = BIT(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) { +#ifdef CONFIG_FW_LOADER_USER_HELPER + /* + * Doing this here ensures that the fw_priv is deleted from + * the pending list in all abort/done paths. + */ + list_del_init(&fw_priv->pending_list); +#endif + 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, + enum fw_opt opt_flags); + +#endif /* __FIRMWARE_LOADER_H */ |