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>
[joshc: renamed request_firmware_direct to request_firmware_into_buf,
avoiding namespace conflict]
Signed-off-by: Josh Cartwright <joshc@codeaurora.org>
[vmulukut: upstream merge conflict fixups]
Signed-off-by: Vikram Mulukutla <markivx@codeaurora.org>
[dkeitel: upstream merge conflict fixups]
Signed-off-by: David Keitel <dkeitel@codeaurora.org>
This commit is contained in:
Vikram Mulukutla 2013-08-05 11:39:20 -07:00 committed by David Keitel
parent e7c870ae4c
commit 53adc478c8
2 changed files with 257 additions and 8 deletions

View file

@ -29,6 +29,7 @@
#include <linux/syscore_ops.h>
#include <linux/reboot.h>
#include <linux/security.h>
#include <linux/io.h>
#include <generated/utsrelease.h>
@ -143,6 +144,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;
bool need_uevent;
@ -170,6 +175,10 @@ struct fw_desc {
const char *name;
struct device *device;
unsigned int opt_flags;
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);
@ -318,7 +327,14 @@ static int fw_read_file_contents(struct file *file, struct firmware_buf *fw_buf)
size = i_size_read(file_inode(file));
if (size <= 0)
return -EINVAL;
buf = vmalloc(size);
if (fw_buf->dest_size > 0 && fw_buf->dest_size < size)
return -EINVAL;
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 -ENOMEM;
rc = kernel_read(file, 0, buf, size);
@ -332,14 +348,20 @@ static int fw_read_file_contents(struct file *file, struct firmware_buf *fw_buf)
goto fail;
fw_buf->data = buf;
fw_buf->size = size;
if (fw_buf->dest_addr)
fw_buf->unmap_fw_mem(buf);
return 0;
fail:
vfree(buf);
if (fw_buf->dest_addr)
fw_buf->unmap_fw_mem(buf);
else
vfree(buf);
return rc;
}
static int 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, len;
int rc = -ENOENT;
@ -674,6 +696,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);
@ -731,6 +757,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)
@ -937,9 +1061,11 @@ static int _request_firmware_load(struct firmware_priv *fw_priv,
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);
@ -949,6 +1075,12 @@ static int _request_firmware_load(struct firmware_priv *fw_priv,
goto err_put_dev;
}
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;
}
mutex_lock(&fw_lock);
list_add(&buf->pending_list, &pending_fw_head);
mutex_unlock(&fw_lock);
@ -1077,6 +1209,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;
}
@ -1179,7 +1315,8 @@ static int _request_firmware(struct fw_desc *desc)
}
}
ret = fw_get_filesystem_firmware(desc->device, fw->priv);
ret = fw_get_filesystem_firmware(desc->device, fw->priv,
desc->dest_addr, desc->dest_size);
if (ret) {
if (!(desc->opt_flags & FW_OPT_NO_WARN))
dev_warn(desc->device,
@ -1236,6 +1373,8 @@ request_firmware(const struct firmware **firmware_p, const char *name,
desc.firmware_p = firmware_p;
desc.name = name;
desc.device = device;
desc.dest_addr = 0;
desc.dest_size = 0;
desc.opt_flags = FW_OPT_UEVENT | FW_OPT_FALLBACK;
/* Need to pin this module until return */
@ -1278,6 +1417,49 @@ int request_firmware_direct(const struct firmware **firmware_p,
}
EXPORT_SYMBOL_GPL(request_firmware_direct);
/**
* request_firmware_into_buf: - 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_into_buf(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.opt_flags = FW_OPT_FALLBACK | FW_OPT_UEVENT | FW_OPT_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;
ret = _request_firmware(&desc);
if (ret)
return ret;
ret = fp->size;
release_firmware(fp);
return ret;
}
EXPORT_SYMBOL_GPL(request_firmware_into_buf);
/**
* release_firmware: - release the resource associated with a firmware image
* @fw: firmware resource to release
@ -1313,10 +1495,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;
@ -1326,6 +1515,10 @@ _request_firmware_nowait(
desc->device = device;
desc->context = context;
desc->cont = cont;
desc->dest_addr = dest_addr;
desc->dest_size = dest_size;
desc->map_fw_mem = map_fw_mem;
desc->unmap_fw_mem = unmap_fw_mem;
desc->opt_flags = FW_OPT_FALLBACK | FW_OPT_NOWAIT;
if (uevent)
@ -1376,10 +1569,35 @@ request_firmware_nowait(
void (*cont)(const struct firmware *fw, void *context))
{
return _request_firmware_nowait(module, uevent, name, device, gfp,
context, cont, false);
context, cont, false, 0, 0, NULL, NULL);
}
EXPORT_SYMBOL(request_firmware_nowait);
/**
* request_firmware_nowait_into_buf - 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_into_buf(
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);
}
EXPORT_SYMBOL_GPL(request_firmware_nowait_into_buf);
#ifdef CONFIG_PM_SLEEP
static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain);

View file

@ -48,6 +48,18 @@ int request_firmware_nowait(
int request_firmware_direct(const struct firmware **fw, const char *name,
struct device *device);
int request_firmware_into_buf(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_into_buf(
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);
#else
static inline int request_firmware(const struct firmware **fw,
@ -56,6 +68,16 @@ static inline int request_firmware(const struct firmware **fw,
{
return -EINVAL;
}
static inline int request_firmware_into_buf(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 +85,16 @@ static inline int request_firmware_nowait(
{
return -EINVAL;
}
static inline int request_firmware_nowait_into_buf(
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)
{
}