Commit f52cc379 authored by Daniel Wagner's avatar Daniel Wagner Committed by Greg Kroah-Hartman

firmware: refactor loading status

The firmware loader tracks the current state of the loading process
via unsigned long status and a completion in struct
firmware_buf. Instead of open code tracking the state, introduce data
structure which encapsulate the state tracking and synchronization.

While at it also separate UHM states from direct loading states, e.g.
the loading_timeout is only defined when CONFIG_FW_LOADER_USER_HELPER.

Cc: Ming Lei <ming.lei@canonical.com>
Signed-off-by: default avatarDaniel Wagner <daniel.wagner@bmw-carit.de>
Acked-by: default avatarLuis R. Rodriguez <mcgrof@kernel.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 2e700f8d
......@@ -91,10 +91,11 @@ static inline bool fw_is_builtin_firmware(const struct firmware *fw)
}
#endif
enum {
enum fw_status {
FW_STATUS_UNKNOWN,
FW_STATUS_LOADING,
FW_STATUS_DONE,
FW_STATUS_ABORT,
FW_STATUS_ABORTED,
};
static int loading_timeout = 60; /* In seconds */
......@@ -104,6 +105,76 @@ static inline long firmware_loading_timeout(void)
return loading_timeout > 0 ? loading_timeout * HZ : MAX_JIFFY_OFFSET;
}
/*
* 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;
unsigned long status;
};
static void fw_state_init(struct fw_state *fw_st)
{
init_completion(&fw_st->completion);
fw_st->status = FW_STATUS_UNKNOWN;
}
static int __fw_state_check(struct fw_state *fw_st, enum fw_status status)
{
return test_bit(status, &fw_st->status);
}
static long __fw_state_wait_common(struct fw_state *fw_st, long timeout)
{
long ret;
ret = wait_for_completion_interruptible_timeout(&fw_st->completion,
timeout);
if (ret != 0 && test_bit(FW_STATUS_ABORTED, &fw_st->status))
return -ENOENT;
return ret;
}
static void __fw_state_set(struct fw_state *fw_st,
enum fw_status status)
{
set_bit(status, &fw_st->status);
if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED) {
clear_bit(FW_STATUS_LOADING, &fw_st->status);
complete_all(&fw_st->completion);
}
}
#define fw_state_start(fw_st) \
__fw_state_set(fw_st, FW_STATUS_LOADING)
#define fw_state_done(fw_st) \
__fw_state_set(fw_st, FW_STATUS_DONE)
#define fw_state_is_done(fw_st) \
__fw_state_check(fw_st, FW_STATUS_DONE)
#define fw_state_wait(fw_st) \
__fw_state_wait_common(fw_st, MAX_SCHEDULE_TIMEOUT)
#ifndef CONFIG_FW_LOADER_USER_HELPER
#define fw_state_is_aborted(fw_st) false
#else /* CONFIG_FW_LOADER_USER_HELPER */
#define fw_state_aborted(fw_st) \
__fw_state_set(fw_st, FW_STATUS_ABORTED)
#define fw_state_is_loading(fw_st) \
__fw_state_check(fw_st, FW_STATUS_LOADING)
#define fw_state_is_aborted(fw_st) \
__fw_state_check(fw_st, FW_STATUS_ABORTED)
#define fw_state_wait_timeout(fw_st, timeout) \
__fw_state_wait_common(fw_st, timeout)
#endif /* CONFIG_FW_LOADER_USER_HELPER */
/* firmware behavior options */
#define FW_OPT_UEVENT (1U << 0)
#define FW_OPT_NOWAIT (1U << 1)
......@@ -145,9 +216,8 @@ struct firmware_cache {
struct firmware_buf {
struct kref ref;
struct list_head list;
struct completion completion;
struct firmware_cache *fwc;
unsigned long status;
struct fw_state fw_st;
void *data;
size_t size;
size_t allocated_size;
......@@ -205,7 +275,7 @@ static struct firmware_buf *__allocate_fw_buf(const char *fw_name,
buf->fwc = fwc;
buf->data = dbuf;
buf->allocated_size = size;
init_completion(&buf->completion);
fw_state_init(&buf->fw_st);
#ifdef CONFIG_FW_LOADER_USER_HELPER
INIT_LIST_HEAD(&buf->pending_list);
#endif
......@@ -309,8 +379,7 @@ static void fw_finish_direct_load(struct device *device,
struct firmware_buf *buf)
{
mutex_lock(&fw_lock);
set_bit(FW_STATUS_DONE, &buf->status);
complete_all(&buf->completion);
fw_state_done(&buf->fw_st);
mutex_unlock(&fw_lock);
}
......@@ -478,12 +547,11 @@ static void __fw_load_abort(struct firmware_buf *buf)
* There is a small window in which user can write to 'loading'
* between loading done and disappearance of 'loading'
*/
if (test_bit(FW_STATUS_DONE, &buf->status))
if (fw_state_is_done(&buf->fw_st))
return;
list_del_init(&buf->pending_list);
set_bit(FW_STATUS_ABORT, &buf->status);
complete_all(&buf->completion);
fw_state_aborted(&buf->fw_st);
}
static void fw_load_abort(struct firmware_priv *fw_priv)
......@@ -496,9 +564,6 @@ static void fw_load_abort(struct firmware_priv *fw_priv)
fw_priv->buf = NULL;
}
#define is_fw_load_aborted(buf) \
test_bit(FW_STATUS_ABORT, &(buf)->status)
static LIST_HEAD(pending_fw_head);
/* reboot notifier for avoid deadlock with usermode_lock */
......@@ -600,7 +665,7 @@ static ssize_t firmware_loading_show(struct device *dev,
mutex_lock(&fw_lock);
if (fw_priv->buf)
loading = test_bit(FW_STATUS_LOADING, &fw_priv->buf->status);
loading = fw_state_is_loading(&fw_priv->buf->fw_st);
mutex_unlock(&fw_lock);
return sprintf(buf, "%d\n", loading);
......@@ -655,23 +720,20 @@ static ssize_t firmware_loading_store(struct device *dev,
switch (loading) {
case 1:
/* discarding any previous partial load */
if (!test_bit(FW_STATUS_DONE, &fw_buf->status)) {
if (!fw_state_is_done(&fw_buf->fw_st)) {
for (i = 0; i < fw_buf->nr_pages; i++)
__free_page(fw_buf->pages[i]);
vfree(fw_buf->pages);
fw_buf->pages = NULL;
fw_buf->page_array_size = 0;
fw_buf->nr_pages = 0;
set_bit(FW_STATUS_LOADING, &fw_buf->status);
fw_state_start(&fw_buf->fw_st);
}
break;
case 0:
if (test_bit(FW_STATUS_LOADING, &fw_buf->status)) {
if (fw_state_is_loading(&fw_buf->fw_st)) {
int rc;
set_bit(FW_STATUS_DONE, &fw_buf->status);
clear_bit(FW_STATUS_LOADING, &fw_buf->status);
/*
* Several loading requests may be pending on
* one same firmware buf, so let all requests
......@@ -693,10 +755,11 @@ static ssize_t firmware_loading_store(struct device *dev,
*/
list_del_init(&fw_buf->pending_list);
if (rc) {
set_bit(FW_STATUS_ABORT, &fw_buf->status);
fw_state_aborted(&fw_buf->fw_st);
written = rc;
} else {
fw_state_done(&fw_buf->fw_st);
}
complete_all(&fw_buf->completion);
break;
}
/* fallthrough */
......@@ -757,7 +820,7 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj,
mutex_lock(&fw_lock);
buf = fw_priv->buf;
if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) {
if (!buf || fw_state_is_done(&buf->fw_st)) {
ret_count = -ENODEV;
goto out;
}
......@@ -844,7 +907,7 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj,
mutex_lock(&fw_lock);
buf = fw_priv->buf;
if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) {
if (!buf || fw_state_is_done(&buf->fw_st)) {
retval = -ENODEV;
goto out;
}
......@@ -957,8 +1020,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv,
timeout = MAX_JIFFY_OFFSET;
}
timeout = wait_for_completion_interruptible_timeout(&buf->completion,
timeout);
timeout = fw_state_wait_timeout(&buf->fw_st, timeout);
if (timeout == -ERESTARTSYS || !timeout) {
retval = timeout;
mutex_lock(&fw_lock);
......@@ -968,7 +1030,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv,
retval = 0;
}
if (is_fw_load_aborted(buf))
if (fw_state_is_aborted(&buf->fw_st))
retval = -EAGAIN;
else if (buf->is_paged_buf && !buf->data)
retval = -ENOMEM;
......@@ -1018,9 +1080,6 @@ fw_load_from_user_helper(struct firmware *firmware, const char *name,
return -ENOENT;
}
/* No abort during direct loading */
#define is_fw_load_aborted(buf) false
#ifdef CONFIG_PM_SLEEP
static inline void kill_requests_without_uevent(void) { }
#endif
......@@ -1034,13 +1093,13 @@ static int sync_cached_firmware_buf(struct firmware_buf *buf)
int ret = 0;
mutex_lock(&fw_lock);
while (!test_bit(FW_STATUS_DONE, &buf->status)) {
if (is_fw_load_aborted(buf)) {
while (!fw_state_is_done(&buf->fw_st)) {
if (fw_state_is_aborted(&buf->fw_st)) {
ret = -ENOENT;
break;
}
mutex_unlock(&fw_lock);
ret = wait_for_completion_interruptible(&buf->completion);
ret = fw_state_wait(&buf->fw_st);
mutex_lock(&fw_lock);
}
mutex_unlock(&fw_lock);
......@@ -1098,7 +1157,7 @@ static int assign_firmware_buf(struct firmware *fw, struct device *device,
struct firmware_buf *buf = fw->priv;
mutex_lock(&fw_lock);
if (!buf->size || is_fw_load_aborted(buf)) {
if (!buf->size || fw_state_is_aborted(&buf->fw_st)) {
mutex_unlock(&fw_lock);
return -ENOENT;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment