From 65710cb6ea315b3ef76a8a3da7be99afcf58d2bb Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:16 +0800 Subject: firmware loader: simplify pages ownership transfer This patch doesn't transfer ownership of pages' buffer to the instance of firmware until the firmware loading is completed, which will simplify firmware_loading_store a lot, so help to introduce the following cache_firmware and uncache_firmware mechanism during system suspend-resume cycle. In fact, this patch fixes one bug: if writing data into firmware loader device is bypassed between writting 1 and 0 to 'loading', OOPS will be triggered without the patch. Also handle the vmap failure case, and add some comments to make code more readable. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 62 +++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 23 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 803cfc1597a9..1cbefcfd15f7 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -93,6 +93,8 @@ struct firmware_priv { struct completion completion; struct firmware *fw; unsigned long status; + void *data; + size_t size; struct page **pages; int nr_pages; int page_array_size; @@ -156,9 +158,11 @@ static void fw_dev_release(struct device *dev) struct firmware_priv *fw_priv = to_firmware_priv(dev); int i; + /* free untransfered pages buffer */ for (i = 0; i < fw_priv->nr_pages; i++) __free_page(fw_priv->pages[i]); kfree(fw_priv->pages); + kfree(fw_priv); module_put(THIS_MODULE); @@ -194,6 +198,7 @@ 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) { int i; @@ -237,9 +242,7 @@ static ssize_t firmware_loading_store(struct device *dev, switch (loading) { case 1: - firmware_free_data(fw_priv->fw); - memset(fw_priv->fw, 0, sizeof(struct firmware)); - /* If the pages are not owned by 'struct firmware' */ + /* discarding any previous partial load */ for (i = 0; i < fw_priv->nr_pages; i++) __free_page(fw_priv->pages[i]); kfree(fw_priv->pages); @@ -250,20 +253,6 @@ static ssize_t firmware_loading_store(struct device *dev, break; case 0: if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) { - vunmap(fw_priv->fw->data); - fw_priv->fw->data = vmap(fw_priv->pages, - fw_priv->nr_pages, - 0, PAGE_KERNEL_RO); - if (!fw_priv->fw->data) { - dev_err(dev, "%s: vmap() failed\n", __func__); - goto err; - } - /* Pages are now owned by 'struct firmware' */ - fw_priv->fw->pages = fw_priv->pages; - fw_priv->pages = NULL; - - fw_priv->page_array_size = 0; - fw_priv->nr_pages = 0; complete(&fw_priv->completion); clear_bit(FW_STATUS_LOADING, &fw_priv->status); break; @@ -273,7 +262,6 @@ static ssize_t firmware_loading_store(struct device *dev, dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); /* fallthrough */ case -1: - err: fw_load_abort(fw_priv); break; } @@ -299,12 +287,12 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, ret_count = -ENODEV; goto out; } - if (offset > fw->size) { + if (offset > fw_priv->size) { ret_count = 0; goto out; } - if (count > fw->size - offset) - count = fw->size - offset; + if (count > fw_priv->size - offset) + count = fw_priv->size - offset; ret_count = count; @@ -396,6 +384,7 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, retval = -ENODEV; goto out; } + retval = fw_realloc_buffer(fw_priv, offset + count); if (retval) goto out; @@ -418,7 +407,7 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, count -= page_cnt; } - fw->size = max_t(size_t, offset, fw->size); + fw_priv->size = max_t(size_t, offset, fw_priv->size); out: mutex_unlock(&fw_lock); return retval; @@ -504,6 +493,29 @@ static void _request_firmware_cleanup(const struct firmware **firmware_p) *firmware_p = NULL; } +/* transfer the ownership of pages to firmware */ +static int fw_set_page_data(struct firmware_priv *fw_priv) +{ + struct firmware *fw = fw_priv->fw; + + fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, + 0, PAGE_KERNEL_RO); + if (!fw_priv->data) + return -ENOMEM; + + fw->data = fw_priv->data; + fw->pages = fw_priv->pages; + fw->size = fw_priv->size; + + WARN_ON(PFN_UP(fw->size) != fw_priv->nr_pages); + + fw_priv->nr_pages = 0; + fw_priv->pages = NULL; + fw_priv->data = NULL; + + return 0; +} + static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, long timeout) { @@ -549,8 +561,12 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, del_timer_sync(&fw_priv->timeout); mutex_lock(&fw_lock); - if (!fw_priv->fw->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) + if (!fw_priv->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) retval = -ENOENT; + + /* transfer pages ownership at the last minute */ + if (!retval) + retval = fw_set_page_data(fw_priv); fw_priv->fw = NULL; mutex_unlock(&fw_lock); -- cgit v1.2.3 From 28eefa750b5e16b13bb869c2c4f7d624a43eb48b Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:17 +0800 Subject: firmware loader: fix races during loading firmware This patch fixes two races in loading firmware: 1, FW_STATUS_DONE should be set before waking up the task waitting on _request_firmware_load, otherwise FW_STATUS_ABORT may be thought as DONE mistakenly. 2, Inside _request_firmware_load(), there is a small window between wait_for_completion() and mutex_lock(&fw_lock), and 'echo 1 > loading' still may happen during the period, so this patch checks FW_STATUS_DONE to prevent pages' buffer completed from being freed in firmware_loading_store. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 1cbefcfd15f7..1915ad821688 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -243,18 +243,21 @@ static ssize_t firmware_loading_store(struct device *dev, switch (loading) { case 1: /* discarding any previous partial load */ - for (i = 0; i < fw_priv->nr_pages; i++) - __free_page(fw_priv->pages[i]); - kfree(fw_priv->pages); - fw_priv->pages = NULL; - fw_priv->page_array_size = 0; - fw_priv->nr_pages = 0; - set_bit(FW_STATUS_LOADING, &fw_priv->status); + if (!test_bit(FW_STATUS_DONE, &fw_priv->status)) { + for (i = 0; i < fw_priv->nr_pages; i++) + __free_page(fw_priv->pages[i]); + kfree(fw_priv->pages); + fw_priv->pages = NULL; + fw_priv->page_array_size = 0; + fw_priv->nr_pages = 0; + set_bit(FW_STATUS_LOADING, &fw_priv->status); + } break; case 0: if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) { - complete(&fw_priv->completion); + set_bit(FW_STATUS_DONE, &fw_priv->status); clear_bit(FW_STATUS_LOADING, &fw_priv->status); + complete(&fw_priv->completion); break; } /* fallthrough */ @@ -557,7 +560,6 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, wait_for_completion(&fw_priv->completion); - set_bit(FW_STATUS_DONE, &fw_priv->status); del_timer_sync(&fw_priv->timeout); mutex_lock(&fw_lock); -- cgit v1.2.3 From 0c25a850f7f336cd3bf2b0a479fe70cecb242c6e Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:18 +0800 Subject: firmware loader: remove unnecessary wmb() The wmb() inside fw_load_abort is not necessary, since complete() and wait_on_completion() has implied one pair of memory barrier. Also wmb() isn't a correct usage, so just remove it. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 1915ad821688..0bd09c7545c9 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -112,7 +112,6 @@ static struct firmware_priv *to_firmware_priv(struct device *dev) static void fw_load_abort(struct firmware_priv *fw_priv) { set_bit(FW_STATUS_ABORT, &fw_priv->status); - wmb(); complete(&fw_priv->completion); } -- cgit v1.2.3 From 99c2aa72306079976369aad7fc62cc71931d692a Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:19 +0800 Subject: firmware loader: fix creation failure of fw loader device If one device driver calls request_firmware_nowait() to request several different firmwares' loading, device_add() will return failure since all firmware loader device use same name of the device who is requesting firmware. This patch always use the name of firmware image as the firmware loader device name to fix the problem since the following patches for caching firmware will make sure only one loading for same firmware is alllowd at the same time. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 0bd09c7545c9..04c75b56f4fc 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -452,7 +452,7 @@ fw_create_instance(struct firmware *firmware, const char *fw_name, f_dev = &fw_priv->dev; device_initialize(f_dev); - dev_set_name(f_dev, "%s", dev_name(device)); + dev_set_name(f_dev, "%s", fw_name); f_dev->parent = device; f_dev->class = &firmware_class; -- cgit v1.2.3 From 1244691c73b250be522e77ac1a00ad53b601b4c4 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:20 +0800 Subject: firmware loader: introduce firmware_buf This patch introduces struct firmware_buf to describe the buffer which holds the firmware data, which will make the following cache_firmware/uncache_firmware implemented easily. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 180 ++++++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 78 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 04c75b56f4fc..5f2076e5d5b1 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -89,7 +89,7 @@ static inline long firmware_loading_timeout(void) * guarding for corner cases a global lock should be OK */ static DEFINE_MUTEX(fw_lock); -struct firmware_priv { +struct firmware_buf { struct completion completion; struct firmware *fw; unsigned long status; @@ -98,10 +98,14 @@ struct firmware_priv { struct page **pages; int nr_pages; int page_array_size; + char fw_id[]; +}; + +struct firmware_priv { struct timer_list timeout; - struct device dev; bool nowait; - char fw_id[]; + struct device dev; + struct firmware_buf *buf; }; static struct firmware_priv *to_firmware_priv(struct device *dev) @@ -111,8 +115,10 @@ static struct firmware_priv *to_firmware_priv(struct device *dev) static void fw_load_abort(struct firmware_priv *fw_priv) { - set_bit(FW_STATUS_ABORT, &fw_priv->status); - complete(&fw_priv->completion); + struct firmware_buf *buf = fw_priv->buf; + + set_bit(FW_STATUS_ABORT, &buf->status); + complete(&buf->completion); } static ssize_t firmware_timeout_show(struct class *class, @@ -152,15 +158,21 @@ static struct class_attribute firmware_class_attrs[] = { __ATTR_NULL }; -static void fw_dev_release(struct device *dev) +static void fw_free_buf(struct firmware_buf *buf) { - struct firmware_priv *fw_priv = to_firmware_priv(dev); int i; - /* free untransfered pages buffer */ - for (i = 0; i < fw_priv->nr_pages; i++) - __free_page(fw_priv->pages[i]); - kfree(fw_priv->pages); + if (!buf) + return; + + for (i = 0; i < buf->nr_pages; i++) + __free_page(buf->pages[i]); + kfree(buf->pages); +} + +static void fw_dev_release(struct device *dev) +{ + struct firmware_priv *fw_priv = to_firmware_priv(dev); kfree(fw_priv); @@ -171,7 +183,7 @@ static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env) { struct firmware_priv *fw_priv = to_firmware_priv(dev); - if (add_uevent_var(env, "FIRMWARE=%s", fw_priv->fw_id)) + if (add_uevent_var(env, "FIRMWARE=%s", fw_priv->buf->fw_id)) return -ENOMEM; if (add_uevent_var(env, "TIMEOUT=%i", loading_timeout)) return -ENOMEM; @@ -192,7 +204,7 @@ static ssize_t firmware_loading_show(struct device *dev, struct device_attribute *attr, char *buf) { struct firmware_priv *fw_priv = to_firmware_priv(dev); - int loading = test_bit(FW_STATUS_LOADING, &fw_priv->status); + int loading = test_bit(FW_STATUS_LOADING, &fw_priv->buf->status); return sprintf(buf, "%d\n", loading); } @@ -231,32 +243,33 @@ static ssize_t firmware_loading_store(struct device *dev, const char *buf, size_t count) { struct firmware_priv *fw_priv = to_firmware_priv(dev); + struct firmware_buf *fw_buf = fw_priv->buf; int loading = simple_strtol(buf, NULL, 10); int i; mutex_lock(&fw_lock); - if (!fw_priv->fw) + if (!fw_buf) goto out; switch (loading) { case 1: /* discarding any previous partial load */ - if (!test_bit(FW_STATUS_DONE, &fw_priv->status)) { - for (i = 0; i < fw_priv->nr_pages; i++) - __free_page(fw_priv->pages[i]); - kfree(fw_priv->pages); - fw_priv->pages = NULL; - fw_priv->page_array_size = 0; - fw_priv->nr_pages = 0; - set_bit(FW_STATUS_LOADING, &fw_priv->status); + if (!test_bit(FW_STATUS_DONE, &fw_buf->status)) { + for (i = 0; i < fw_buf->nr_pages; i++) + __free_page(fw_buf->pages[i]); + kfree(fw_buf->pages); + fw_buf->pages = NULL; + fw_buf->page_array_size = 0; + fw_buf->nr_pages = 0; + set_bit(FW_STATUS_LOADING, &fw_buf->status); } break; case 0: - if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) { - set_bit(FW_STATUS_DONE, &fw_priv->status); - clear_bit(FW_STATUS_LOADING, &fw_priv->status); - complete(&fw_priv->completion); + 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); + complete(&fw_buf->completion); break; } /* fallthrough */ @@ -280,21 +293,21 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, { struct device *dev = kobj_to_dev(kobj); struct firmware_priv *fw_priv = to_firmware_priv(dev); - struct firmware *fw; + struct firmware_buf *buf; ssize_t ret_count; mutex_lock(&fw_lock); - fw = fw_priv->fw; - if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) { + buf = fw_priv->buf; + if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) { ret_count = -ENODEV; goto out; } - if (offset > fw_priv->size) { + if (offset > buf->size) { ret_count = 0; goto out; } - if (count > fw_priv->size - offset) - count = fw_priv->size - offset; + if (count > buf->size - offset) + count = buf->size - offset; ret_count = count; @@ -304,11 +317,11 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, int page_ofs = offset & (PAGE_SIZE-1); int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count); - page_data = kmap(fw_priv->pages[page_nr]); + page_data = kmap(buf->pages[page_nr]); memcpy(buffer, page_data + page_ofs, page_cnt); - kunmap(fw_priv->pages[page_nr]); + kunmap(buf->pages[page_nr]); buffer += page_cnt; offset += page_cnt; count -= page_cnt; @@ -320,12 +333,13 @@ out: static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) { + struct firmware_buf *buf = fw_priv->buf; int pages_needed = ALIGN(min_size, PAGE_SIZE) >> PAGE_SHIFT; /* If the array of pages is too small, grow it... */ - if (fw_priv->page_array_size < pages_needed) { + if (buf->page_array_size < pages_needed) { int new_array_size = max(pages_needed, - fw_priv->page_array_size * 2); + buf->page_array_size * 2); struct page **new_pages; new_pages = kmalloc(new_array_size * sizeof(void *), @@ -334,24 +348,24 @@ static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) fw_load_abort(fw_priv); return -ENOMEM; } - memcpy(new_pages, fw_priv->pages, - fw_priv->page_array_size * sizeof(void *)); - memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) * - (new_array_size - fw_priv->page_array_size)); - kfree(fw_priv->pages); - fw_priv->pages = new_pages; - fw_priv->page_array_size = new_array_size; + memcpy(new_pages, buf->pages, + buf->page_array_size * sizeof(void *)); + memset(&new_pages[buf->page_array_size], 0, sizeof(void *) * + (new_array_size - buf->page_array_size)); + kfree(buf->pages); + buf->pages = new_pages; + buf->page_array_size = new_array_size; } - while (fw_priv->nr_pages < pages_needed) { - fw_priv->pages[fw_priv->nr_pages] = + while (buf->nr_pages < pages_needed) { + buf->pages[buf->nr_pages] = alloc_page(GFP_KERNEL | __GFP_HIGHMEM); - if (!fw_priv->pages[fw_priv->nr_pages]) { + if (!buf->pages[buf->nr_pages]) { fw_load_abort(fw_priv); return -ENOMEM; } - fw_priv->nr_pages++; + buf->nr_pages++; } return 0; } @@ -374,15 +388,15 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, { struct device *dev = kobj_to_dev(kobj); struct firmware_priv *fw_priv = to_firmware_priv(dev); - struct firmware *fw; + struct firmware_buf *buf; ssize_t retval; if (!capable(CAP_SYS_RAWIO)) return -EPERM; mutex_lock(&fw_lock); - fw = fw_priv->fw; - if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) { + buf = fw_priv->buf; + if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) { retval = -ENODEV; goto out; } @@ -399,17 +413,17 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, int page_ofs = offset & (PAGE_SIZE - 1); int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count); - page_data = kmap(fw_priv->pages[page_nr]); + page_data = kmap(buf->pages[page_nr]); memcpy(page_data + page_ofs, buffer, page_cnt); - kunmap(fw_priv->pages[page_nr]); + kunmap(buf->pages[page_nr]); buffer += page_cnt; offset += page_cnt; count -= page_cnt; } - fw_priv->size = max_t(size_t, offset, fw_priv->size); + buf->size = max_t(size_t, offset, buf->size); out: mutex_unlock(&fw_lock); return retval; @@ -434,20 +448,31 @@ fw_create_instance(struct firmware *firmware, const char *fw_name, struct device *device, bool uevent, bool nowait) { struct firmware_priv *fw_priv; + struct firmware_buf *buf; struct device *f_dev; - fw_priv = kzalloc(sizeof(*fw_priv) + strlen(fw_name) + 1 , GFP_KERNEL); + fw_priv = kzalloc(sizeof(*fw_priv), GFP_KERNEL); if (!fw_priv) { dev_err(device, "%s: kmalloc failed\n", __func__); - return ERR_PTR(-ENOMEM); + fw_priv = ERR_PTR(-ENOMEM); + goto exit; + } + + buf = kzalloc(sizeof(*buf) + strlen(fw_name) + 1, GFP_KERNEL); + if (!buf) { + dev_err(device, "%s: kmalloc failed\n", __func__); + kfree(fw_priv); + fw_priv = ERR_PTR(-ENOMEM); + goto exit; } - fw_priv->fw = firmware; + buf->fw = firmware; + fw_priv->buf = buf; fw_priv->nowait = nowait; - strcpy(fw_priv->fw_id, fw_name); - init_completion(&fw_priv->completion); setup_timer(&fw_priv->timeout, firmware_class_timeout, (u_long) fw_priv); + strcpy(buf->fw_id, fw_name); + init_completion(&buf->completion); f_dev = &fw_priv->dev; @@ -455,7 +480,7 @@ fw_create_instance(struct firmware *firmware, const char *fw_name, dev_set_name(f_dev, "%s", fw_name); f_dev->parent = device; f_dev->class = &firmware_class; - +exit: return fw_priv; } @@ -496,24 +521,18 @@ static void _request_firmware_cleanup(const struct firmware **firmware_p) } /* transfer the ownership of pages to firmware */ -static int fw_set_page_data(struct firmware_priv *fw_priv) +static int fw_set_page_data(struct firmware_buf *buf) { - struct firmware *fw = fw_priv->fw; + struct firmware *fw = buf->fw; - fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, - 0, PAGE_KERNEL_RO); - if (!fw_priv->data) + buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO); + if (!buf->data) return -ENOMEM; - fw->data = fw_priv->data; - fw->pages = fw_priv->pages; - fw->size = fw_priv->size; - - WARN_ON(PFN_UP(fw->size) != fw_priv->nr_pages); - - fw_priv->nr_pages = 0; - fw_priv->pages = NULL; - fw_priv->data = NULL; + fw->data = buf->data; + fw->pages = buf->pages; + fw->size = buf->size; + WARN_ON(PFN_UP(fw->size) != buf->nr_pages); return 0; } @@ -523,6 +542,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, { int retval = 0; struct device *f_dev = &fw_priv->dev; + struct firmware_buf *buf = fw_priv->buf; dev_set_uevent_suppress(f_dev, true); @@ -549,7 +569,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, if (uevent) { dev_set_uevent_suppress(f_dev, false); - dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_id); + dev_dbg(f_dev, "firmware: requesting %s\n", buf->fw_id); if (timeout != MAX_SCHEDULE_TIMEOUT) mod_timer(&fw_priv->timeout, round_jiffies_up(jiffies + timeout)); @@ -557,18 +577,22 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, kobject_uevent(&fw_priv->dev.kobj, KOBJ_ADD); } - wait_for_completion(&fw_priv->completion); + wait_for_completion(&buf->completion); del_timer_sync(&fw_priv->timeout); mutex_lock(&fw_lock); - if (!fw_priv->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) + if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status)) retval = -ENOENT; /* transfer pages ownership at the last minute */ if (!retval) - retval = fw_set_page_data(fw_priv); - fw_priv->fw = NULL; + retval = fw_set_page_data(buf); + if (retval) + fw_free_buf(buf); /* free untransfered pages buffer */ + + kfree(buf); + fw_priv->buf = NULL; mutex_unlock(&fw_lock); device_remove_file(f_dev, &dev_attr_loading); -- cgit v1.2.3 From 1f2b79599ee8f5fc82cc73c6c090eb6cdff881d6 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:21 +0800 Subject: firmware loader: always let firmware_buf own the pages buffer This patch always let firmware_buf own the pages buffer allocated inside firmware_data_write, and add all instances of firmware_buf into the firmware cache global list. Also introduce one private field in 'struct firmware', so release_firmware will see the instance of firmware_buf associated with the current firmware instance, then just 'free' the instance of firmware_buf. The firmware_buf instance represents one pages buffer for one firmware image, so lots of firmware loading requests can share the same firmware_buf instance if they request the same firmware image file. This patch will make implementation of the following cache_firmware/ uncache_firmware very easy and simple. In fact, the patch improves request_formware/release_firmware: - only request userspace to write firmware image once if several devices share one same firmware image and its drivers call request_firmware concurrently. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 240 ++++++++++++++++++++++++++++++------------ include/linux/firmware.h | 3 + 2 files changed, 174 insertions(+), 69 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 5f2076e5d5b1..848ad97e8d79 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -21,6 +21,7 @@ #include #include #include +#include MODULE_AUTHOR("Manuel Estrada Sainz"); MODULE_DESCRIPTION("Multi purpose firmware loading support"); @@ -85,13 +86,17 @@ static inline long firmware_loading_timeout(void) return loading_timeout > 0 ? loading_timeout * HZ : MAX_SCHEDULE_TIMEOUT; } -/* fw_lock could be moved to 'struct firmware_priv' but since it is just - * guarding for corner cases a global lock should be OK */ -static DEFINE_MUTEX(fw_lock); +struct firmware_cache { + /* firmware_buf instance will be added into the below list */ + spinlock_t lock; + struct list_head head; +}; struct firmware_buf { + struct kref ref; + struct list_head list; struct completion completion; - struct firmware *fw; + struct firmware_cache *fwc; unsigned long status; void *data; size_t size; @@ -106,8 +111,94 @@ struct firmware_priv { bool nowait; struct device dev; struct firmware_buf *buf; + struct firmware *fw; }; +#define to_fwbuf(d) container_of(d, struct firmware_buf, ref) + +/* fw_lock could be moved to 'struct firmware_priv' but since it is just + * guarding for corner cases a global lock should be OK */ +static DEFINE_MUTEX(fw_lock); + +static struct firmware_cache fw_cache; + +static struct firmware_buf *__allocate_fw_buf(const char *fw_name, + struct firmware_cache *fwc) +{ + struct firmware_buf *buf; + + buf = kzalloc(sizeof(*buf) + strlen(fw_name) + 1 , GFP_ATOMIC); + + if (!buf) + return buf; + + kref_init(&buf->ref); + strcpy(buf->fw_id, fw_name); + buf->fwc = fwc; + init_completion(&buf->completion); + + pr_debug("%s: fw-%s buf=%p\n", __func__, fw_name, buf); + + return buf; +} + +static int fw_lookup_and_allocate_buf(const char *fw_name, + struct firmware_cache *fwc, + struct firmware_buf **buf) +{ + struct firmware_buf *tmp; + + spin_lock(&fwc->lock); + list_for_each_entry(tmp, &fwc->head, list) + if (!strcmp(tmp->fw_id, fw_name)) { + kref_get(&tmp->ref); + spin_unlock(&fwc->lock); + *buf = tmp; + return 1; + } + + tmp = __allocate_fw_buf(fw_name, fwc); + if (tmp) + list_add(&tmp->list, &fwc->head); + spin_unlock(&fwc->lock); + + *buf = tmp; + + return tmp ? 0 : -ENOMEM; +} + +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, + (unsigned int)buf->size); + + spin_lock(&fwc->lock); + 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); + kfree(buf); +} + +static void fw_free_buf(struct firmware_buf *buf) +{ + kref_put(&buf->ref, __fw_free_buf); +} + +static void __init fw_cache_init(void) +{ + spin_lock_init(&fw_cache.lock); + INIT_LIST_HEAD(&fw_cache.head); +} + static struct firmware_priv *to_firmware_priv(struct device *dev) { return container_of(dev, struct firmware_priv, dev); @@ -118,7 +209,7 @@ static void fw_load_abort(struct firmware_priv *fw_priv) struct firmware_buf *buf = fw_priv->buf; set_bit(FW_STATUS_ABORT, &buf->status); - complete(&buf->completion); + complete_all(&buf->completion); } static ssize_t firmware_timeout_show(struct class *class, @@ -158,18 +249,6 @@ static struct class_attribute firmware_class_attrs[] = { __ATTR_NULL }; -static void fw_free_buf(struct firmware_buf *buf) -{ - int i; - - if (!buf) - return; - - for (i = 0; i < buf->nr_pages; i++) - __free_page(buf->pages[i]); - kfree(buf->pages); -} - static void fw_dev_release(struct device *dev) { struct firmware_priv *fw_priv = to_firmware_priv(dev); @@ -212,13 +291,8 @@ static ssize_t firmware_loading_show(struct device *dev, /* firmware holds the ownership of pages */ static void firmware_free_data(const struct firmware *fw) { - int i; - vunmap(fw->data); - if (fw->pages) { - for (i = 0; i < PFN_UP(fw->size); i++) - __free_page(fw->pages[i]); - kfree(fw->pages); - } + WARN_ON(!fw->priv); + fw_free_buf(fw->priv); } /* Some architectures don't have PAGE_KERNEL_RO */ @@ -269,7 +343,7 @@ 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); - complete(&fw_buf->completion); + complete_all(&fw_buf->completion); break; } /* fallthrough */ @@ -448,7 +522,6 @@ fw_create_instance(struct firmware *firmware, const char *fw_name, struct device *device, bool uevent, bool nowait) { struct firmware_priv *fw_priv; - struct firmware_buf *buf; struct device *f_dev; fw_priv = kzalloc(sizeof(*fw_priv), GFP_KERNEL); @@ -458,21 +531,10 @@ fw_create_instance(struct firmware *firmware, const char *fw_name, goto exit; } - buf = kzalloc(sizeof(*buf) + strlen(fw_name) + 1, GFP_KERNEL); - if (!buf) { - dev_err(device, "%s: kmalloc failed\n", __func__); - kfree(fw_priv); - fw_priv = ERR_PTR(-ENOMEM); - goto exit; - } - - buf->fw = firmware; - fw_priv->buf = buf; fw_priv->nowait = nowait; + fw_priv->fw = firmware; setup_timer(&fw_priv->timeout, firmware_class_timeout, (u_long) fw_priv); - strcpy(buf->fw_id, fw_name); - init_completion(&buf->completion); f_dev = &fw_priv->dev; @@ -484,12 +546,42 @@ 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) +{ + 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); +} + +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; + struct firmware_priv *fw_priv = NULL; + struct firmware_buf *buf; + int ret; if (!firmware_p) return ERR_PTR(-EINVAL); @@ -506,35 +598,45 @@ _request_firmware_prepare(const struct firmware **firmware_p, const char *name, return NULL; } - fw_priv = fw_create_instance(firmware, name, device, uevent, nowait); - if (IS_ERR(fw_priv)) { - release_firmware(firmware); + 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; } - return fw_priv; -} - -static void _request_firmware_cleanup(const struct firmware **firmware_p) -{ - release_firmware(*firmware_p); - *firmware_p = NULL; -} -/* transfer the ownership of pages to firmware */ -static int fw_set_page_data(struct firmware_buf *buf) -{ - struct firmware *fw = buf->fw; - - buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO); - if (!buf->data) - return -ENOMEM; - - fw->data = buf->data; - fw->pages = buf->pages; - fw->size = buf->size; - WARN_ON(PFN_UP(fw->size) != buf->nr_pages); + /* 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); + _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; - return 0; +exit: + mutex_unlock(&fw_lock); + return fw_priv; } static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, @@ -585,13 +687,12 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status)) retval = -ENOENT; - /* transfer pages ownership at the last minute */ if (!retval) - retval = fw_set_page_data(buf); - if (retval) - fw_free_buf(buf); /* free untransfered pages buffer */ + retval = fw_map_pages_buf(buf); + + /* pass the pages buffer to driver at the last minute */ + fw_set_page_data(buf, fw_priv->fw); - kfree(buf); fw_priv->buf = NULL; mutex_unlock(&fw_lock); @@ -753,6 +854,7 @@ request_firmware_nowait( static int __init firmware_class_init(void) { + fw_cache_init(); return class_register(&firmware_class); } diff --git a/include/linux/firmware.h b/include/linux/firmware.h index 1e7c01189fa6..e85b771f3d8c 100644 --- a/include/linux/firmware.h +++ b/include/linux/firmware.h @@ -12,6 +12,9 @@ struct firmware { size_t size; const u8 *data; struct page **pages; + + /* firmware loader private fields */ + void *priv; }; struct module; -- cgit v1.2.3 From 2887b3959c8b2f6ed1f62ce95c0888aedb1ea84b Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:22 +0800 Subject: firmware loader: introduce cache_firmware and uncache_firmware This patches introduce two kernel APIs of cache_firmware and uncache_firmware, both of which take the firmware file name as the only parameter. So any drivers can call cache_firmware to cache the specified firmware file into kernel memory, and can use the cached firmware in situations which can't request firmware from user space. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 100 ++++++++++++++++++++++++++++++++++++++---- include/linux/firmware.h | 12 +++++ 2 files changed, 104 insertions(+), 8 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 848ad97e8d79..fc119ce6fdb8 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -142,6 +142,17 @@ static struct firmware_buf *__allocate_fw_buf(const char *fw_name, return buf; } +static struct firmware_buf *__fw_lookup_buf(const char *fw_name) +{ + struct firmware_buf *tmp; + struct firmware_cache *fwc = &fw_cache; + + list_for_each_entry(tmp, &fwc->head, list) + if (!strcmp(tmp->fw_id, fw_name)) + return tmp; + return NULL; +} + static int fw_lookup_and_allocate_buf(const char *fw_name, struct firmware_cache *fwc, struct firmware_buf **buf) @@ -149,14 +160,13 @@ static int fw_lookup_and_allocate_buf(const char *fw_name, struct firmware_buf *tmp; spin_lock(&fwc->lock); - list_for_each_entry(tmp, &fwc->head, list) - if (!strcmp(tmp->fw_id, fw_name)) { - kref_get(&tmp->ref); - spin_unlock(&fwc->lock); - *buf = tmp; - return 1; - } - + tmp = __fw_lookup_buf(fw_name); + if (tmp) { + kref_get(&tmp->ref); + spin_unlock(&fwc->lock); + *buf = tmp; + return 1; + } tmp = __allocate_fw_buf(fw_name, fwc); if (tmp) list_add(&tmp->list, &fwc->head); @@ -167,6 +177,18 @@ static int fw_lookup_and_allocate_buf(const char *fw_name, return tmp ? 0 : -ENOMEM; } +static struct firmware_buf *fw_lookup_buf(const char *fw_name) +{ + struct firmware_buf *tmp; + struct firmware_cache *fwc = &fw_cache; + + spin_lock(&fwc->lock); + tmp = __fw_lookup_buf(fw_name); + spin_unlock(&fwc->lock); + + return tmp; +} + static void __fw_free_buf(struct kref *ref) { struct firmware_buf *buf = to_fwbuf(ref); @@ -852,6 +874,66 @@ request_firmware_nowait( return 0; } +/** + * cache_firmware - cache one firmware image in kernel memory space + * @fw_name: the firmware image name + * + * Cache firmware in kernel memory so that drivers can use it when + * system isn't ready for them to request firmware image from userspace. + * Once it returns successfully, driver can use request_firmware or its + * nowait version to get the cached firmware without any interacting + * with userspace + * + * Return 0 if the firmware image has been cached successfully + * Return !0 otherwise + * + */ +int cache_firmware(const char *fw_name) +{ + int ret; + const struct firmware *fw; + + pr_debug("%s: %s\n", __func__, fw_name); + + ret = request_firmware(&fw, fw_name, NULL); + if (!ret) + kfree(fw); + + pr_debug("%s: %s ret=%d\n", __func__, fw_name, ret); + + return ret; +} + +/** + * uncache_firmware - remove one cached firmware image + * @fw_name: the firmware image name + * + * Uncache one firmware image which has been cached successfully + * before. + * + * Return 0 if the firmware cache has been removed successfully + * Return !0 otherwise + * + */ +int uncache_firmware(const char *fw_name) +{ + struct firmware_buf *buf; + struct firmware fw; + + pr_debug("%s: %s\n", __func__, fw_name); + + if (fw_get_builtin_firmware(&fw, fw_name)) + return 0; + + buf = fw_lookup_buf(fw_name); + if (buf) { + fw_free_buf(buf); + return 0; + } + + return -EINVAL; +} + static int __init firmware_class_init(void) { fw_cache_init(); @@ -869,3 +951,5 @@ module_exit(firmware_class_exit); EXPORT_SYMBOL(release_firmware); EXPORT_SYMBOL(request_firmware); EXPORT_SYMBOL(request_firmware_nowait); +EXPORT_SYMBOL_GPL(cache_firmware); +EXPORT_SYMBOL_GPL(uncache_firmware); diff --git a/include/linux/firmware.h b/include/linux/firmware.h index e85b771f3d8c..e4279fedb93a 100644 --- a/include/linux/firmware.h +++ b/include/linux/firmware.h @@ -47,6 +47,8 @@ int request_firmware_nowait( void (*cont)(const struct firmware *fw, void *context)); void release_firmware(const struct firmware *fw); +int cache_firmware(const char *name); +int uncache_firmware(const char *name); #else static inline int request_firmware(const struct firmware **fw, const char *name, @@ -65,6 +67,16 @@ static inline int request_firmware_nowait( static inline void release_firmware(const struct firmware *fw) { } + +static inline int cache_firmware(const char *name) +{ + return -ENOENT; +} + +static inline int uncache_firmware(const char *name) +{ + return -EINVAL; +} #endif #endif -- cgit v1.2.3 From 0cfc1e1e7b5347b4b6df1212f365ce6620bdd98f Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:23 +0800 Subject: firmware loader: fix device lifetime Callers of request_firmware* must hold the reference count of @device, otherwise it is easy to trigger oops since the firmware loader device is the child of @device. This patch adds comments about the usage. In fact, most of drivers call request_firmware* in its probe() or open(), so the constraint should be reasonable and can be satisfied. Also this patch holds the reference count of @device before schedule_work() in request_firmware_nowait() to avoid that the @device is released after request_firmware_nowait returns and before the worker function is scheduled. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index fc119ce6fdb8..7d3a83bb1318 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -742,6 +742,8 @@ err_put_dev: * @name will be used as $FIRMWARE in the uevent environment and * should be distinctive enough not to be confused with any other * firmware image for this or any other device. + * + * Caller must hold the reference count of @device. **/ int request_firmware(const struct firmware **firmware_p, const char *name, @@ -823,6 +825,7 @@ static void request_firmware_work_func(struct work_struct *work) out: fw_work->cont(fw, fw_work->context); + put_device(fw_work->device); module_put(fw_work->module); kfree(fw_work); @@ -841,6 +844,8 @@ static void request_firmware_work_func(struct work_struct *work) * @cont: function will be called asynchronously when the firmware * request is over. * + * Caller must hold the reference count of @device. + * * Asynchronous variant of request_firmware() for user contexts where * it is not possible to sleep for long time. It can't be called * in atomic contexts. @@ -869,6 +874,7 @@ request_firmware_nowait( return -EFAULT; } + get_device(fw_work->device); INIT_WORK(&fw_work->work, request_firmware_work_func); schedule_work(&fw_work->work); return 0; -- cgit v1.2.3 From 6f21a62a58bc3c80cd8b05cacb55003cccd4863e Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:24 +0800 Subject: firmware loader: fix comments on request_firmware_nowait request_firmware_nowait is allowed to be called in atomic context now if @gfp is GFP_ATOMIC, so fix the obsolete comments and states which situations are suitable for using it. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 7d3a83bb1318..a47266ccfc60 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -846,9 +846,13 @@ static void request_firmware_work_func(struct work_struct *work) * * Caller must hold the reference count of @device. * - * Asynchronous variant of request_firmware() for user contexts where - * it is not possible to sleep for long time. It can't be called - * in atomic contexts. + * Asynchronous variant of request_firmware() for user contexts: + * - sleep for as small periods as possible since it may + * increase kernel boot time of built-in device drivers + * requesting firmware in their ->probe() methods, if + * @gfp is GFP_KERNEL. + * + * - can't sleep at all if @gfp is GFP_ATOMIC. **/ int request_firmware_nowait( -- cgit v1.2.3 From f531f05ae9437df5ba1ebd90017e4dd5526048e9 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:25 +0800 Subject: firmware loader: store firmware name into devres list This patch will store firmware name into devres list of the device which is requesting firmware loading, so that we can implement auto cache and uncache firmware for devices in need. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 64 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index a47266ccfc60..65c60666685b 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -114,6 +114,11 @@ struct firmware_priv { struct firmware *fw; }; +struct fw_name_devm { + unsigned long magic; + char name[]; +}; + #define to_fwbuf(d) container_of(d, struct firmware_buf, ref) /* fw_lock could be moved to 'struct firmware_priv' but since it is just @@ -590,6 +595,55 @@ static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw) (unsigned int)buf->size); } +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; +} + static void _request_firmware_cleanup(const struct firmware **firmware_p) { release_firmware(*firmware_p); @@ -709,6 +763,16 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status)) retval = -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 + * should be fixed in devres or driver core. + */ + if (!retval && f_dev->parent) + fw_add_devm_name(f_dev->parent, buf->fw_id); + if (!retval) retval = fw_map_pages_buf(buf); -- cgit v1.2.3 From 37276a51f803ef63be988e26293ac85188475556 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:27 +0800 Subject: firmware: introduce device_cache/uncache_fw_images This patch introduces the three helpers below: void device_cache_fw_images(void) void device_uncache_fw_images(void) void device_uncache_fw_images_delay(unsigned long) so we can use device_cache_fw_images() to cache firmware for all devices which need firmware to work, and the device driver can get the firmware easily from kernel memory when system isn't ready for completing requests of loading firmware. After system is ready for completing firmware loading, driver core will call device_uncache_fw_images() or its delay version to free the cached firmware. The above helpers will be used to cache device firmware during system suspend/resume cycle in the following patches. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 221 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 215 insertions(+), 6 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 65c60666685b..68fd4c698c77 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -22,6 +22,11 @@ #include #include #include +#include +#include + +#include "base.h" +#include "power/power.h" MODULE_AUTHOR("Manuel Estrada Sainz"); MODULE_DESCRIPTION("Multi purpose firmware loading support"); @@ -90,6 +95,19 @@ struct firmware_cache { /* firmware_buf instance will be added into the below list */ spinlock_t lock; struct list_head head; + + /* + * Names of firmware images which have been cached successfully + * will be added into the below list so that device uncache + * helper can trace which firmware images have been cached + * before. + */ + spinlock_t name_lock; + struct list_head fw_names; + + wait_queue_head_t wait_queue; + int cnt; + struct delayed_work work; }; struct firmware_buf { @@ -106,6 +124,11 @@ struct firmware_buf { char fw_id[]; }; +struct fw_cache_entry { + struct list_head list; + char name[]; +}; + struct firmware_priv { struct timer_list timeout; bool nowait; @@ -220,12 +243,6 @@ static void fw_free_buf(struct firmware_buf *buf) kref_put(&buf->ref, __fw_free_buf); } -static void __init fw_cache_init(void) -{ - spin_lock_init(&fw_cache.lock); - INIT_LIST_HEAD(&fw_cache.head); -} - static struct firmware_priv *to_firmware_priv(struct device *dev) { return container_of(dev, struct firmware_priv, dev); @@ -1008,6 +1025,198 @@ int uncache_firmware(const char *fw_name) return -EINVAL; } +static struct fw_cache_entry *alloc_fw_cache_entry(const char *name) +{ + struct fw_cache_entry *fce; + + fce = kzalloc(sizeof(*fce) + strlen(name) + 1, GFP_ATOMIC); + if (!fce) + goto exit; + + strcpy(fce->name, name); +exit: + return fce; +} + +static void free_fw_cache_entry(struct fw_cache_entry *fce) +{ + kfree(fce); +} + +static void __async_dev_cache_fw_image(void *fw_entry, + async_cookie_t cookie) +{ + struct fw_cache_entry *fce = fw_entry; + struct firmware_cache *fwc = &fw_cache; + int ret; + + ret = cache_firmware(fce->name); + if (ret) + goto free; + + spin_lock(&fwc->name_lock); + list_add(&fce->list, &fwc->fw_names); + spin_unlock(&fwc->name_lock); + goto drop_ref; + +free: + free_fw_cache_entry(fce); +drop_ref: + spin_lock(&fwc->name_lock); + fwc->cnt--; + spin_unlock(&fwc->name_lock); + + wake_up(&fwc->wait_queue); +} + +/* called with dev->devres_lock held */ +static void dev_create_fw_entry(struct device *dev, void *res, + void *data) +{ + struct fw_name_devm *fwn = res; + const char *fw_name = fwn->name; + struct list_head *head = data; + struct fw_cache_entry *fce; + + fce = alloc_fw_cache_entry(fw_name); + if (fce) + list_add(&fce->list, head); +} + +static int devm_name_match(struct device *dev, void *res, + void *match_data) +{ + struct fw_name_devm *fwn = res; + return (fwn->magic == (unsigned long)match_data); +} + +static void dev_cache_fw_image(struct device *dev) +{ + LIST_HEAD(todo); + struct fw_cache_entry *fce; + struct fw_cache_entry *fce_next; + struct firmware_cache *fwc = &fw_cache; + + devres_for_each_res(dev, fw_name_devm_release, + devm_name_match, &fw_cache, + dev_create_fw_entry, &todo); + + list_for_each_entry_safe(fce, fce_next, &todo, list) { + list_del(&fce->list); + + spin_lock(&fwc->name_lock); + fwc->cnt++; + spin_unlock(&fwc->name_lock); + + async_schedule(__async_dev_cache_fw_image, (void *)fce); + } +} + +static void __device_uncache_fw_images(void) +{ + struct firmware_cache *fwc = &fw_cache; + struct fw_cache_entry *fce; + + spin_lock(&fwc->name_lock); + while (!list_empty(&fwc->fw_names)) { + fce = list_entry(fwc->fw_names.next, + struct fw_cache_entry, list); + list_del(&fce->list); + spin_unlock(&fwc->name_lock); + + uncache_firmware(fce->name); + free_fw_cache_entry(fce); + + spin_lock(&fwc->name_lock); + } + spin_unlock(&fwc->name_lock); +} + +/** + * device_cache_fw_images - cache devices' firmware + * + * If one device called request_firmware or its nowait version + * successfully before, the firmware names are recored into the + * device's devres link list, so device_cache_fw_images can call + * cache_firmware() to cache these firmwares for the device, + * then the device driver can load its firmwares easily at + * time when system is not ready to complete loading firmware. + */ +static void device_cache_fw_images(void) +{ + struct firmware_cache *fwc = &fw_cache; + struct device *dev; + DEFINE_WAIT(wait); + + pr_debug("%s\n", __func__); + + device_pm_lock(); + list_for_each_entry(dev, &dpm_list, power.entry) + dev_cache_fw_image(dev); + device_pm_unlock(); + + /* 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); +} + +/** + * device_uncache_fw_images - uncache devices' firmware + * + * uncache all firmwares which have been cached successfully + * by device_uncache_fw_images earlier + */ +static void device_uncache_fw_images(void) +{ + pr_debug("%s\n", __func__); + __device_uncache_fw_images(); +} + +static void device_uncache_fw_images_work(struct work_struct *work) +{ + device_uncache_fw_images(); +} + +/** + * device_uncache_fw_images_delay - uncache devices firmwares + * @delay: number of milliseconds to delay uncache device firmwares + * + * uncache all devices's firmwares which has been cached successfully + * by device_cache_fw_images after @delay milliseconds. + */ +static void device_uncache_fw_images_delay(unsigned long delay) +{ + schedule_delayed_work(&fw_cache.work, + msecs_to_jiffies(delay)); +} + +static void __init fw_cache_init(void) +{ + spin_lock_init(&fw_cache.lock); + INIT_LIST_HEAD(&fw_cache.head); + + 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); +} + static int __init firmware_class_init(void) { fw_cache_init(); -- cgit v1.2.3 From ffe53f6f388a3aaa208e7c45065ce096d5d2f4e9 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:28 +0800 Subject: firmware loader: use small timeout for cache device firmware Because device_cache_fw_images only cache the firmware which has been loaded sucessfully at leat once, using a small loading timeout should be reasonable. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 68fd4c698c77..8ca00524d58f 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -1146,10 +1146,22 @@ static void device_cache_fw_images(void) { struct firmware_cache *fwc = &fw_cache; struct device *dev; + int old_timeout; DEFINE_WAIT(wait); pr_debug("%s\n", __func__); + /* + * use small loading timeout for caching devices' firmware + * because all these firmware images have been loaded + * successfully at lease once, also system is ready for + * completing firmware loading now. The maximum size of + * firmware in current distributions is about 2M bytes, + * so 10 secs should be enough. + */ + old_timeout = loading_timeout; + loading_timeout = 10; + device_pm_lock(); list_for_each_entry(dev, &dpm_list, power.entry) dev_cache_fw_image(dev); @@ -1171,6 +1183,8 @@ static void device_cache_fw_images(void) } spin_unlock(&fwc->name_lock); finish_wait(&fwc->wait_queue, &wait); + + loading_timeout = old_timeout; } /** -- cgit v1.2.3 From 07646d9c0938d40b943c592dd1c6435ab24c4e2f Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 4 Aug 2012 12:01:29 +0800 Subject: firmware loader: cache devices firmware during suspend/resume cycle This patch implements caching devices' firmware automatically during system syspend/resume cycle, so any device drivers can call request_firmware or request_firmware_nowait inside resume path to get the cached firmware if they have loaded firmwares successfully at least once before entering suspend. Signed-off-by: Ming Lei Cc: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 8ca00524d58f..5bd2100a4dcf 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "base.h" #include "power/power.h" @@ -108,6 +109,8 @@ struct firmware_cache { wait_queue_head_t wait_queue; int cnt; struct delayed_work work; + + struct notifier_block pm_notify; }; struct firmware_buf { @@ -1217,6 +1220,31 @@ static void device_uncache_fw_images_delay(unsigned long delay) msecs_to_jiffies(delay)); } +#ifdef CONFIG_PM +static int fw_pm_notify(struct notifier_block *notify_block, + unsigned long mode, void *unused) +{ + switch (mode) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + device_cache_fw_images(); + break; + + case PM_POST_SUSPEND: + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + device_uncache_fw_images_delay(10 * MSEC_PER_SEC); + break; + } + + return 0; +} +#else +static int fw_pm_notify(struct notifier_block *notify_block, + unsigned long mode, void *unused) +{} +#endif + static void __init fw_cache_init(void) { spin_lock_init(&fw_cache.lock); @@ -1229,6 +1257,9 @@ static void __init fw_cache_init(void) init_waitqueue_head(&fw_cache.wait_queue); INIT_DELAYED_WORK(&fw_cache.work, device_uncache_fw_images_work); + + fw_cache.pm_notify.notifier_call = fw_pm_notify; + register_pm_notifier(&fw_cache.pm_notify); } static int __init firmware_class_init(void) @@ -1239,6 +1270,7 @@ static int __init firmware_class_init(void) static void __exit firmware_class_exit(void) { + unregister_pm_notifier(&fw_cache.pm_notify); class_unregister(&firmware_class); } -- cgit v1.2.3 From c08f67730aba342b03f070209acc2990d3decf3c Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 17 Aug 2012 22:06:58 +0800 Subject: firmware loader: fix compile failure if !PM 'return 0' should be added to fw_pm_notify if !PM because return value of the funcion is defined as 'int'. Reported-by: Fengguang Wu Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 5bd2100a4dcf..4c8d8efecdf4 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -1242,7 +1242,9 @@ static int fw_pm_notify(struct notifier_block *notify_block, #else static int fw_pm_notify(struct notifier_block *notify_block, unsigned long mode, void *unused) -{} +{ + return 0; +} #endif static void __init fw_cache_init(void) -- cgit v1.2.3 From ab6dd8e5ecf8c1a38ee1b9b9dfa9ab129dc3e200 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 17 Aug 2012 22:07:00 +0800 Subject: firmware loader: fix build failure if FW_LOADER is m device_cache_fw_images need to iterate devices in system, so this patch applies the introduced dpm_for_each_dev to avoid link failure if CONFIG_FW_LOADER is m. Reported-by: Fengguang Wu Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 4c8d8efecdf4..ed0510a912c8 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -27,7 +27,6 @@ #include #include "base.h" -#include "power/power.h" MODULE_AUTHOR("Manuel Estrada Sainz"); MODULE_DESCRIPTION("Multi purpose firmware loading support"); @@ -1093,7 +1092,7 @@ static int devm_name_match(struct device *dev, void *res, return (fwn->magic == (unsigned long)match_data); } -static void dev_cache_fw_image(struct device *dev) +static void dev_cache_fw_image(struct device *dev, void *data) { LIST_HEAD(todo); struct fw_cache_entry *fce; @@ -1148,7 +1147,6 @@ static void __device_uncache_fw_images(void) static void device_cache_fw_images(void) { struct firmware_cache *fwc = &fw_cache; - struct device *dev; int old_timeout; DEFINE_WAIT(wait); @@ -1165,10 +1163,7 @@ static void device_cache_fw_images(void) old_timeout = loading_timeout; loading_timeout = 10; - device_pm_lock(); - list_for_each_entry(dev, &dpm_list, power.entry) - dev_cache_fw_image(dev); - device_pm_unlock(); + dpm_for_each_dev(NULL, dev_cache_fw_image); /* wait for completion of caching firmware for all devices */ spin_lock(&fwc->name_lock); -- cgit v1.2.3 From ef40bb1bd01738670bd567e3dce8e862f2b91bf3 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Mon, 20 Aug 2012 19:04:15 +0800 Subject: firmware loader: fix firmware -ENOENT situations If the requested firmware image doesn't exist, firmware->priv should be set for the later concurrent requests, otherwise warning and oops will be triggered inside firmware_free_data(). Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index ed0510a912c8..edc88bc68b3d 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -718,6 +718,7 @@ _request_firmware_prepare(const struct firmware **firmware_p, const char *name, 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)) { -- cgit v1.2.3 From ac39b3ea73aacde876d1d5ee1ca3e2719f771482 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Mon, 20 Aug 2012 19:04:16 +0800 Subject: firmware loader: let caching firmware piggyback on loading firmware After starting caching firmware, there is still some time left before devices are suspended, during the period, request_firmware or its nowait version may still be triggered by the below situations to load firmware images which can't be cached during suspend/resume cycle. - new devices added - driver bind - or device open kind of things This patch utilizes the piggyback trick to cache firmware for this kind of situation: just increase the firmware buf's reference count and add the fw name entry into cache entry list after starting caching firmware and before syscore_suspend() is called. Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 83 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 9 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index edc88bc68b3d..95f6851b3010 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "base.h" @@ -105,6 +106,8 @@ struct firmware_cache { spinlock_t name_lock; struct list_head fw_names; + int state; + wait_queue_head_t wait_queue; int cnt; struct delayed_work work; @@ -146,6 +149,11 @@ struct fw_name_devm { #define to_fwbuf(d) container_of(d, struct firmware_buf, ref) +#define FW_LOADER_NO_CACHE 0 +#define FW_LOADER_START_CACHE 1 + +static int fw_cache_piggyback_on_request(const char *name); + /* fw_lock could be moved to 'struct firmware_priv' but since it is just * guarding for corner cases a global lock should be OK */ static DEFINE_MUTEX(fw_lock); @@ -741,6 +749,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, int retval = 0; struct device *f_dev = &fw_priv->dev; struct firmware_buf *buf = fw_priv->buf; + struct firmware_cache *fwc = &fw_cache; dev_set_uevent_suppress(f_dev, true); @@ -796,6 +805,15 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, if (!retval) retval = fw_map_pages_buf(buf); + /* + * After caching firmware image is started, let it piggyback + * on request firmware. + */ + if (!retval && 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); @@ -1041,6 +1059,29 @@ exit: return fce; } +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); + list_for_each_entry(fce, &fwc->fw_names, list) { + if (!strcmp(fce->name, name)) + goto found; + } + + fce = alloc_fw_cache_entry(name); + if (fce) { + ret = 1; + list_add(&fce->list, &fwc->fw_names); + pr_debug("%s: fw: %s\n", __func__, name); + } +found: + spin_unlock(&fwc->name_lock); + return ret; +} + static void free_fw_cache_entry(struct fw_cache_entry *fce) { kfree(fce); @@ -1054,17 +1095,14 @@ static void __async_dev_cache_fw_image(void *fw_entry, int ret; ret = cache_firmware(fce->name); - if (ret) - goto free; + if (ret) { + spin_lock(&fwc->name_lock); + list_del(&fce->list); + spin_unlock(&fwc->name_lock); - spin_lock(&fwc->name_lock); - list_add(&fce->list, &fwc->fw_names); - spin_unlock(&fwc->name_lock); - goto drop_ref; + free_fw_cache_entry(fce); + } -free: - free_fw_cache_entry(fce); -drop_ref: spin_lock(&fwc->name_lock); fwc->cnt--; spin_unlock(&fwc->name_lock); @@ -1109,6 +1147,7 @@ static void dev_cache_fw_image(struct device *dev, void *data) spin_lock(&fwc->name_lock); fwc->cnt++; + list_add(&fce->list, &fwc->fw_names); spin_unlock(&fwc->name_lock); async_schedule(__async_dev_cache_fw_image, (void *)fce); @@ -1164,7 +1203,10 @@ static void device_cache_fw_images(void) old_timeout = loading_timeout; loading_timeout = 10; + mutex_lock(&fw_lock); + fwc->state = FW_LOADER_START_CACHE; dpm_for_each_dev(NULL, dev_cache_fw_image); + mutex_unlock(&fw_lock); /* wait for completion of caching firmware for all devices */ spin_lock(&fwc->name_lock); @@ -1229,6 +1271,14 @@ static int fw_pm_notify(struct notifier_block *notify_block, case PM_POST_SUSPEND: case PM_POST_HIBERNATION: case PM_POST_RESTORE: + /* + * In case that system sleep failed and syscore_suspend is + * not called. + */ + mutex_lock(&fw_lock); + fw_cache.state = FW_LOADER_NO_CACHE; + mutex_unlock(&fw_lock); + device_uncache_fw_images_delay(10 * MSEC_PER_SEC); break; } @@ -1243,6 +1293,17 @@ static int fw_pm_notify(struct notifier_block *notify_block, } #endif +/* stop caching firmware once syscore_suspend is reached */ +static int fw_suspend(void) +{ + fw_cache.state = FW_LOADER_NO_CACHE; + return 0; +} + +static struct syscore_ops fw_syscore_ops = { + .suspend = fw_suspend, +}; + static void __init fw_cache_init(void) { spin_lock_init(&fw_cache.lock); @@ -1251,6 +1312,7 @@ static void __init fw_cache_init(void) spin_lock_init(&fw_cache.name_lock); INIT_LIST_HEAD(&fw_cache.fw_names); fw_cache.cnt = 0; + fw_cache.state = FW_LOADER_NO_CACHE; init_waitqueue_head(&fw_cache.wait_queue); INIT_DELAYED_WORK(&fw_cache.work, @@ -1258,6 +1320,8 @@ static void __init fw_cache_init(void) fw_cache.pm_notify.notifier_call = fw_pm_notify; register_pm_notifier(&fw_cache.pm_notify); + + register_syscore_ops(&fw_syscore_ops); } static int __init firmware_class_init(void) @@ -1268,6 +1332,7 @@ static int __init firmware_class_init(void) static void __exit firmware_class_exit(void) { + unregister_syscore_ops(&fw_syscore_ops); unregister_pm_notifier(&fw_cache.pm_notify); class_unregister(&firmware_class); } -- cgit v1.2.3 From cfe016b14e1ee106e906ddfe77d598a2b288d7d6 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 8 Sep 2012 17:32:30 +0800 Subject: firmware loader: fix compile warning when CONFIG_PM=n This patch replaces the previous macro of CONFIG_PM with CONFIG_PM_SLEEP becasue firmware cache is only used in system sleep situations. Also this patch fixes the below compile warning when CONFIG_PM=n: drivers/base/firmware_class.c:1147: warning: 'device_cache_fw_images' defined but not used drivers/base/firmware_class.c:1212: warning: 'device_uncache_fw_images_delay' defined but not used Reported-by: Andrew Morton Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 95f6851b3010..6e210802c37b 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -96,7 +96,9 @@ struct firmware_cache { /* firmware_buf instance will be added into the below list */ spinlock_t lock; struct list_head head; + int state; +#ifdef CONFIG_PM_SLEEP /* * Names of firmware images which have been cached successfully * will be added into the below list so that device uncache @@ -106,13 +108,12 @@ struct firmware_cache { spinlock_t name_lock; struct list_head fw_names; - int state; - wait_queue_head_t wait_queue; int cnt; struct delayed_work work; struct notifier_block pm_notify; +#endif }; struct firmware_buf { @@ -622,6 +623,7 @@ static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw) (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; @@ -670,6 +672,12 @@ static int fw_add_devm_name(struct device *dev, const char *name) 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) { @@ -1046,6 +1054,7 @@ int uncache_firmware(const char *fw_name) return -EINVAL; } +#ifdef CONFIG_PM_SLEEP static struct fw_cache_entry *alloc_fw_cache_entry(const char *name) { struct fw_cache_entry *fce; @@ -1258,7 +1267,6 @@ static void device_uncache_fw_images_delay(unsigned long delay) msecs_to_jiffies(delay)); } -#ifdef CONFIG_PM static int fw_pm_notify(struct notifier_block *notify_block, unsigned long mode, void *unused) { @@ -1285,13 +1293,6 @@ static int fw_pm_notify(struct notifier_block *notify_block, return 0; } -#else -static int fw_pm_notify(struct notifier_block *notify_block, - unsigned long mode, void *unused) -{ - return 0; -} -#endif /* stop caching firmware once syscore_suspend is reached */ static int fw_suspend(void) @@ -1303,16 +1304,23 @@ static int fw_suspend(void) static struct syscore_ops fw_syscore_ops = { .suspend = fw_suspend, }; +#else +static int fw_cache_piggyback_on_request(const char *name) +{ + return 0; +} +#endif static void __init fw_cache_init(void) { spin_lock_init(&fw_cache.lock); INIT_LIST_HEAD(&fw_cache.head); + fw_cache.state = FW_LOADER_NO_CACHE; +#ifdef CONFIG_PM_SLEEP spin_lock_init(&fw_cache.name_lock); INIT_LIST_HEAD(&fw_cache.fw_names); fw_cache.cnt = 0; - fw_cache.state = FW_LOADER_NO_CACHE; init_waitqueue_head(&fw_cache.wait_queue); INIT_DELAYED_WORK(&fw_cache.work, @@ -1322,6 +1330,7 @@ static void __init fw_cache_init(void) register_pm_notifier(&fw_cache.pm_notify); register_syscore_ops(&fw_syscore_ops); +#endif } static int __init firmware_class_init(void) @@ -1332,8 +1341,10 @@ static int __init firmware_class_init(void) static void __exit firmware_class_exit(void) { +#ifdef CONFIG_PM_SLEEP unregister_syscore_ops(&fw_syscore_ops); unregister_pm_notifier(&fw_cache.pm_notify); +#endif class_unregister(&firmware_class); } -- cgit v1.2.3 From abb139e75c2cdbb955e840d6331cb5863e409d0e Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 3 Oct 2012 15:58:32 -0700 Subject: firmware: teach the kernel to load firmware files directly from the filesystem This is a first step in allowing people to by-pass udev for loading device firmware. Current versions of udev will deadlock (causing us to block for the 30 second timeout) under some circumstances if the firmware is loaded as part of the module initialization path, and this is causing problems for media drivers in particular. The current patch hardcodes the firmware path that udev uses by default, and will fall back to the legacy udev mode if the firmware cannot be found there. We'd like to add support for both configuring the paths and the fallback behaviour, but in the meantime this hopefully fixes the immediate problem, while also giving us a way forward. [ v2: Some VFS layer interface cleanups suggested by Al Viro ] [ v3: use the default udev paths suggested by Kay Sievers ] Suggested-by: Ivan Kalvachev Acked-by: Greg KH Acked-by: Al Viro Cc: Mauro Carvalho Chehab Cc: Kay Sievers Cc: Ming Lei Signed-off-by: Linus Torvalds --- drivers/base/firmware_class.c | 78 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 6e210802c37b..e85763de928f 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -21,18 +21,85 @@ #include #include #include +#include #include #include #include #include #include +#include + #include "base.h" 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) +{ + loff_t pos; + long size; + char *buf; + + size = fw_file_size(file); + if (size < 0) + return false; + buf = vmalloc(size); + if (!buf) + return false; + pos = 0; + if (vfs_read(file, buf, size, &pos) != 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 @@ -346,7 +413,11 @@ static ssize_t firmware_loading_show(struct device *dev, /* firmware holds the ownership of pages */ static void firmware_free_data(const struct firmware *fw) { - WARN_ON(!fw->priv); + /* Loaded directly? */ + if (!fw->priv) { + vfree(fw->data); + return; + } fw_free_buf(fw->priv); } @@ -709,6 +780,11 @@ _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, -- cgit v1.2.3 From ce57e981f2b996aaca2031003b3f866368307766 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 4 Oct 2012 09:19:02 -0700 Subject: firmware: use 'kernel_read()' to read firmware into kernel buffer Fengguang correctly points out that the firmware reading should not use vfs_read(), since the buffer is in kernel space. The vfs_read() just happened to work for kernel threads, but sparse warns about the incorrect address spaces, and it's definitely incorrect and could fail for other users of the firmware loading. Reported-by: Fengguang Wu Signed-off-by: Linus Torvalds --- drivers/base/firmware_class.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index e85763de928f..81541452887b 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -58,7 +58,6 @@ static noinline long fw_file_size(struct file *file) static bool fw_read_file_contents(struct file *file, struct firmware *fw) { - loff_t pos; long size; char *buf; @@ -68,8 +67,7 @@ static bool fw_read_file_contents(struct file *file, struct firmware *fw) buf = vmalloc(size); if (!buf) return false; - pos = 0; - if (vfs_read(file, buf, size, &pos) != size) { + if (kernel_read(file, 0, buf, size) != size) { vfree(buf); return false; } -- cgit v1.2.3 From 373304fe10fc46e68815c7116709ad292695dfd1 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Tue, 9 Oct 2012 12:01:01 +0800 Subject: firmware loader: cancel uncache work before caching firmware Under 'Opportunistic sleep' situation, system sleep might be triggered very frequently, so the uncahce work may not be completed before caching firmware during next suspend. This patch cancels the uncache work before caching firmware to fix the problem above. Also this patch optimizes the cacheing firmware mechanism a bit by only storing one firmware cache entry for one firmware image. So if the firmware is still cached during suspend, it doesn't need to be loaded from user space any more. Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 81541452887b..d06a8d0534bf 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -1142,17 +1142,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) { @@ -1229,11 +1239,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)) { + fwc->cnt++; + 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(__async_dev_cache_fw_image, + (void *)fce); } } @@ -1275,6 +1293,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 -- cgit v1.2.3 From 253c9240ee09fd2a05d315ea44ac037a893d8981 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Tue, 9 Oct 2012 12:01:02 +0800 Subject: firmware loader: fix one reqeust_firmware race Several loading requests may be pending on one same firmware buf, and this patch moves fw_map_pages_buf() before complete_all(&fw_buf->completion) and let all requests see the mapped 'buf->data' once the loading is completed. Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index d06a8d0534bf..f2882511a9c1 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -423,6 +423,18 @@ 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->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 +479,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 +690,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) { @@ -884,9 +895,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. -- cgit v1.2.3 From 746058f4304343507e48d39f80d7a3b0d8550e3a Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Tue, 9 Oct 2012 12:01:03 +0800 Subject: firmware loader: let direct loading back on 'firmware_buf' Firstly 'firmware_buf' is introduced to make all loading requests to share one firmware kernel buffer, so firmware_buf should be used in direct loading for saving memory and speedup firmware loading. Secondly, the commit below abb139e75c2cdbb955e840d6331cb5863e409d0e(firmware:teach the kernel to load firmware files directly from the filesystem) introduces direct loading for fixing udev regression, but it bypasses the firmware cache meachnism, so this patch enables caching firmware for direct loading case since it is still needed to solve drivers' dependency during system resume. Cc: Linus Torvalds Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 171 ++++++++++++++++++++++++------------------ 1 file changed, 100 insertions(+), 71 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index f2882511a9c1..a095d84ddfd9 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) @@ -187,6 +130,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 +184,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 +252,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 +268,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); @@ -427,6 +439,9 @@ 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) + return 0; + if (buf->data) vunmap(buf->data); buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO); @@ -789,11 +804,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, @@ -843,6 +853,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); @@ -881,6 +906,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; @@ -910,6 +936,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); -- cgit v1.2.3 From d28d3882bd1fdb88ae4e02f11b091a92b0e5068b Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Tue, 9 Oct 2012 12:01:04 +0800 Subject: firmware loader: sync firmware cache by async_synchronize_full_domain async.c has provided synchronization mechanism on async_schedule_*, so use async_synchronize_full_domain to sync caching firmware instead of reinventing the wheel. Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 34 ++++++---------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) (limited to 'drivers/base/firmware_class.c') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index a095d84ddfd9..8945f4e489ed 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -116,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; @@ -1166,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; @@ -1232,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 */ @@ -1278,7 +1272,6 @@ static void dev_cache_fw_image(struct device *dev, void *data) spin_lock(&fwc->name_lock); /* only one cache entry for one firmware */ if (!__fw_entry_found(fce->name)) { - fwc->cnt++; list_add(&fce->list, &fwc->fw_names); } else { free_fw_cache_entry(fce); @@ -1287,8 +1280,9 @@ static void dev_cache_fw_image(struct device *dev, void *data) spin_unlock(&fwc->name_lock); if (fce) - async_schedule(__async_dev_cache_fw_image, - (void *)fce); + async_schedule_domain(__async_dev_cache_fw_image, + (void *)fce, + &fw_cache_domain); } } @@ -1350,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; } @@ -1452,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); -- cgit v1.2.3