firmware_class: Introduce the request_firmware_direct API

On devices with low memory, using request_firmware on rather
large firmware images results in a memory usage penalty that
might be unaffordable. Introduce a new API that allows the
firmware image to be directly loaded to a destination address
without using any intermediate buffer.

Change-Id: I51b55dd9044ea669e2126a3f908028850bf76325
Signed-off-by: Vikram Mulukutla <markivx@codeaurora.org>
This commit is contained in:
Vikram Mulukutla 2013-08-05 11:39:20 -07:00 committed by Artem Borisov
parent dc2ce1cad8
commit c8cd2ed442
2 changed files with 252 additions and 10 deletions

View file

@ -27,6 +27,7 @@
#include <linux/pm.h>
#include <linux/suspend.h>
#include <linux/syscore_ops.h>
#include <linux/io.h>
#include <generated/utsrelease.h>
@ -125,6 +126,10 @@ struct firmware_buf {
unsigned long status;
void *data;
size_t size;
phys_addr_t dest_addr;
size_t dest_size;
void * (*map_fw_mem)(phys_addr_t phys, size_t size);
void (*unmap_fw_mem)(void *virt);
#ifdef CONFIG_FW_LOADER_USER_HELPER
bool is_paged_buf;
struct page **pages;
@ -152,6 +157,10 @@ struct fw_desc {
bool uevent;
bool nowait;
bool nocache;
phys_addr_t dest_addr;
size_t dest_size;
void * (*map_fw_mem)(phys_addr_t phys, size_t size);
void (*unmap_fw_mem)(void *virt);
struct module *module;
void *context;
void (*cont)(const struct firmware *fw, void *context);
@ -313,20 +322,33 @@ static bool fw_read_file_contents(struct file *file, struct firmware_buf *fw_buf
size = fw_file_size(file);
if (size <= 0)
return false;
buf = vmalloc(size);
if (fw_buf->dest_size > 0 && fw_buf->dest_size < size)
return false;
if (fw_buf->dest_addr)
buf = fw_buf->map_fw_mem(fw_buf->dest_addr,
fw_buf->dest_size);
else
buf = vmalloc(size);
if (!buf)
return false;
if (kernel_read(file, 0, buf, size) != size) {
vfree(buf);
if (fw_buf->dest_addr)
fw_buf->unmap_fw_mem(buf);
else
vfree(buf);
return false;
}
fw_buf->data = buf;
fw_buf->size = size;
if (fw_buf->dest_addr)
fw_buf->unmap_fw_mem(buf);
return true;
}
static bool fw_get_filesystem_firmware(struct device *device,
struct firmware_buf *buf)
struct firmware_buf *buf,
phys_addr_t dest_addr, size_t dest_size)
{
int i;
bool success = false;
@ -615,6 +637,10 @@ static ssize_t firmware_loading_store(struct device *dev,
case 1:
/* discarding any previous partial load */
if (!test_bit(FW_STATUS_DONE, &fw_buf->status)) {
if (fw_buf->dest_addr) {
set_bit(FW_STATUS_LOADING, &fw_buf->status);
break;
}
for (i = 0; i < fw_buf->nr_pages; i++)
__free_page(fw_buf->pages[i]);
kfree(fw_buf->pages);
@ -654,6 +680,104 @@ out:
static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store);
static int __firmware_data_rw(struct firmware_priv *fw_priv, char *buffer,
loff_t *offset, size_t count, int read)
{
u8 __iomem *fw_buf;
struct firmware_buf *buf = fw_priv->buf;
int retval = count;
if ((*offset + count) > buf->dest_size) {
pr_debug("%s: Failed size check.\n", __func__);
retval = -EINVAL;
goto out;
}
fw_buf = buf->map_fw_mem(buf->dest_addr + *offset, count);
if (!fw_buf) {
pr_debug("%s: Failed ioremap.\n", __func__);
retval = -ENOMEM;
goto out;
}
if (read)
memcpy(buffer, fw_buf, count);
else
memcpy(fw_buf, buffer, count);
*offset += count;
buf->unmap_fw_mem(fw_buf);
out:
return retval;
}
static ssize_t firmware_direct_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buffer, loff_t offset, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct firmware_priv *fw_priv = to_firmware_priv(dev);
struct firmware *fw;
ssize_t ret_count;
mutex_lock(&fw_lock);
fw = fw_priv->fw;
if (offset > fw->size) {
ret_count = 0;
goto out;
}
if (count > fw->size - offset)
count = fw->size - offset;
if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->buf->status)) {
ret_count = -ENODEV;
goto out;
}
ret_count = __firmware_data_rw(fw_priv, buffer, &offset, count, 1);
out:
mutex_unlock(&fw_lock);
return ret_count;
}
static ssize_t firmware_direct_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buffer, loff_t offset, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct firmware_priv *fw_priv = to_firmware_priv(dev);
struct firmware *fw;
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->buf->status)) {
retval = -ENODEV;
goto out;
}
retval = __firmware_data_rw(fw_priv, buffer, &offset, count, 0);
if (retval < 0)
goto out;
fw_priv->buf->size = max_t(size_t, offset, fw_priv->buf->size);
out:
mutex_unlock(&fw_lock);
return retval;
}
static struct bin_attribute firmware_direct_attr_data = {
.attr = { .name = "data", .mode = 0644 },
.size = 0,
.read = firmware_direct_read,
.write = firmware_direct_write,
};
static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buffer, loff_t offset, size_t count)
@ -849,9 +973,11 @@ 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 bin_attribute *fw_attr_data = buf->dest_addr ?
&firmware_direct_attr_data : &firmware_attr_data;
/* fall back on userspace loading */
buf->is_paged_buf = true;
buf->is_paged_buf = buf->dest_addr ? false : true;
dev_set_uevent_suppress(f_dev, true);
@ -864,7 +990,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
goto err_put_dev;
}
retval = device_create_bin_file(f_dev, &firmware_attr_data);
retval = device_create_bin_file(f_dev, fw_attr_data);
if (retval) {
dev_err(f_dev, "%s: sysfs_create_bin_file failed\n", __func__);
goto err_del_dev;
@ -973,6 +1099,10 @@ _request_firmware_prepare(struct firmware **firmware_p, struct fw_desc *desc)
buf = __allocate_fw_buf(desc->name, NULL);
if (!buf)
return -ENOMEM;
buf->dest_addr = desc->dest_addr;
buf->dest_size = desc->dest_size;
buf->map_fw_mem = desc->map_fw_mem;
buf->unmap_fw_mem = desc->unmap_fw_mem;
firmware->priv = buf;
return 1;
}
@ -1067,7 +1197,8 @@ static int _request_firmware(struct fw_desc *desc)
}
}
if (!fw_get_filesystem_firmware(desc->device, fw->priv))
if (!fw_get_filesystem_firmware(desc->device, fw->priv,
desc->dest_addr, desc->dest_size))
ret = fw_load_from_user_helper(fw, desc, timeout);
if (!ret)
ret = assign_firmware_buf(fw, desc->device, desc->nocache);
@ -1116,9 +1247,55 @@ request_firmware(const struct firmware **firmware_p, const char *name,
desc.uevent = true;
desc.nowait = false;
desc.nocache = false;
desc.dest_addr = 0;
desc.dest_size = 0;
return _request_firmware(&desc);
}
/**
* request_firmware_direct: - send firmware request and wait for it
* @dest_addr: Destination address for the firmware
* @dest_size: Size of destination buffer
*
* Similar to request_firmware, except takes in a buffer address and
* copies firmware data directly to that buffer. Returns the size of
* the firmware that was loaded at dest_addr. This API prevents the
* caching of images.
*/
int
request_firmware_direct(const char *name, struct device *device,
phys_addr_t dest_addr, size_t dest_size,
void * (*map_fw_mem)(phys_addr_t phys, size_t size),
void (*unmap_fw_mem)(void *virt))
{
struct fw_desc desc;
const struct firmware *fp = NULL;
int ret;
if (dest_addr && !map_fw_mem)
return -EINVAL;
if (dest_addr && dest_size <= 0)
return -EINVAL;
desc.firmware_p = &fp;
desc.name = name;
desc.device = device;
desc.uevent = true;
desc.nowait = false;
desc.nocache = true;
desc.dest_addr = dest_addr;
desc.dest_size = dest_size;
desc.map_fw_mem = map_fw_mem;
desc.unmap_fw_mem = unmap_fw_mem;
ret = _request_firmware(&desc);
if (ret)
return ret;
ret = fp->size;
release_firmware(fp);
return ret;
}
/**
* release_firmware: - release the resource associated with a firmware image
* @fw: firmware resource to release
@ -1153,10 +1330,17 @@ _request_firmware_nowait(
struct module *module, bool uevent,
const char *name, struct device *device, gfp_t gfp, void *context,
void (*cont)(const struct firmware *fw, void *context),
bool nocache)
bool nocache, phys_addr_t dest_addr, size_t dest_size,
void * (*map_fw_mem)(phys_addr_t phys, size_t size),
void (*unmap_fw_mem)(void *virt))
{
struct fw_desc *desc;
if (dest_addr && !map_fw_mem)
return -EINVAL;
if (dest_addr && dest_size <= 0)
return -EINVAL;
desc = kzalloc(sizeof(struct fw_desc), gfp);
if (!desc)
return -ENOMEM;
@ -1168,6 +1352,10 @@ _request_firmware_nowait(
desc->cont = cont;
desc->uevent = uevent;
desc->nocache = nocache;
desc->dest_addr = dest_addr;
desc->dest_size = dest_size;
desc->map_fw_mem = map_fw_mem;
desc->unmap_fw_mem = unmap_fw_mem;
if (!try_module_get(module)) {
kfree(desc);
@ -1212,7 +1400,31 @@ request_firmware_nowait(
{
return _request_firmware_nowait(module, uevent, name, device, gfp,
context, cont, false);
context, cont, false, 0, 0, NULL, NULL);
}
/**
* request_firmware_nowait_direct - asynchronous version of request_firmware
* @dest_addr: Destination address for the firmware
* @dest_size: Size of destination buffer
*
* Similar to request_firmware_nowait, except loads the firmware
* directly to a destination address without using an intermediate
* buffer.
*
**/
int
request_firmware_nowait_direct(
struct module *module, bool uevent,
const char *name, struct device *device, gfp_t gfp, void *context,
void (*cont)(const struct firmware *fw, void *context),
phys_addr_t dest_addr, size_t dest_size,
void * (*map_fw_mem)(phys_addr_t phys, size_t size),
void (*unmap_fw_mem)(void *virt))
{
return _request_firmware_nowait(module, uevent, name, device, gfp,
context, cont, true, dest_addr,
dest_size, map_fw_mem, unmap_fw_mem);
}
/**

View file

@ -45,7 +45,18 @@ int request_firmware_nowait(
struct module *module, bool uevent,
const char *name, struct device *device, gfp_t gfp, void *context,
void (*cont)(const struct firmware *fw, void *context));
int request_firmware_direct(const char *name, struct device *device,
phys_addr_t dest_addr, size_t dest_size,
void * (*map_fw_mem)(phys_addr_t phys,
size_t size),
void (*unmap_fw_mem)(void *virt));
int request_firmware_nowait_direct(
struct module *module, bool uevent,
const char *name, struct device *device, gfp_t gfp, void *context,
void (*cont)(const struct firmware *fw, void *context),
phys_addr_t dest_addr, size_t dest_size,
void * (*map_fw_mem)(phys_addr_t phys, size_t size),
void (*unmap_fw_mem)(void *virt));
void release_firmware(const struct firmware *fw);
int cache_firmware(const char *name);
int uncache_firmware(const char *name);
@ -56,6 +67,16 @@ static inline int request_firmware(const struct firmware **fw,
{
return -EINVAL;
}
static inline int request_firmware_direct(const char *name,
struct device *device,
phys_addr_t dest_addr,
size_t dest_size,
void * (*map_fw_mem)(phys_addr_t phys,
size_t size),
void (*unmap_fw_mem)(void *virt))
{
return -EINVAL;
}
static inline int request_firmware_nowait(
struct module *module, bool uevent,
const char *name, struct device *device, gfp_t gfp, void *context,
@ -63,7 +84,16 @@ static inline int request_firmware_nowait(
{
return -EINVAL;
}
static inline int request_firmware_nowait_direct(
struct module *module, bool uevent,
const char *name, struct device *device, gfp_t gfp, void *context,
void (*cont)(const struct firmware *fw, void *context),
phys_addr_t dest_addr, size_t dest_size,
void * (*map_fw_mem)(phys_addr_t phys, size_t size),
void (*unmap_fw_mem)(void *virt))
{
return -EINVAL;
}
static inline void release_firmware(const struct firmware *fw)
{
}