OSDN Git Service

firmware_class: Introduce the request_firmware_direct API
authorVikram Mulukutla <markivx@codeaurora.org>
Mon, 5 Aug 2013 18:39:20 +0000 (11:39 -0700)
committerDavid Keitel <dkeitel@codeaurora.org>
Tue, 22 Mar 2016 18:07:35 +0000 (11:07 -0700)
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>
drivers/base/firmware_class.c
include/linux/firmware.h

index a389eb5..345b3d5 100644 (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 */
@@ -1279,6 +1418,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);
 
index 5c41c5e..bb31b5a 100644 (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)
 {
 }