Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 53adc478 authored by Vikram Mulukutla's avatar Vikram Mulukutla Committed by David Keitel
Browse files

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: default avatarVikram Mulukutla <markivx@codeaurora.org>
[joshc: renamed request_firmware_direct to request_firmware_into_buf,
avoiding namespace conflict]
Signed-off-by: default avatarJosh Cartwright <joshc@codeaurora.org>
[vmulukut: upstream merge conflict fixups]
Signed-off-by: default avatarVikram Mulukutla <markivx@codeaurora.org>
[dkeitel: upstream merge conflict fixups]
Signed-off-by: default avatarDavid Keitel <dkeitel@codeaurora.org>
parent e7c870ae
Loading
Loading
Loading
Loading
+225 −7
Original line number Diff line number Diff line
@@ -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,6 +327,13 @@ 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;
	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;
@@ -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:
	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);

+32 −1
Original line number Diff line number Diff line
@@ -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)
{
}