diff options
Diffstat (limited to 'drivers/base/firmware_class.c')
| -rw-r--r-- | drivers/base/firmware_class.c | 550 | 
1 files changed, 299 insertions, 251 deletions
| diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index b392b353be39..4a223fedcd73 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -88,11 +88,6 @@ enum {  	FW_STATUS_ABORT,  }; -enum fw_buf_fmt { -	VMALLOC_BUF,	/* used in direct loading */ -	PAGE_BUF,	/* used in loading via userspace */ -}; -  static int loading_timeout = 60;	/* In seconds */  static inline long firmware_loading_timeout(void) @@ -128,12 +123,14 @@ struct firmware_buf {  	struct completion completion;  	struct firmware_cache *fwc;  	unsigned long status; -	enum fw_buf_fmt fmt;  	void *data;  	size_t size; +#ifdef CONFIG_FW_LOADER_USER_HELPER +	bool is_paged_buf;  	struct page **pages;  	int nr_pages;  	int page_array_size; +#endif  	char fw_id[];  }; @@ -142,14 +139,6 @@ struct fw_cache_entry {  	char name[];  }; -struct firmware_priv { -	struct delayed_work timeout_work; -	bool nowait; -	struct device dev; -	struct firmware_buf *buf; -	struct firmware *fw; -}; -  struct fw_name_devm {  	unsigned long magic;  	char name[]; @@ -182,7 +171,6 @@ static struct firmware_buf *__allocate_fw_buf(const char *fw_name,  	strcpy(buf->fw_id, fw_name);  	buf->fwc = fwc;  	init_completion(&buf->completion); -	buf->fmt = VMALLOC_BUF;  	pr_debug("%s: fw-%s buf=%p\n", __func__, fw_name, buf); @@ -240,7 +228,6 @@ static void __fw_free_buf(struct kref *ref)  {  	struct firmware_buf *buf = to_fwbuf(ref);  	struct firmware_cache *fwc = buf->fwc; -	int i;  	pr_debug("%s: fw-%s buf=%p data=%p size=%u\n",  		 __func__, buf->fw_id, buf, buf->data, @@ -249,13 +236,15 @@ static void __fw_free_buf(struct kref *ref)  	list_del(&buf->list);  	spin_unlock(&fwc->lock); - -	if (buf->fmt == PAGE_BUF) { +#ifdef CONFIG_FW_LOADER_USER_HELPER +	if (buf->is_paged_buf) { +		int i;  		vunmap(buf->data);  		for (i = 0; i < buf->nr_pages; i++)  			__free_page(buf->pages[i]);  		kfree(buf->pages);  	} else +#endif  		vfree(buf->data);  	kfree(buf);  } @@ -319,7 +308,8 @@ static bool fw_read_file_contents(struct file *file, struct firmware_buf *fw_buf  	return true;  } -static bool fw_get_filesystem_firmware(struct firmware_buf *buf) +static bool fw_get_filesystem_firmware(struct device *device, +				       struct firmware_buf *buf)  {  	int i;  	bool success = false; @@ -343,9 +333,114 @@ static bool fw_get_filesystem_firmware(struct firmware_buf *buf)  			break;  	}  	__putname(path); + +	if (success) { +		dev_dbg(device, "firmware: direct-loading firmware %s\n", +			buf->fw_id); +		mutex_lock(&fw_lock); +		set_bit(FW_STATUS_DONE, &buf->status); +		complete_all(&buf->completion); +		mutex_unlock(&fw_lock); +	} +  	return success;  } +/* firmware holds the ownership of pages */ +static void firmware_free_data(const struct firmware *fw) +{ +	/* Loaded directly? */ +	if (!fw->priv) { +		vfree(fw->data); +		return; +	} +	fw_free_buf(fw->priv); +} + +/* store the pages buffer info firmware from buf */ +static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw) +{ +	fw->priv = buf; +#ifdef CONFIG_FW_LOADER_USER_HELPER +	fw->pages = buf->pages; +#endif +	fw->size = buf->size; +	fw->data = buf->data; + +	pr_debug("%s: fw-%s buf=%p data=%p size=%u\n", +		 __func__, buf->fw_id, buf, buf->data, +		 (unsigned int)buf->size); +} + +#ifdef CONFIG_PM_SLEEP +static void fw_name_devm_release(struct device *dev, void *res) +{ +	struct fw_name_devm *fwn = res; + +	if (fwn->magic == (unsigned long)&fw_cache) +		pr_debug("%s: fw_name-%s devm-%p released\n", +				__func__, fwn->name, res); +} + +static int fw_devm_match(struct device *dev, void *res, +		void *match_data) +{ +	struct fw_name_devm *fwn = res; + +	return (fwn->magic == (unsigned long)&fw_cache) && +		!strcmp(fwn->name, match_data); +} + +static struct fw_name_devm *fw_find_devm_name(struct device *dev, +		const char *name) +{ +	struct fw_name_devm *fwn; + +	fwn = devres_find(dev, fw_name_devm_release, +			  fw_devm_match, (void *)name); +	return fwn; +} + +/* add firmware name into devres list */ +static int fw_add_devm_name(struct device *dev, const char *name) +{ +	struct fw_name_devm *fwn; + +	fwn = fw_find_devm_name(dev, name); +	if (fwn) +		return 1; + +	fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm) + +			   strlen(name) + 1, GFP_KERNEL); +	if (!fwn) +		return -ENOMEM; + +	fwn->magic = (unsigned long)&fw_cache; +	strcpy(fwn->name, name); +	devres_add(dev, fwn); + +	return 0; +} +#else +static int fw_add_devm_name(struct device *dev, const char *name) +{ +	return 0; +} +#endif + + +/* + * user-mode helper code + */ +#ifdef CONFIG_FW_LOADER_USER_HELPER +struct firmware_priv { +	struct delayed_work timeout_work; +	bool nowait; +	struct device dev; +	struct firmware_buf *buf; +	struct firmware *fw; +}; +  static struct firmware_priv *to_firmware_priv(struct device *dev)  {  	return container_of(dev, struct firmware_priv, dev); @@ -359,6 +454,9 @@ static void fw_load_abort(struct firmware_priv *fw_priv)  	complete_all(&buf->completion);  } +#define is_fw_load_aborted(buf)	\ +	test_bit(FW_STATUS_ABORT, &(buf)->status) +  static ssize_t firmware_timeout_show(struct class *class,  				     struct class_attribute *attr,  				     char *buf) @@ -435,17 +533,6 @@ static ssize_t firmware_loading_show(struct device *dev,  	return sprintf(buf, "%d\n", loading);  } -/* firmware holds the ownership of pages */ -static void firmware_free_data(const struct firmware *fw) -{ -	/* Loaded directly? */ -	if (!fw->priv) { -		vfree(fw->data); -		return; -	} -	fw_free_buf(fw->priv); -} -  /* Some architectures don't have PAGE_KERNEL_RO */  #ifndef PAGE_KERNEL_RO  #define PAGE_KERNEL_RO PAGE_KERNEL @@ -454,7 +541,7 @@ static void firmware_free_data(const struct firmware *fw)  /* one pages buffer should be mapped/unmapped only once */  static int fw_map_pages_buf(struct firmware_buf *buf)  { -	if (buf->fmt != PAGE_BUF) +	if (!buf->is_paged_buf)  		return 0;  	if (buf->data) @@ -727,171 +814,16 @@ exit:  	return fw_priv;  } -/* store the pages buffer info firmware from buf */ -static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw) -{ -	fw->priv = buf; -	fw->pages = buf->pages; -	fw->size = buf->size; -	fw->data = buf->data; - -	pr_debug("%s: fw-%s buf=%p data=%p size=%u\n", -		 __func__, buf->fw_id, buf, buf->data, -		 (unsigned int)buf->size); -} - -#ifdef CONFIG_PM_SLEEP -static void fw_name_devm_release(struct device *dev, void *res) -{ -	struct fw_name_devm *fwn = res; - -	if (fwn->magic == (unsigned long)&fw_cache) -		pr_debug("%s: fw_name-%s devm-%p released\n", -				__func__, fwn->name, res); -} - -static int fw_devm_match(struct device *dev, void *res, -		void *match_data) -{ -	struct fw_name_devm *fwn = res; - -	return (fwn->magic == (unsigned long)&fw_cache) && -		!strcmp(fwn->name, match_data); -} - -static struct fw_name_devm *fw_find_devm_name(struct device *dev, -		const char *name) -{ -	struct fw_name_devm *fwn; - -	fwn = devres_find(dev, fw_name_devm_release, -			  fw_devm_match, (void *)name); -	return fwn; -} - -/* add firmware name into devres list */ -static int fw_add_devm_name(struct device *dev, const char *name) -{ -	struct fw_name_devm *fwn; - -	fwn = fw_find_devm_name(dev, name); -	if (fwn) -		return 1; - -	fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm) + -			   strlen(name) + 1, GFP_KERNEL); -	if (!fwn) -		return -ENOMEM; - -	fwn->magic = (unsigned long)&fw_cache; -	strcpy(fwn->name, name); -	devres_add(dev, fwn); - -	return 0; -} -#else -static int fw_add_devm_name(struct device *dev, const char *name) -{ -	return 0; -} -#endif - -static void _request_firmware_cleanup(const struct firmware **firmware_p) -{ -	release_firmware(*firmware_p); -	*firmware_p = NULL; -} - -static struct firmware_priv * -_request_firmware_prepare(const struct firmware **firmware_p, const char *name, -			  struct device *device, bool uevent, bool nowait) -{ -	struct firmware *firmware; -	struct firmware_priv *fw_priv = NULL; -	struct firmware_buf *buf; -	int ret; - -	if (!firmware_p) -		return ERR_PTR(-EINVAL); - -	*firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); -	if (!firmware) { -		dev_err(device, "%s: kmalloc(struct firmware) failed\n", -			__func__); -		return ERR_PTR(-ENOMEM); -	} - -	if (fw_get_builtin_firmware(firmware, name)) { -		dev_dbg(device, "firmware: using built-in firmware %s\n", name); -		return NULL; -	} - -	ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf); -	if (!ret) -		fw_priv = fw_create_instance(firmware, name, device, -				uevent, nowait); - -	if (IS_ERR(fw_priv) || ret < 0) { -		kfree(firmware); -		*firmware_p = NULL; -		return ERR_PTR(-ENOMEM); -	} else if (fw_priv) { -		fw_priv->buf = buf; - -		/* -		 * bind with 'buf' now to avoid warning in failure path -		 * of requesting firmware. -		 */ -		firmware->priv = buf; -		return fw_priv; -	} - -	/* share the cached buf, which is inprogessing or completed */ - check_status: -	mutex_lock(&fw_lock); -	if (test_bit(FW_STATUS_ABORT, &buf->status)) { -		fw_priv = ERR_PTR(-ENOENT); -		firmware->priv = buf; -		_request_firmware_cleanup(firmware_p); -		goto exit; -	} else if (test_bit(FW_STATUS_DONE, &buf->status)) { -		fw_priv = NULL; -		fw_set_page_data(buf, firmware); -		goto exit; -	} -	mutex_unlock(&fw_lock); -	wait_for_completion(&buf->completion); -	goto check_status; - -exit: -	mutex_unlock(&fw_lock); -	return fw_priv; -} - +/* load a firmware via user helper */  static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,  				  long timeout)  {  	int retval = 0;  	struct device *f_dev = &fw_priv->dev;  	struct firmware_buf *buf = fw_priv->buf; -	struct firmware_cache *fwc = &fw_cache; -	int direct_load = 0; - -	/* try direct loading from fs first */ -	if (fw_get_filesystem_firmware(buf)) { -		dev_dbg(f_dev->parent, "firmware: direct-loading" -			" firmware %s\n", buf->fw_id); - -		mutex_lock(&fw_lock); -		set_bit(FW_STATUS_DONE, &buf->status); -		mutex_unlock(&fw_lock); -		complete_all(&buf->completion); -		direct_load = 1; -		goto handle_fw; -	}  	/* fall back on userspace loading */ -	buf->fmt = PAGE_BUF; +	buf->is_paged_buf = true;  	dev_set_uevent_suppress(f_dev, true); @@ -929,47 +861,196 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,  	cancel_delayed_work_sync(&fw_priv->timeout_work); -handle_fw: +	fw_priv->buf = NULL; + +	device_remove_file(f_dev, &dev_attr_loading); +err_del_bin_attr: +	device_remove_bin_file(f_dev, &firmware_attr_data); +err_del_dev: +	device_del(f_dev); +err_put_dev: +	put_device(f_dev); +	return retval; +} + +static int fw_load_from_user_helper(struct firmware *firmware, +				    const char *name, struct device *device, +				    bool uevent, bool nowait, long timeout) +{ +	struct firmware_priv *fw_priv; + +	fw_priv = fw_create_instance(firmware, name, device, uevent, nowait); +	if (IS_ERR(fw_priv)) +		return PTR_ERR(fw_priv); + +	fw_priv->buf = firmware->priv; +	return _request_firmware_load(fw_priv, uevent, timeout); +} +#else /* CONFIG_FW_LOADER_USER_HELPER */ +static inline int +fw_load_from_user_helper(struct firmware *firmware, const char *name, +			 struct device *device, bool uevent, bool nowait, +			 long timeout) +{ +	return -ENOENT; +} + +/* No abort during direct loading */ +#define is_fw_load_aborted(buf) false + +#endif /* CONFIG_FW_LOADER_USER_HELPER */ + + +/* wait until the shared firmware_buf becomes ready (or error) */ +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)) { +			ret = -ENOENT; +			break; +		} +		mutex_unlock(&fw_lock); +		wait_for_completion(&buf->completion); +		mutex_lock(&fw_lock); +	} +	mutex_unlock(&fw_lock); +	return ret; +} + +/* prepare firmware and firmware_buf structs; + * return 0 if a firmware is already assigned, 1 if need to load one, + * or a negative error code + */ +static int +_request_firmware_prepare(struct firmware **firmware_p, const char *name, +			  struct device *device) +{ +	struct firmware *firmware; +	struct firmware_buf *buf; +	int ret; + +	*firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); +	if (!firmware) { +		dev_err(device, "%s: kmalloc(struct firmware) failed\n", +			__func__); +		return -ENOMEM; +	} + +	if (fw_get_builtin_firmware(firmware, name)) { +		dev_dbg(device, "firmware: using built-in firmware %s\n", name); +		return 0; /* assigned */ +	} + +	ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf); + +	/* +	 * bind with 'buf' now to avoid warning in failure path +	 * of requesting firmware. +	 */ +	firmware->priv = buf; + +	if (ret > 0) { +		ret = sync_cached_firmware_buf(buf); +		if (!ret) { +			fw_set_page_data(buf, firmware); +			return 0; /* assigned */ +		} +	} + +	if (ret < 0) +		return ret; +	return 1; /* need to load */ +} + +static int assign_firmware_buf(struct firmware *fw, struct device *device) +{ +	struct firmware_buf *buf = fw->priv; +  	mutex_lock(&fw_lock); -	if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status)) -		retval = -ENOENT; +	if (!buf->size || is_fw_load_aborted(buf)) { +		mutex_unlock(&fw_lock); +		return -ENOENT; +	}  	/*  	 * add firmware name into devres list so that we can auto cache  	 * and uncache firmware for device.  	 * -	 * f_dev->parent may has been deleted already, but the problem +	 * device may has been deleted already, but the problem  	 * should be fixed in devres or driver core.  	 */ -	if (!retval && f_dev->parent) -		fw_add_devm_name(f_dev->parent, buf->fw_id); +	if (device) +		fw_add_devm_name(device, buf->fw_id);  	/*  	 * After caching firmware image is started, let it piggyback  	 * on request firmware.  	 */ -	if (!retval && fwc->state == FW_LOADER_START_CACHE) { +	if (buf->fwc->state == FW_LOADER_START_CACHE) {  		if (fw_cache_piggyback_on_request(buf->fw_id))  			kref_get(&buf->ref);  	}  	/* pass the pages buffer to driver at the last minute */ -	fw_set_page_data(buf, fw_priv->fw); - -	fw_priv->buf = NULL; +	fw_set_page_data(buf, fw);  	mutex_unlock(&fw_lock); +	return 0; +} -	if (direct_load) -		goto err_put_dev; +/* called from request_firmware() and request_firmware_work_func() */ +static int +_request_firmware(const struct firmware **firmware_p, const char *name, +		  struct device *device, bool uevent, bool nowait) +{ +	struct firmware *fw; +	long timeout; +	int ret; -	device_remove_file(f_dev, &dev_attr_loading); -err_del_bin_attr: -	device_remove_bin_file(f_dev, &firmware_attr_data); -err_del_dev: -	device_del(f_dev); -err_put_dev: -	put_device(f_dev); -	return retval; +	if (!firmware_p) +		return -EINVAL; + +	ret = _request_firmware_prepare(&fw, name, device); +	if (ret <= 0) /* error or already assigned */ +		goto out; + +	ret = 0; +	timeout = firmware_loading_timeout(); +	if (nowait) { +		timeout = usermodehelper_read_lock_wait(timeout); +		if (!timeout) { +			dev_dbg(device, "firmware: %s loading timed out\n", +				name); +			ret = -EBUSY; +			goto out; +		} +	} else { +		ret = usermodehelper_read_trylock(); +		if (WARN_ON(ret)) { +			dev_err(device, "firmware: %s will not be loaded\n", +				name); +			goto out; +		} +	} + +	if (!fw_get_filesystem_firmware(device, fw->priv)) +		ret = fw_load_from_user_helper(fw, name, device, +					       uevent, nowait, timeout); +	if (!ret) +		ret = assign_firmware_buf(fw, device); + +	usermodehelper_read_unlock(); + + out: +	if (ret < 0) { +		release_firmware(fw); +		fw = NULL; +	} + +	*firmware_p = fw; +	return ret;  }  /** @@ -996,26 +1077,7 @@ int  request_firmware(const struct firmware **firmware_p, const char *name,                   struct device *device)  { -	struct firmware_priv *fw_priv; -	int ret; - -	fw_priv = _request_firmware_prepare(firmware_p, name, device, true, -					    false); -	if (IS_ERR_OR_NULL(fw_priv)) -		return PTR_RET(fw_priv); - -	ret = usermodehelper_read_trylock(); -	if (WARN_ON(ret)) { -		dev_err(device, "firmware: %s will not be loaded\n", name); -	} else { -		ret = _request_firmware_load(fw_priv, true, -					firmware_loading_timeout()); -		usermodehelper_read_unlock(); -	} -	if (ret) -		_request_firmware_cleanup(firmware_p); - -	return ret; +	return _request_firmware(firmware_p, name, device, true, false);  }  /** @@ -1046,33 +1108,13 @@ static void request_firmware_work_func(struct work_struct *work)  {  	struct firmware_work *fw_work;  	const struct firmware *fw; -	struct firmware_priv *fw_priv; -	long timeout; -	int ret;  	fw_work = container_of(work, struct firmware_work, work); -	fw_priv = _request_firmware_prepare(&fw, fw_work->name, fw_work->device, -			fw_work->uevent, true); -	if (IS_ERR_OR_NULL(fw_priv)) { -		ret = PTR_RET(fw_priv); -		goto out; -	} - -	timeout = usermodehelper_read_lock_wait(firmware_loading_timeout()); -	if (timeout) { -		ret = _request_firmware_load(fw_priv, fw_work->uevent, timeout); -		usermodehelper_read_unlock(); -	} else { -		dev_dbg(fw_work->device, "firmware: %s loading timed out\n", -			fw_work->name); -		ret = -EAGAIN; -	} -	if (ret) -		_request_firmware_cleanup(&fw); - out: +	_request_firmware(&fw, fw_work->name, fw_work->device, +			  fw_work->uevent, true);  	fw_work->cont(fw, fw_work->context); -	put_device(fw_work->device); +	put_device(fw_work->device); /* taken in request_firmware_nowait() */  	module_put(fw_work->module);  	kfree(fw_work); @@ -1474,7 +1516,11 @@ static void __init fw_cache_init(void)  static int __init firmware_class_init(void)  {  	fw_cache_init(); +#ifdef CONFIG_FW_LOADER_USER_HELPER  	return class_register(&firmware_class); +#else +	return 0; +#endif  }  static void __exit firmware_class_exit(void) @@ -1483,7 +1529,9 @@ static void __exit firmware_class_exit(void)  	unregister_syscore_ops(&fw_syscore_ops);  	unregister_pm_notifier(&fw_cache.pm_notify);  #endif +#ifdef CONFIG_FW_LOADER_USER_HELPER  	class_unregister(&firmware_class); +#endif  }  fs_initcall(firmware_class_init); |