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: I3ac3c5036c14773125291eccf83d494a8703b4cd
Signed-off-by: Vikram Mulukutla <markivx@codeaurora.org>
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index 5401814..7f159f0 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -21,6 +21,7 @@
#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/sched.h>
+#include <linux/io.h>
#define to_dev(obj) container_of(obj, struct device, kobj)
@@ -98,6 +99,8 @@
struct page **pages;
int nr_pages;
int page_array_size;
+ phys_addr_t dest_addr;
+ size_t dest_size;
struct timer_list timeout;
struct device dev;
bool nowait;
@@ -239,6 +242,10 @@
switch (loading) {
case 1:
+ if (fw_priv->dest_addr) {
+ set_bit(FW_STATUS_LOADING, &fw_priv->status);
+ break;
+ }
firmware_free_data(fw_priv->fw);
memset(fw_priv->fw, 0, sizeof(struct firmware));
/* If the pages are not owned by 'struct firmware' */
@@ -252,6 +259,11 @@
break;
case 0:
if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) {
+ if (fw_priv->dest_addr) {
+ complete(&fw_priv->completion);
+ clear_bit(FW_STATUS_LOADING, &fw_priv->status);
+ break;
+ }
vunmap(fw_priv->fw->data);
fw_priv->fw->data = vmap(fw_priv->pages,
fw_priv->nr_pages,
@@ -286,6 +298,67 @@
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;
+ int retval = count;
+
+ if ((*offset + count) > fw_priv->dest_size) {
+ pr_debug("%s: Failed size check.\n", __func__);
+ retval = -EINVAL;
+ goto out;
+ }
+
+ fw_buf = ioremap(fw_priv->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;
+ iounmap(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 = 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->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_data_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buffer, loff_t offset, size_t count)
@@ -368,6 +441,35 @@
return 0;
}
+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 = 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->status)) {
+ retval = -ENODEV;
+ goto out;
+ }
+
+ retval = __firmware_data_rw(fw_priv, buffer, &offset, count, 0);
+ if (retval < 0)
+ goto out;
+
+ fw->size = max_t(size_t, offset, fw->size);
+out:
+ mutex_unlock(&fw_lock);
+ return retval;
+}
+
/**
* firmware_data_write - write method for firmware
* @filp: open sysfs file
@@ -433,6 +535,13 @@
.write = firmware_data_write,
};
+static struct bin_attribute firmware_direct_attr_data = {
+ .attr = { .name = "data", .mode = 0644 },
+ .size = 0,
+ .read = firmware_direct_read,
+ .write = firmware_direct_write,
+};
+
static void firmware_class_timeout(u_long data)
{
struct firmware_priv *fw_priv = (struct firmware_priv *) data;
@@ -511,6 +620,8 @@
{
int retval = 0;
struct device *f_dev = &fw_priv->dev;
+ struct bin_attribute *fw_attr_data = fw_priv->dest_addr ?
+ &firmware_direct_attr_data : &firmware_attr_data;
dev_set_uevent_suppress(f_dev, true);
@@ -523,7 +634,7 @@
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;
@@ -558,7 +669,7 @@
device_remove_file(f_dev, &dev_attr_loading);
err_del_bin_attr:
- device_remove_bin_file(f_dev, &firmware_attr_data);
+ device_remove_bin_file(f_dev, fw_attr_data);
err_del_dev:
device_del(f_dev);
err_put_dev:
@@ -566,6 +677,35 @@
return retval;
}
+static int
+__request_firmware(const struct firmware **firmware_p, const char *name,
+ struct device *device, phys_addr_t dest_addr, size_t size)
+{
+ struct firmware_priv *fw_priv;
+ int ret;
+
+ fw_priv = _request_firmware_prepare(firmware_p, name, device, true,
+ false);
+ if (IS_ERR_OR_NULL(fw_priv))
+ return PTR_RET(fw_priv);
+
+ fw_priv->dest_addr = dest_addr;
+ fw_priv->dest_size = size;
+
+ ret = usermodehelper_read_trylock();
+ if (WARN_ON(ret)) {
+ dev_err(device, "firmware: %s will not be loaded\n", name);
+ } else {
+ ret = _request_firmware_load(fw_priv, true,
+ firmware_loading_timeout());
+ usermodehelper_read_unlock();
+ }
+ if (ret)
+ _request_firmware_cleanup(firmware_p);
+
+ return ret;
+}
+
/**
* request_firmware: - send firmware request and wait for it
* @firmware_p: pointer to firmware image
@@ -583,27 +723,33 @@
**/
int
request_firmware(const struct firmware **firmware_p, const char *name,
- struct device *device)
+ struct device *device)
{
- struct firmware_priv *fw_priv;
+ return __request_firmware(firmware_p, name, device, 0, 0);
+}
+
+/**
+ * request_firmware_direct: - send firmware request and wait for it
+ * @name: name of firmware file
+ * @device: device for which firmware is being loaded
+ * @dest_addr: Destination address for the firmware
+ * @dest_size:
+ *
+ * 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.
+*/
+int request_firmware_direct(const char *name, struct device *device,
+ phys_addr_t dest_addr, size_t dest_size)
+{
+ const struct firmware *fp = NULL;
int ret;
- fw_priv = _request_firmware_prepare(firmware_p, name, device, true,
- false);
- if (IS_ERR_OR_NULL(fw_priv))
- return PTR_RET(fw_priv);
-
- ret = usermodehelper_read_trylock();
- if (WARN_ON(ret)) {
- dev_err(device, "firmware: %s will not be loaded\n", name);
- } else {
- ret = _request_firmware_load(fw_priv, true,
- firmware_loading_timeout());
- usermodehelper_read_unlock();
- }
+ ret = __request_firmware(&fp, name, device, dest_addr, dest_size);
if (ret)
- _request_firmware_cleanup(firmware_p);
-
+ return ret;
+ ret = fp->size;
+ release_firmware(fp);
return ret;
}
diff --git a/include/linux/firmware.h b/include/linux/firmware.h
index 1e7c011..4f6fffb 100644
--- a/include/linux/firmware.h
+++ b/include/linux/firmware.h
@@ -36,6 +36,8 @@
__used __section(.builtin_fw) = { name, blob, size }
#if defined(CONFIG_FW_LOADER) || (defined(CONFIG_FW_LOADER_MODULE) && defined(MODULE))
+int request_firmware_direct(const char *name, struct device *device,
+ phys_addr_t addr, size_t size);
int request_firmware(const struct firmware **fw, const char *name,
struct device *device);
int request_firmware_nowait(
@@ -45,6 +47,12 @@
void release_firmware(const struct firmware *fw);
#else
+static inline int request_firmware_direct(const char *name,
+ struct device *device,
+ phys_addr_t addr, size_t size)
+{
+ return -EINVAL;
+}
static inline int request_firmware(const struct firmware **fw,
const char *name,
struct device *device)