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); |