Skip to content

Commit

Permalink
firmware: refactor loading status
Browse files Browse the repository at this point in the history
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: Daniel Wagner <daniel.wagner@bmw-carit.de>
Acked-by: Luis R. Rodriguez <mcgrof@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Daniel Wagner authored and gregkh committed Nov 29, 2016
1 parent 2e700f8 commit f52cc37
Showing 1 changed file with 93 additions and 34 deletions.
127 changes: 93 additions & 34 deletions drivers/base/firmware_class.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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)
Expand All @@ -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 */
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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 */
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit f52cc37

Please sign in to comment.