diff options
Diffstat (limited to 'drivers/base/firmware_class.c')
| -rw-r--r-- | drivers/base/firmware_class.c | 46 | 
1 files changed, 43 insertions, 3 deletions
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 01e21037d8f..d7872b96019 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;  		} @@ -853,8 +884,15 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,  		goto err_del_dev;  	} +	mutex_lock(&fw_lock); +	list_add(&buf->pending_list, &pending_fw_head); +	mutex_unlock(&fw_lock); +  	retval = device_create_file(f_dev, &dev_attr_loading);  	if (retval) { +		mutex_lock(&fw_lock); +		list_del_init(&buf->pending_list); +		mutex_unlock(&fw_lock);  		dev_err(f_dev, "%s: device_create_file failed\n", __func__);  		goto err_del_bin_attr;  	} @@ -1526,6 +1564,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 +1578,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  }  |