diff options
| author | Takashi Iwai <tiwai@suse.de> | 2013-05-22 18:28:38 +0200 | 
|---|---|---|
| committer | Doug Zobel <dzobel1@motorola.com> | 2014-11-17 09:12:16 -0600 | 
| commit | 70f0e2337725fd14899a6b76c6aae2dbda8f6ba4 (patch) | |
| tree | e68eba9ed571f0be68d7637ec76d1efbd34f2007 | |
| parent | da1f2d42974e2f72cb0e53cf60e48254ed00f741 (diff) | |
| download | olio-linux-3.10-70f0e2337725fd14899a6b76c6aae2dbda8f6ba4.tar.xz olio-linux-3.10-70f0e2337725fd14899a6b76c6aae2dbda8f6ba4.zip  | |
firmware: Avoid deadlock of usermodehelper lock at shutdown
When a system goes to reboot/shutdown, it tries to disable the
usermode helper via usermodehelper_disable().  This might be blocked
when a driver tries to load a firmware beforehand and it's stuck by
some reason.  For example, dell_rbu driver loads the firmware in
non-hotplug mode and waits for user-space clearing the loading sysfs
flag.  If user-space doesn't clear the flag, it waits forever, thus
blocks the reboot, too.
As a workaround, in this patch, the firmware class driver registers a
reboot notifier so that it can abort all pending f/w bufs before
issuing usermodehelper_disable().
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Acked-by: Ming Lei <ming.lei@canonical.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Conflicts:
	drivers/base/firmware_class.c
Change-Id: I7ff6c198cd34090e55845b9d4035b1e5dc86226b
| -rw-r--r-- | drivers/base/firmware_class.c | 43 | 
1 files changed, 40 insertions, 3 deletions
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 01e21037d8f..55d682e6ece 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -27,6 +27,7 @@  #include <linux/pm.h>  #include <linux/suspend.h>  #include <linux/syscore_ops.h> +#include <linux/reboot.h>  #include <generated/utsrelease.h> @@ -130,6 +131,7 @@ struct firmware_buf {  	struct page **pages;  	int nr_pages;  	int page_array_size; +	struct list_head pending_list;  #endif  	char fw_id[];  }; @@ -171,6 +173,9 @@ static struct firmware_buf *__allocate_fw_buf(const char *fw_name,  	strcpy(buf->fw_id, fw_name);  	buf->fwc = fwc;  	init_completion(&buf->completion); +#ifdef CONFIG_FW_LOADER_USER_HELPER +	INIT_LIST_HEAD(&buf->pending_list); +#endif  	pr_debug("%s: fw-%s buf=%p\n", __func__, fw_name, buf); @@ -446,10 +451,8 @@ static struct firmware_priv *to_firmware_priv(struct device *dev)  	return container_of(dev, struct firmware_priv, dev);  } -static void fw_load_abort(struct firmware_priv *fw_priv) +static void __fw_load_abort(struct firmware_buf *buf)  { -	struct firmware_buf *buf = fw_priv->buf; -  	/*  	 * There is a small window in which user can write to 'loading'  	 * between loading done and disappearance of 'loading' @@ -457,8 +460,16 @@ static void fw_load_abort(struct firmware_priv *fw_priv)  	if (test_bit(FW_STATUS_DONE, &buf->status))  		return; +	list_del_init(&buf->pending_list);  	set_bit(FW_STATUS_ABORT, &buf->status);  	complete_all(&buf->completion); +} + +static void fw_load_abort(struct firmware_priv *fw_priv) +{ +	struct firmware_buf *buf = fw_priv->buf; + +	__fw_load_abort(buf);  	/* avoid user action after loading abort */  	fw_priv->buf = NULL; @@ -467,6 +478,25 @@ static void fw_load_abort(struct firmware_priv *fw_priv)  #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 */ +static int fw_shutdown_notify(struct notifier_block *unused1, +			      unsigned long unused2, void *unused3) +{ +	mutex_lock(&fw_lock); +	while (!list_empty(&pending_fw_head)) +		__fw_load_abort(list_first_entry(&pending_fw_head, +					       struct firmware_buf, +					       pending_list)); +	mutex_unlock(&fw_lock); +	return NOTIFY_DONE; +} + +static struct notifier_block fw_shutdown_nb = { +	.notifier_call = fw_shutdown_notify, +}; +  static ssize_t firmware_timeout_show(struct class *class,  				     struct class_attribute *attr,  				     char *buf) @@ -619,6 +649,7 @@ static ssize_t firmware_loading_store(struct device *dev,  			 * is completed.  			 * */  			fw_map_pages_buf(fw_buf); +			list_del_init(&fw_buf->pending_list);  			complete_all(&fw_buf->completion);  			break;  		} @@ -868,6 +899,10 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,  		kobject_uevent(&fw_priv->dev.kobj, KOBJ_ADD);  	} +	mutex_lock(&fw_lock); +	list_add(&buf->pending_list, &pending_fw_head); +	mutex_unlock(&fw_lock); +  	wait_for_completion(&buf->completion);  	cancel_delayed_work_sync(&fw_priv->timeout_work); @@ -1526,6 +1561,7 @@ static int __init firmware_class_init(void)  {  	fw_cache_init();  #ifdef CONFIG_FW_LOADER_USER_HELPER +	register_reboot_notifier(&fw_shutdown_nb);  	return class_register(&firmware_class);  #else  	return 0; @@ -1539,6 +1575,7 @@ static void __exit firmware_class_exit(void)  	unregister_pm_notifier(&fw_cache.pm_notify);  #endif  #ifdef CONFIG_FW_LOADER_USER_HELPER +	unregister_reboot_notifier(&fw_shutdown_nb);  	class_unregister(&firmware_class);  #endif  }  |