Commit 6e03a201 authored by David Woodhouse's avatar David Woodhouse Committed by David Woodhouse

firmware: speed up request_firmware(), v3

Rather than calling vmalloc() repeatedly to grow the firmware image as
we receive data from userspace, just allocate and fill individual pages.
Then vmap() the whole lot in one go when we're done.

A quick test with a 337KiB iwlagn firmware shows the time taken for
request_firmware() going from ~32ms to ~5ms after I apply this patch.

[v2: define PAGE_KERNEL_RO as PAGE_KERNEL where necessary, use min_t()]
[v3: kunmap() takes the struct page *, not the virtual address]
Signed-off-by: default avatarDavid Woodhouse <David.Woodhouse@intel.com>
Tested-by: default avatarSachin Sant <sachinp@in.ibm.com>
parent 091bf762
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
#include <linux/bitops.h> #include <linux/bitops.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/kthread.h> #include <linux/kthread.h>
#include <linux/highmem.h>
#include <linux/firmware.h> #include <linux/firmware.h>
#include "base.h" #include "base.h"
...@@ -45,7 +45,10 @@ struct firmware_priv { ...@@ -45,7 +45,10 @@ struct firmware_priv {
struct bin_attribute attr_data; struct bin_attribute attr_data;
struct firmware *fw; struct firmware *fw;
unsigned long status; unsigned long status;
int alloc_size; struct page **pages;
int nr_pages;
int page_array_size;
const char *vdata;
struct timer_list timeout; struct timer_list timeout;
}; };
...@@ -122,6 +125,10 @@ static ssize_t firmware_loading_show(struct device *dev, ...@@ -122,6 +125,10 @@ static ssize_t firmware_loading_show(struct device *dev,
return sprintf(buf, "%d\n", loading); return sprintf(buf, "%d\n", loading);
} }
/* Some architectures don't have PAGE_KERNEL_RO */
#ifndef PAGE_KERNEL_RO
#define PAGE_KERNEL_RO PAGE_KERNEL
#endif
/** /**
* firmware_loading_store - set value in the 'loading' control file * firmware_loading_store - set value in the 'loading' control file
* @dev: device pointer * @dev: device pointer
...@@ -141,6 +148,7 @@ static ssize_t firmware_loading_store(struct device *dev, ...@@ -141,6 +148,7 @@ static ssize_t firmware_loading_store(struct device *dev,
{ {
struct firmware_priv *fw_priv = dev_get_drvdata(dev); struct firmware_priv *fw_priv = dev_get_drvdata(dev);
int loading = simple_strtol(buf, NULL, 10); int loading = simple_strtol(buf, NULL, 10);
int i;
switch (loading) { switch (loading) {
case 1: case 1:
...@@ -151,13 +159,30 @@ static ssize_t firmware_loading_store(struct device *dev, ...@@ -151,13 +159,30 @@ static ssize_t firmware_loading_store(struct device *dev,
} }
vfree(fw_priv->fw->data); vfree(fw_priv->fw->data);
fw_priv->fw->data = NULL; fw_priv->fw->data = NULL;
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;
fw_priv->fw->size = 0; fw_priv->fw->size = 0;
fw_priv->alloc_size = 0;
set_bit(FW_STATUS_LOADING, &fw_priv->status); set_bit(FW_STATUS_LOADING, &fw_priv->status);
mutex_unlock(&fw_lock); mutex_unlock(&fw_lock);
break; break;
case 0: case 0:
if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) { if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) {
vfree(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 will be freed by vfree() */
fw_priv->pages = NULL;
fw_priv->page_array_size = 0;
fw_priv->nr_pages = 0;
complete(&fw_priv->completion); complete(&fw_priv->completion);
clear_bit(FW_STATUS_LOADING, &fw_priv->status); clear_bit(FW_STATUS_LOADING, &fw_priv->status);
break; break;
...@@ -167,6 +192,7 @@ static ssize_t firmware_loading_store(struct device *dev, ...@@ -167,6 +192,7 @@ static ssize_t firmware_loading_store(struct device *dev,
dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading);
/* fallthrough */ /* fallthrough */
case -1: case -1:
err:
fw_load_abort(fw_priv); fw_load_abort(fw_priv);
break; break;
} }
...@@ -191,8 +217,28 @@ firmware_data_read(struct kobject *kobj, struct bin_attribute *bin_attr, ...@@ -191,8 +217,28 @@ firmware_data_read(struct kobject *kobj, struct bin_attribute *bin_attr,
ret_count = -ENODEV; ret_count = -ENODEV;
goto out; goto out;
} }
ret_count = memory_read_from_buffer(buffer, count, &offset, if (offset > fw->size)
fw->data, fw->size); return 0;
if (count > fw->size - offset)
count = fw->size - offset;
ret_count = count;
while (count) {
void *page_data;
int page_nr = offset >> PAGE_SHIFT;
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]);
memcpy(buffer, page_data + page_ofs, page_cnt);
kunmap(fw_priv->pages[page_nr]);
buffer += page_cnt;
offset += page_cnt;
count -= page_cnt;
}
out: out:
mutex_unlock(&fw_lock); mutex_unlock(&fw_lock);
return ret_count; return ret_count;
...@@ -201,27 +247,39 @@ out: ...@@ -201,27 +247,39 @@ out:
static int static int
fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
{ {
u8 *new_data; int pages_needed = ALIGN(min_size, PAGE_SIZE) >> PAGE_SHIFT;
int new_size = fw_priv->alloc_size;
if (min_size <= fw_priv->alloc_size) /* If the array of pages is too small, grow it... */
return 0; if (fw_priv->page_array_size < pages_needed) {
int new_array_size = max(pages_needed,
fw_priv->page_array_size * 2);
struct page **new_pages;
new_size = ALIGN(min_size, PAGE_SIZE); new_pages = kmalloc(new_array_size * sizeof(void *),
new_data = vmalloc(new_size); GFP_KERNEL);
if (!new_data) { if (!new_pages) {
printk(KERN_ERR "%s: unable to alloc buffer\n", __func__);
/* Make sure that we don't keep incomplete data */
fw_load_abort(fw_priv); fw_load_abort(fw_priv);
return -ENOMEM; return -ENOMEM;
} }
fw_priv->alloc_size = new_size; memcpy(new_pages, fw_priv->pages,
if (fw_priv->fw->data) { fw_priv->page_array_size * sizeof(void *));
memcpy(new_data, fw_priv->fw->data, fw_priv->fw->size); memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) *
vfree(fw_priv->fw->data); (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;
}
while (fw_priv->nr_pages < pages_needed) {
fw_priv->pages[fw_priv->nr_pages] =
alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
if (!fw_priv->pages[fw_priv->nr_pages]) {
fw_load_abort(fw_priv);
return -ENOMEM;
}
fw_priv->nr_pages++;
} }
fw_priv->fw->data = new_data;
BUG_ON(min_size > fw_priv->alloc_size);
return 0; return 0;
} }
...@@ -258,10 +316,25 @@ firmware_data_write(struct kobject *kobj, struct bin_attribute *bin_attr, ...@@ -258,10 +316,25 @@ firmware_data_write(struct kobject *kobj, struct bin_attribute *bin_attr,
if (retval) if (retval)
goto out; goto out;
memcpy((u8 *)fw->data + offset, buffer, count);
fw->size = max_t(size_t, offset + count, fw->size);
retval = count; retval = count;
while (count) {
void *page_data;
int page_nr = offset >> PAGE_SHIFT;
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]);
memcpy(page_data + page_ofs, buffer, page_cnt);
kunmap(fw_priv->pages[page_nr]);
buffer += page_cnt;
offset += page_cnt;
count -= page_cnt;
}
fw->size = max_t(size_t, offset, fw->size);
out: out:
mutex_unlock(&fw_lock); mutex_unlock(&fw_lock);
return retval; return retval;
...@@ -277,7 +350,11 @@ static struct bin_attribute firmware_attr_data_tmpl = { ...@@ -277,7 +350,11 @@ static struct bin_attribute firmware_attr_data_tmpl = {
static void fw_dev_release(struct device *dev) static void fw_dev_release(struct device *dev)
{ {
struct firmware_priv *fw_priv = dev_get_drvdata(dev); struct firmware_priv *fw_priv = dev_get_drvdata(dev);
int i;
for (i = 0; i < fw_priv->nr_pages; i++)
__free_page(fw_priv->pages[i]);
kfree(fw_priv->pages);
kfree(fw_priv); kfree(fw_priv);
kfree(dev); kfree(dev);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment