diff options
Diffstat (limited to 'drivers/base/firmware_class.c')
| -rw-r--r-- | drivers/base/firmware_class.c | 266 | 
1 files changed, 151 insertions, 115 deletions
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 81541452887..8945f4e489e 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -36,68 +36,6 @@ MODULE_AUTHOR("Manuel Estrada Sainz");  MODULE_DESCRIPTION("Multi purpose firmware loading support");  MODULE_LICENSE("GPL"); -static const char *fw_path[] = { -	"/lib/firmware/updates/" UTS_RELEASE, -	"/lib/firmware/updates", -	"/lib/firmware/" UTS_RELEASE, -	"/lib/firmware" -}; - -/* Don't inline this: 'struct kstat' is biggish */ -static noinline long fw_file_size(struct file *file) -{ -	struct kstat st; -	if (vfs_getattr(file->f_path.mnt, file->f_path.dentry, &st)) -		return -1; -	if (!S_ISREG(st.mode)) -		return -1; -	if (st.size != (long)st.size) -		return -1; -	return st.size; -} - -static bool fw_read_file_contents(struct file *file, struct firmware *fw) -{ -	long size; -	char *buf; - -	size = fw_file_size(file); -	if (size < 0) -		return false; -	buf = vmalloc(size); -	if (!buf) -		return false; -	if (kernel_read(file, 0, buf, size) != size) { -		vfree(buf); -		return false; -	} -	fw->data = buf; -	fw->size = size; -	return true; -} - -static bool fw_get_filesystem_firmware(struct firmware *fw, const char *name) -{ -	int i; -	bool success = false; -	char *path = __getname(); - -	for (i = 0; i < ARRAY_SIZE(fw_path); i++) { -		struct file *file; -		snprintf(path, PATH_MAX, "%s/%s", fw_path[i], name); - -		file = filp_open(path, O_RDONLY, 0); -		if (IS_ERR(file)) -			continue; -		success = fw_read_file_contents(file, fw); -		fput(file); -		if (success) -			break; -	} -	__putname(path); -	return success; -} -  /* Builtin firmware support */  #ifdef CONFIG_FW_LOADER @@ -150,6 +88,11 @@ 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) @@ -173,8 +116,6 @@ struct firmware_cache {  	spinlock_t name_lock;  	struct list_head fw_names; -	wait_queue_head_t wait_queue; -	int cnt;  	struct delayed_work work;  	struct notifier_block   pm_notify; @@ -187,6 +128,7 @@ struct firmware_buf {  	struct completion completion;  	struct firmware_cache *fwc;  	unsigned long status; +	enum fw_buf_fmt fmt;  	void *data;  	size_t size;  	struct page **pages; @@ -240,6 +182,7 @@ 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); @@ -307,10 +250,14 @@ static void __fw_free_buf(struct kref *ref)  	list_del(&buf->list);  	spin_unlock(&fwc->lock); -	vunmap(buf->data); -	for (i = 0; i < buf->nr_pages; i++) -		__free_page(buf->pages[i]); -	kfree(buf->pages); + +	if (buf->fmt == PAGE_BUF) { +		vunmap(buf->data); +		for (i = 0; i < buf->nr_pages; i++) +			__free_page(buf->pages[i]); +		kfree(buf->pages); +	} else +		vfree(buf->data);  	kfree(buf);  } @@ -319,6 +266,69 @@ static void fw_free_buf(struct firmware_buf *buf)  	kref_put(&buf->ref, __fw_free_buf);  } +/* direct firmware loading support */ +static const char *fw_path[] = { +	"/lib/firmware/updates/" UTS_RELEASE, +	"/lib/firmware/updates", +	"/lib/firmware/" UTS_RELEASE, +	"/lib/firmware" +}; + +/* Don't inline this: 'struct kstat' is biggish */ +static noinline long fw_file_size(struct file *file) +{ +	struct kstat st; +	if (vfs_getattr(file->f_path.mnt, file->f_path.dentry, &st)) +		return -1; +	if (!S_ISREG(st.mode)) +		return -1; +	if (st.size != (long)st.size) +		return -1; +	return st.size; +} + +static bool fw_read_file_contents(struct file *file, struct firmware_buf *fw_buf) +{ +	long size; +	char *buf; + +	size = fw_file_size(file); +	if (size < 0) +		return false; +	buf = vmalloc(size); +	if (!buf) +		return false; +	if (kernel_read(file, 0, buf, size) != size) { +		vfree(buf); +		return false; +	} +	fw_buf->data = buf; +	fw_buf->size = size; +	return true; +} + +static bool fw_get_filesystem_firmware(struct firmware_buf *buf) +{ +	int i; +	bool success = false; +	char *path = __getname(); + +	for (i = 0; i < ARRAY_SIZE(fw_path); i++) { +		struct file *file; +		snprintf(path, PATH_MAX, "%s/%s", fw_path[i], buf->fw_id); + +		file = filp_open(path, O_RDONLY, 0); +		if (IS_ERR(file)) +			continue; +		success = fw_read_file_contents(file, buf); +		fput(file); +		if (success) +			break; +	} +	__putname(path); +	return success; +} +  static struct firmware_priv *to_firmware_priv(struct device *dev)  {  	return container_of(dev, struct firmware_priv, dev); @@ -423,6 +433,21 @@ static void firmware_free_data(const struct firmware *fw)  #ifndef PAGE_KERNEL_RO  #define PAGE_KERNEL_RO PAGE_KERNEL  #endif + +/* one pages buffer should be mapped/unmapped only once */ +static int fw_map_pages_buf(struct firmware_buf *buf) +{ +	if (buf->fmt != PAGE_BUF) +		return 0; + +	if (buf->data) +		vunmap(buf->data); +	buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO); +	if (!buf->data) +		return -ENOMEM; +	return 0; +} +  /**   * firmware_loading_store - set value in the 'loading' control file   * @dev: device pointer @@ -467,6 +492,14 @@ static ssize_t firmware_loading_store(struct device *dev,  		if (test_bit(FW_STATUS_LOADING, &fw_buf->status)) {  			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 +			 * see the mapped 'buf->data' once the loading +			 * is completed. +			 * */ +			fw_map_pages_buf(fw_buf);  			complete_all(&fw_buf->completion);  			break;  		} @@ -670,15 +703,6 @@ exit:  	return fw_priv;  } -/* one pages buffer is mapped/unmapped only once */ -static int fw_map_pages_buf(struct firmware_buf *buf) -{ -	buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO); -	if (!buf->data) -		return -ENOMEM; -	return 0; -} -  /* store the pages buffer info firmware from buf */  static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw)  { @@ -778,11 +802,6 @@ _request_firmware_prepare(const struct firmware **firmware_p, const char *name,  		return NULL;  	} -	if (fw_get_filesystem_firmware(firmware, name)) { -		dev_dbg(device, "firmware: direct-loading 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, @@ -832,6 +851,21 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,  	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); + +		set_bit(FW_STATUS_DONE, &buf->status); +		complete_all(&buf->completion); +		direct_load = 1; +		goto handle_fw; +	} + +	/* fall back on userspace loading */ +	buf->fmt = PAGE_BUF;  	dev_set_uevent_suppress(f_dev, true); @@ -870,6 +904,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,  	del_timer_sync(&fw_priv->timeout); +handle_fw:  	mutex_lock(&fw_lock);  	if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status))  		retval = -ENOENT; @@ -884,9 +919,6 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,  	if (!retval && f_dev->parent)  		fw_add_devm_name(f_dev->parent, buf->fw_id); -	if (!retval) -		retval = fw_map_pages_buf(buf); -  	/*  	 * After caching firmware image is started, let it piggyback  	 * on request firmware. @@ -902,6 +934,9 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,  	fw_priv->buf = NULL;  	mutex_unlock(&fw_lock); +	if (direct_load) +		goto err_put_dev; +  	device_remove_file(f_dev, &dev_attr_loading);  err_del_bin_attr:  	device_remove_bin_file(f_dev, &firmware_attr_data); @@ -1129,6 +1164,8 @@ int uncache_firmware(const char *fw_name)  }  #ifdef CONFIG_PM_SLEEP +static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain); +  static struct fw_cache_entry *alloc_fw_cache_entry(const char *name)  {  	struct fw_cache_entry *fce; @@ -1142,17 +1179,27 @@ exit:  	return fce;  } -static int fw_cache_piggyback_on_request(const char *name) +static int __fw_entry_found(const char *name)  {  	struct firmware_cache *fwc = &fw_cache;  	struct fw_cache_entry *fce; -	int ret = 0; -	spin_lock(&fwc->name_lock);  	list_for_each_entry(fce, &fwc->fw_names, list) {  		if (!strcmp(fce->name, name)) -			goto found; +			return 1;  	} +	return 0; +} + +static int fw_cache_piggyback_on_request(const char *name) +{ +	struct firmware_cache *fwc = &fw_cache; +	struct fw_cache_entry *fce; +	int ret = 0; + +	spin_lock(&fwc->name_lock); +	if (__fw_entry_found(name)) +		goto found;  	fce = alloc_fw_cache_entry(name);  	if (fce) { @@ -1185,12 +1232,6 @@ static void __async_dev_cache_fw_image(void *fw_entry,  		free_fw_cache_entry(fce);  	} - -	spin_lock(&fwc->name_lock); -	fwc->cnt--; -	spin_unlock(&fwc->name_lock); - -	wake_up(&fwc->wait_queue);  }  /* called with dev->devres_lock held */ @@ -1229,11 +1270,19 @@ static void dev_cache_fw_image(struct device *dev, void *data)  		list_del(&fce->list);  		spin_lock(&fwc->name_lock); -		fwc->cnt++; -		list_add(&fce->list, &fwc->fw_names); +		/* only one cache entry for one firmware */ +		if (!__fw_entry_found(fce->name)) { +			list_add(&fce->list, &fwc->fw_names); +		} else { +			free_fw_cache_entry(fce); +			fce = NULL; +		}  		spin_unlock(&fwc->name_lock); -		async_schedule(__async_dev_cache_fw_image, (void *)fce); +		if (fce) +			async_schedule_domain(__async_dev_cache_fw_image, +					      (void *)fce, +					      &fw_cache_domain);  	}  } @@ -1275,6 +1324,9 @@ static void device_cache_fw_images(void)  	pr_debug("%s\n", __func__); +	/* cancel uncache work */ +	cancel_delayed_work_sync(&fwc->work); +  	/*  	 * use small loading timeout for caching devices' firmware  	 * because all these firmware images have been loaded @@ -1292,21 +1344,7 @@ static void device_cache_fw_images(void)  	mutex_unlock(&fw_lock);  	/* wait for completion of caching firmware for all devices */ -	spin_lock(&fwc->name_lock); -	for (;;) { -		prepare_to_wait(&fwc->wait_queue, &wait, -				TASK_UNINTERRUPTIBLE); -		if (!fwc->cnt) -			break; - -		spin_unlock(&fwc->name_lock); - -		schedule(); - -		spin_lock(&fwc->name_lock); -	} -	spin_unlock(&fwc->name_lock); -	finish_wait(&fwc->wait_queue, &wait); +	async_synchronize_full_domain(&fw_cache_domain);  	loading_timeout = old_timeout;  } @@ -1394,9 +1432,7 @@ static void __init fw_cache_init(void)  #ifdef CONFIG_PM_SLEEP  	spin_lock_init(&fw_cache.name_lock);  	INIT_LIST_HEAD(&fw_cache.fw_names); -	fw_cache.cnt = 0; -	init_waitqueue_head(&fw_cache.wait_queue);  	INIT_DELAYED_WORK(&fw_cache.work,  			  device_uncache_fw_images_work);  |