From 7623dcc15c2f2c829f3a5e72571120355eaa6500 Mon Sep 17 00:00:00 2001
From: Vikram Mulukutla <markivx@codeaurora.org>
Date: Mon, 5 Aug 2013 11:39:20 -0700
Subject: [PATCH] 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>
---
drivers/base/firmware_class.c | 228 ++++++++++++++++++++++++++++++++--
include/linux/firmware.h | 34 ++++-
2 files changed, 252 insertions(+), 10 deletions(-)
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index b71991a01999..f0493028fc75 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -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);
}
/**
diff --git a/include/linux/firmware.h b/include/linux/firmware.h
index e4279fedb93a..ddca055c712a 100644
--- a/include/linux/firmware.h
+++ b/include/linux/firmware.h
@@ -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)
{
}
--
GitLab