Skip to content
Snippets Groups Projects
firmware_class.c 38.9 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
/*
 * firmware_class.c - Multi purpose firmware loading support
 *
 * Copyright (c) 2003 Manuel Estrada Sainz
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * Please see Documentation/firmware_class/ for more information.
 *
 */

#include <linux/capability.h>
Linus Torvalds's avatar
Linus Torvalds committed
#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/vmalloc.h>
#include <linux/interrupt.h>
#include <linux/bitops.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/highmem.h>
Linus Torvalds's avatar
Linus Torvalds committed
#include <linux/firmware.h>
#include <linux/list.h>
#include <linux/async.h>
#include <linux/pm.h>
#include <linux/syscore_ops.h>
#include <linux/reboot.h>
#include "base.h"
Linus Torvalds's avatar
Linus Torvalds committed

MODULE_AUTHOR("Manuel Estrada Sainz");
Linus Torvalds's avatar
Linus Torvalds committed
MODULE_DESCRIPTION("Multi purpose firmware loading support");
MODULE_LICENSE("GPL");

/* Builtin firmware support */

#ifdef CONFIG_FW_LOADER

extern struct builtin_fw __start_builtin_fw[];
extern struct builtin_fw __end_builtin_fw[];

static bool fw_get_builtin_firmware(struct firmware *fw, const char *name)
{
	struct builtin_fw *b_fw;

	for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++) {
		if (strcmp(name, b_fw->name) == 0) {
			fw->size = b_fw->size;
			fw->data = b_fw->data;
			return true;
		}
	}

	return false;
}

static bool fw_is_builtin_firmware(const struct firmware *fw)
{
	struct builtin_fw *b_fw;

	for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++)
		if (fw->data == b_fw->data)
			return true;

	return false;
}

#else /* Module case - no builtin firmware support */

static inline bool fw_get_builtin_firmware(struct firmware *fw, const char *name)
{
	return false;
}

static inline bool fw_is_builtin_firmware(const struct firmware *fw)
{
	return false;
}
#endif

Linus Torvalds's avatar
Linus Torvalds committed
enum {
	FW_STATUS_LOADING,
	FW_STATUS_DONE,
	FW_STATUS_ABORT,
};

static int loading_timeout = 60;	/* In seconds */
Linus Torvalds's avatar
Linus Torvalds committed

static inline long firmware_loading_timeout(void)
{
	return loading_timeout > 0 ? loading_timeout * HZ : MAX_SCHEDULE_TIMEOUT;
}

/* firmware behavior options */
#define FW_OPT_UEVENT	(1U << 0)
#define FW_OPT_NOWAIT	(1U << 1)
#ifdef CONFIG_FW_LOADER_USER_HELPER
#define FW_OPT_FALLBACK	(1U << 2)
struct firmware_cache {
	/* firmware_buf instance will be added into the below list */
	spinlock_t lock;
	struct list_head head;
#ifdef CONFIG_PM_SLEEP
	/*
	 * Names of firmware images which have been cached successfully
	 * will be added into the below list so that device uncache
	 * helper can trace which firmware images have been cached
	 * before.
	 */
	spinlock_t name_lock;
	struct list_head fw_names;

	struct delayed_work work;

	struct notifier_block   pm_notify;
Linus Torvalds's avatar
Linus Torvalds committed

struct firmware_buf {
	struct kref ref;
	struct list_head list;
Linus Torvalds's avatar
Linus Torvalds committed
	struct completion completion;
	struct firmware_cache *fwc;
Linus Torvalds's avatar
Linus Torvalds committed
	unsigned long status;
	void *data;
	size_t size;
#ifdef CONFIG_FW_LOADER_USER_HELPER
	struct page **pages;
	int nr_pages;
	int page_array_size;
	struct list_head pending_list;
struct fw_cache_entry {
	struct list_head list;
	char name[];
};

struct fw_name_devm {
	unsigned long magic;
	char name[];
};

#define to_fwbuf(d) container_of(d, struct firmware_buf, ref)

#define	FW_LOADER_NO_CACHE	0
#define	FW_LOADER_START_CACHE	1

static int fw_cache_piggyback_on_request(const char *name);

/* fw_lock could be moved to 'struct firmware_priv' but since it is just
 * guarding for corner cases a global lock should be OK */
static DEFINE_MUTEX(fw_lock);

static struct firmware_cache fw_cache;

static struct firmware_buf *__allocate_fw_buf(const char *fw_name,
					      struct firmware_cache *fwc)
{
	struct firmware_buf *buf;

	buf = kzalloc(sizeof(*buf) + strlen(fw_name) + 1 , GFP_ATOMIC);

	if (!buf)
		return buf;

	kref_init(&buf->ref);
	strcpy(buf->fw_id, fw_name);
	buf->fwc = fwc;
	init_completion(&buf->completion);
#ifdef CONFIG_FW_LOADER_USER_HELPER
	INIT_LIST_HEAD(&buf->pending_list);
#endif

	pr_debug("%s: fw-%s buf=%p\n", __func__, fw_name, buf);

	return buf;
}

static struct firmware_buf *__fw_lookup_buf(const char *fw_name)
{
	struct firmware_buf *tmp;
	struct firmware_cache *fwc = &fw_cache;

	list_for_each_entry(tmp, &fwc->head, list)
		if (!strcmp(tmp->fw_id, fw_name))
			return tmp;
	return NULL;
}

static int fw_lookup_and_allocate_buf(const char *fw_name,
				      struct firmware_cache *fwc,
				      struct firmware_buf **buf)
{
	struct firmware_buf *tmp;

	spin_lock(&fwc->lock);
	tmp = __fw_lookup_buf(fw_name);
	if (tmp) {
		kref_get(&tmp->ref);
		spin_unlock(&fwc->lock);
		*buf = tmp;
		return 1;
	}
	tmp = __allocate_fw_buf(fw_name, fwc);
	if (tmp)
		list_add(&tmp->list, &fwc->head);
	spin_unlock(&fwc->lock);

	*buf = tmp;

	return tmp ? 0 : -ENOMEM;
}

static void __fw_free_buf(struct kref *ref)
	__releases(&fwc->lock)
{
	struct firmware_buf *buf = to_fwbuf(ref);
	struct firmware_cache *fwc = buf->fwc;

	pr_debug("%s: fw-%s buf=%p data=%p size=%u\n",
		 __func__, buf->fw_id, buf, buf->data,
		 (unsigned int)buf->size);

	list_del(&buf->list);
	spin_unlock(&fwc->lock);

#ifdef CONFIG_FW_LOADER_USER_HELPER
	if (buf->is_paged_buf) {
		vunmap(buf->data);
		for (i = 0; i < buf->nr_pages; i++)
			__free_page(buf->pages[i]);
		kfree(buf->pages);
	} else
	kfree(buf);
}

static void fw_free_buf(struct firmware_buf *buf)
{
	struct firmware_cache *fwc = buf->fwc;
	spin_lock(&fwc->lock);
	if (!kref_put(&buf->ref, __fw_free_buf))
		spin_unlock(&fwc->lock);
/* direct firmware loading support */
static char fw_path_para[256];
static const char * const fw_path[] = {
	fw_path_para,
	"/lib/firmware/updates/" UTS_RELEASE,
	"/lib/firmware/updates",
	"/lib/firmware/" UTS_RELEASE,
	"/lib/firmware"
};

/*
 * Typical usage is that passing 'firmware_class.path=$CUSTOMIZED_PATH'
 * from kernel command line because firmware_class is generally built in
 * kernel instead of module.
 */
module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644);
MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path");

/* Don't inline this: 'struct kstat' is biggish */
static noinline_for_stack int fw_file_size(struct file *file)
	if (vfs_getattr(&file->f_path, &st))
		return -1;
	if (!S_ISREG(st.mode))
		return -1;
	if (st.size != (int)st.size)
static int fw_read_file_contents(struct file *file, struct firmware_buf *fw_buf)

	size = fw_file_size(file);
	buf = vmalloc(size);
	if (!buf)
		return -ENOMEM;
	rc = kernel_read(file, 0, buf, size);
	if (rc != size) {
		if (rc > 0)
			rc = -EIO;
	}
	fw_buf->data = buf;
	fw_buf->size = size;
static int fw_get_filesystem_firmware(struct device *device,
				       struct firmware_buf *buf)
	char *path = __getname();

	for (i = 0; i < ARRAY_SIZE(fw_path); i++) {
		struct file *file;

		/* skip the unset customized path */
		if (!fw_path[i][0])
			continue;

		snprintf(path, PATH_MAX, "%s/%s", fw_path[i], buf->fw_id);

		file = filp_open(path, O_RDONLY, 0);
		if (IS_ERR(file))
			continue;
		rc = fw_read_file_contents(file, buf);
		if (rc)
			dev_warn(device, "firmware, attempted to load %s, but failed with error %d\n",
				path, rc);
		else
		dev_dbg(device, "firmware: direct-loading firmware %s\n",
			buf->fw_id);
		mutex_lock(&fw_lock);
		set_bit(FW_STATUS_DONE, &buf->status);
		complete_all(&buf->completion);
		mutex_unlock(&fw_lock);
	}

/* firmware holds the ownership of pages */
static void firmware_free_data(const struct firmware *fw)
{
	/* Loaded directly? */
	if (!fw->priv) {
		vfree(fw->data);
		return;
	}
	fw_free_buf(fw->priv);
}

/* store the pages buffer info firmware from buf */
static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw)
{
	fw->priv = buf;
#ifdef CONFIG_FW_LOADER_USER_HELPER
	fw->pages = buf->pages;
#endif
	fw->size = buf->size;
	fw->data = buf->data;

	pr_debug("%s: fw-%s buf=%p data=%p size=%u\n",
		 __func__, buf->fw_id, buf, buf->data,
		 (unsigned int)buf->size);
}

#ifdef CONFIG_PM_SLEEP
static void fw_name_devm_release(struct device *dev, void *res)
{
	struct fw_name_devm *fwn = res;

	if (fwn->magic == (unsigned long)&fw_cache)
		pr_debug("%s: fw_name-%s devm-%p released\n",
				__func__, fwn->name, res);
}

static int fw_devm_match(struct device *dev, void *res,
		void *match_data)
{
	struct fw_name_devm *fwn = res;

	return (fwn->magic == (unsigned long)&fw_cache) &&
		!strcmp(fwn->name, match_data);
}

static struct fw_name_devm *fw_find_devm_name(struct device *dev,
		const char *name)
{
	struct fw_name_devm *fwn;

	fwn = devres_find(dev, fw_name_devm_release,
			  fw_devm_match, (void *)name);
	return fwn;
}

/* add firmware name into devres list */
static int fw_add_devm_name(struct device *dev, const char *name)
{
	struct fw_name_devm *fwn;

	fwn = fw_find_devm_name(dev, name);
	if (fwn)
		return 1;

	fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm) +
			   strlen(name) + 1, GFP_KERNEL);
	if (!fwn)
		return -ENOMEM;

	fwn->magic = (unsigned long)&fw_cache;
	strcpy(fwn->name, name);
	devres_add(dev, fwn);

	return 0;
}
#else
static int fw_add_devm_name(struct device *dev, const char *name)
{
	return 0;
}
#endif


/*
 * user-mode helper code
 */
#ifdef CONFIG_FW_LOADER_USER_HELPER
struct firmware_priv {
	struct delayed_work timeout_work;
	bool nowait;
	struct device dev;
	struct firmware_buf *buf;
	struct firmware *fw;
};
static struct firmware_priv *to_firmware_priv(struct device *dev)
{
	return container_of(dev, struct firmware_priv, dev);
}

static void __fw_load_abort(struct firmware_buf *buf)
Linus Torvalds's avatar
Linus Torvalds committed
{
	/*
	 * There is a small window in which user can write to 'loading'
	 * between loading done and disappearance of 'loading'
	 */
	if (test_bit(FW_STATUS_DONE, &buf->status))
		return;

	list_del_init(&buf->pending_list);
	set_bit(FW_STATUS_ABORT, &buf->status);
	complete_all(&buf->completion);
static void fw_load_abort(struct firmware_priv *fw_priv)
{
	struct firmware_buf *buf = fw_priv->buf;

	__fw_load_abort(buf);

	/* avoid user action after loading abort */
	fw_priv->buf = NULL;
#define is_fw_load_aborted(buf)	\
	test_bit(FW_STATUS_ABORT, &(buf)->status)

static LIST_HEAD(pending_fw_head);

/* reboot notifier for avoid deadlock with usermode_lock */
static int fw_shutdown_notify(struct notifier_block *unused1,
			      unsigned long unused2, void *unused3)
{
	mutex_lock(&fw_lock);
	while (!list_empty(&pending_fw_head))
		__fw_load_abort(list_first_entry(&pending_fw_head,
					       struct firmware_buf,
					       pending_list));
	mutex_unlock(&fw_lock);
	return NOTIFY_DONE;
}

static struct notifier_block fw_shutdown_nb = {
	.notifier_call = fw_shutdown_notify,
};

static ssize_t timeout_show(struct class *class, struct class_attribute *attr,
			    char *buf)
Linus Torvalds's avatar
Linus Torvalds committed
{
	return sprintf(buf, "%d\n", loading_timeout);
}

/**
 * firmware_timeout_store - set number of seconds to wait for firmware
 * @class: device class pointer
 * @attr: device attribute pointer
 * @buf: buffer to scan for timeout value
 * @count: number of bytes in @buf
 *
Linus Torvalds's avatar
Linus Torvalds committed
 *	Sets the number of seconds to wait for the firmware.  Once
 *	this expires an error will be returned to the driver and no
Linus Torvalds's avatar
Linus Torvalds committed
 *	firmware will be provided.
 *
 *	Note: zero means 'wait forever'.
Linus Torvalds's avatar
Linus Torvalds committed
 **/
static ssize_t timeout_store(struct class *class, struct class_attribute *attr,
			     const char *buf, size_t count)
Linus Torvalds's avatar
Linus Torvalds committed
{
	loading_timeout = simple_strtol(buf, NULL, 10);
	if (loading_timeout < 0)
		loading_timeout = 0;
Linus Torvalds's avatar
Linus Torvalds committed
	return count;
}

static struct class_attribute firmware_class_attrs[] = {
	__ATTR_RW(timeout),
Linus Torvalds's avatar
Linus Torvalds committed

static void fw_dev_release(struct device *dev)
{
	struct firmware_priv *fw_priv = to_firmware_priv(dev);
Linus Torvalds's avatar
Linus Torvalds committed

static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct firmware_priv *fw_priv = to_firmware_priv(dev);
Linus Torvalds's avatar
Linus Torvalds committed

	if (add_uevent_var(env, "FIRMWARE=%s", fw_priv->buf->fw_id))
Linus Torvalds's avatar
Linus Torvalds committed
		return -ENOMEM;
	if (add_uevent_var(env, "TIMEOUT=%i", loading_timeout))
	if (add_uevent_var(env, "ASYNC=%d", fw_priv->nowait))
		return -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed

	return 0;
}

static struct class firmware_class = {
	.name		= "firmware",
	.class_attrs	= firmware_class_attrs,
	.dev_uevent	= firmware_uevent,
	.dev_release	= fw_dev_release,
static ssize_t firmware_loading_show(struct device *dev,
				     struct device_attribute *attr, char *buf)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct firmware_priv *fw_priv = to_firmware_priv(dev);
	int loading = 0;

	mutex_lock(&fw_lock);
	if (fw_priv->buf)
		loading = test_bit(FW_STATUS_LOADING, &fw_priv->buf->status);
	mutex_unlock(&fw_lock);
Linus Torvalds's avatar
Linus Torvalds committed
	return sprintf(buf, "%d\n", loading);
}

/* Some architectures don't have PAGE_KERNEL_RO */
#ifndef PAGE_KERNEL_RO
#define PAGE_KERNEL_RO PAGE_KERNEL
#endif

/* one pages buffer should be mapped/unmapped only once */
static int fw_map_pages_buf(struct firmware_buf *buf)
{
	if (!buf->is_paged_buf)
	if (buf->data)
		vunmap(buf->data);
	buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO);
	if (!buf->data)
		return -ENOMEM;
	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
/**
 * firmware_loading_store - set value in the 'loading' control file
 * @attr: device attribute pointer
 * @buf: buffer to scan for loading control value
 * @count: number of bytes in @buf
 *
Linus Torvalds's avatar
Linus Torvalds committed
 *	The relevant values are:
 *
 *	 1: Start a load, discarding any previous partial load.
 *	 0: Conclude the load and hand the data to the driver code.
Linus Torvalds's avatar
Linus Torvalds committed
 *	-1: Conclude the load with an error and discard any written data.
 **/
static ssize_t firmware_loading_store(struct device *dev,
				      struct device_attribute *attr,
				      const char *buf, size_t count)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct firmware_priv *fw_priv = to_firmware_priv(dev);
	struct firmware_buf *fw_buf;
Linus Torvalds's avatar
Linus Torvalds committed
	int loading = simple_strtol(buf, NULL, 10);
Linus Torvalds's avatar
Linus Torvalds committed

	fw_buf = fw_priv->buf;
	if (!fw_buf)
Linus Torvalds's avatar
Linus Torvalds committed
	switch (loading) {
	case 1:
		/* discarding any previous partial load */
		if (!test_bit(FW_STATUS_DONE, &fw_buf->status)) {
			for (i = 0; i < fw_buf->nr_pages; i++)
				__free_page(fw_buf->pages[i]);
			kfree(fw_buf->pages);
			fw_buf->pages = NULL;
			fw_buf->page_array_size = 0;
			fw_buf->nr_pages = 0;
			set_bit(FW_STATUS_LOADING, &fw_buf->status);
Linus Torvalds's avatar
Linus Torvalds committed
		break;
	case 0:
		if (test_bit(FW_STATUS_LOADING, &fw_buf->status)) {
			set_bit(FW_STATUS_DONE, &fw_buf->status);
			clear_bit(FW_STATUS_LOADING, &fw_buf->status);

			/*
			 * Several loading requests may be pending on
			 * one same firmware buf, so let all requests
			 * see the mapped 'buf->data' once the loading
			 * is completed.
			 * */
			fw_map_pages_buf(fw_buf);
			list_del_init(&fw_buf->pending_list);
			complete_all(&fw_buf->completion);
Linus Torvalds's avatar
Linus Torvalds committed
			break;
		}
		/* fallthrough */
	default:
		dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading);
Linus Torvalds's avatar
Linus Torvalds committed
		/* fallthrough */
	case -1:
		fw_load_abort(fw_priv);
		break;
	}
Linus Torvalds's avatar
Linus Torvalds committed
	return count;
}

static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store);
Linus Torvalds's avatar
Linus Torvalds committed

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)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct device *dev = kobj_to_dev(kobj);
	struct firmware_priv *fw_priv = to_firmware_priv(dev);
	struct firmware_buf *buf;
	ssize_t ret_count;
Linus Torvalds's avatar
Linus Torvalds committed

	mutex_lock(&fw_lock);
	buf = fw_priv->buf;
	if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) {
Linus Torvalds's avatar
Linus Torvalds committed
		ret_count = -ENODEV;
		goto out;
	}
	if (offset > buf->size) {
		ret_count = 0;
		goto out;
	}
	if (count > buf->size - offset)
		count = buf->size - offset;

	ret_count = count;

	while (count) {
		void *page_data;
		int page_nr = offset >> PAGE_SHIFT;
		int page_ofs = offset & (PAGE_SIZE-1);
		int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count);

		page_data = kmap(buf->pages[page_nr]);

		memcpy(buffer, page_data + page_ofs, page_cnt);

		kunmap(buf->pages[page_nr]);
		buffer += page_cnt;
		offset += page_cnt;
		count -= page_cnt;
	}
Linus Torvalds's avatar
Linus Torvalds committed
out:
	mutex_unlock(&fw_lock);
Linus Torvalds's avatar
Linus Torvalds committed
	return ret_count;
}
static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct firmware_buf *buf = fw_priv->buf;
	int pages_needed = ALIGN(min_size, PAGE_SIZE) >> PAGE_SHIFT;

	/* If the array of pages is too small, grow it... */
	if (buf->page_array_size < pages_needed) {
		int new_array_size = max(pages_needed,
					 buf->page_array_size * 2);
		struct page **new_pages;

		new_pages = kmalloc(new_array_size * sizeof(void *),
				    GFP_KERNEL);
		if (!new_pages) {
			fw_load_abort(fw_priv);
			return -ENOMEM;
		}
		memcpy(new_pages, buf->pages,
		       buf->page_array_size * sizeof(void *));
		memset(&new_pages[buf->page_array_size], 0, sizeof(void *) *
		       (new_array_size - buf->page_array_size));
		kfree(buf->pages);
		buf->pages = new_pages;
		buf->page_array_size = new_array_size;
Linus Torvalds's avatar
Linus Torvalds committed

	while (buf->nr_pages < pages_needed) {
		buf->pages[buf->nr_pages] =
			alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
Linus Torvalds's avatar
Linus Torvalds committed

		if (!buf->pages[buf->nr_pages]) {
			fw_load_abort(fw_priv);
			return -ENOMEM;
		}
		buf->nr_pages++;
Linus Torvalds's avatar
Linus Torvalds committed
	}
	return 0;
}

/**
 * firmware_data_write - write method for firmware
 * @filp: open sysfs file
 * @kobj: kobject for the device
 * @bin_attr: bin_attr structure
 * @buffer: buffer being written
 * @offset: buffer offset for write in total data store area
 * @count: buffer size
Linus Torvalds's avatar
Linus Torvalds committed
 *
 *	Data written to the 'data' attribute will be later handed to
Linus Torvalds's avatar
Linus Torvalds committed
 *	the driver as a firmware image.
 **/
static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj,
				   struct bin_attribute *bin_attr,
				   char *buffer, loff_t offset, size_t count)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct device *dev = kobj_to_dev(kobj);
	struct firmware_priv *fw_priv = to_firmware_priv(dev);
	struct firmware_buf *buf;
Linus Torvalds's avatar
Linus Torvalds committed
	ssize_t retval;

	if (!capable(CAP_SYS_RAWIO))
		return -EPERM;
	mutex_lock(&fw_lock);
	buf = fw_priv->buf;
	if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) {
Linus Torvalds's avatar
Linus Torvalds committed
		retval = -ENODEV;
		goto out;
	}
Linus Torvalds's avatar
Linus Torvalds committed
	retval = fw_realloc_buffer(fw_priv, offset + count);
	if (retval)
		goto out;

	retval = count;

	while (count) {
		void *page_data;
		int page_nr = offset >> PAGE_SHIFT;
		int page_ofs = offset & (PAGE_SIZE - 1);
		int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count);

		page_data = kmap(buf->pages[page_nr]);

		memcpy(page_data + page_ofs, buffer, page_cnt);

		kunmap(buf->pages[page_nr]);
		buffer += page_cnt;
		offset += page_cnt;
		count -= page_cnt;
	}

	buf->size = max_t(size_t, offset, buf->size);
Linus Torvalds's avatar
Linus Torvalds committed
out:
	mutex_unlock(&fw_lock);
Linus Torvalds's avatar
Linus Torvalds committed
	return retval;
}
static struct bin_attribute firmware_attr_data = {
	.attr = { .name = "data", .mode = 0644 },
Linus Torvalds's avatar
Linus Torvalds committed
	.size = 0,
	.read = firmware_data_read,
	.write = firmware_data_write,
};

static void firmware_class_timeout_work(struct work_struct *work)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct firmware_priv *fw_priv = container_of(work,
			struct firmware_priv, timeout_work.work);
Linus Torvalds's avatar
Linus Torvalds committed
	fw_load_abort(fw_priv);
static struct firmware_priv *
fw_create_instance(struct firmware *firmware, const char *fw_name,
		   struct device *device, unsigned int opt_flags)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct firmware_priv *fw_priv;
	struct device *f_dev;
Linus Torvalds's avatar
Linus Torvalds committed

	fw_priv = kzalloc(sizeof(*fw_priv), GFP_KERNEL);
		dev_err(device, "%s: kmalloc failed\n", __func__);
		fw_priv = ERR_PTR(-ENOMEM);
		goto exit;
	}

	fw_priv->nowait = !!(opt_flags & FW_OPT_NOWAIT);
	INIT_DELAYED_WORK(&fw_priv->timeout_work,
		firmware_class_timeout_work);
Linus Torvalds's avatar
Linus Torvalds committed

	f_dev = &fw_priv->dev;

	device_initialize(f_dev);
	dev_set_name(f_dev, "%s", fw_name);
	f_dev->parent = device;
	f_dev->class = &firmware_class;
/* load a firmware via user helper */
static int _request_firmware_load(struct firmware_priv *fw_priv,
				  unsigned int opt_flags, long timeout)
	int retval = 0;
	struct device *f_dev = &fw_priv->dev;
	struct firmware_buf *buf = fw_priv->buf;
	/* fall back on userspace loading */
	buf->is_paged_buf = true;
	dev_set_uevent_suppress(f_dev, true);
	retval = device_add(f_dev);
	if (retval) {
		dev_err(f_dev, "%s: device_register failed\n", __func__);
		goto err_put_dev;
	}
	retval = device_create_bin_file(f_dev, &firmware_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);

	retval = device_create_file(f_dev, &dev_attr_loading);
	if (retval) {
		mutex_lock(&fw_lock);
		list_del_init(&buf->pending_list);
		mutex_unlock(&fw_lock);
		dev_err(f_dev, "%s: device_create_file failed\n", __func__);
		goto err_del_bin_attr;
	}
	if (opt_flags & FW_OPT_UEVENT) {
		dev_set_uevent_suppress(f_dev, false);
		dev_dbg(f_dev, "firmware: requesting %s\n", buf->fw_id);
		if (timeout != MAX_SCHEDULE_TIMEOUT)
			schedule_delayed_work(&fw_priv->timeout_work, timeout);
		kobject_uevent(&fw_priv->dev.kobj, KOBJ_ADD);
	}
	wait_for_completion(&buf->completion);
	cancel_delayed_work_sync(&fw_priv->timeout_work);
	device_remove_file(f_dev, &dev_attr_loading);
err_del_bin_attr:
	device_remove_bin_file(f_dev, &firmware_attr_data);
err_del_dev:
	device_del(f_dev);
err_put_dev:
	put_device(f_dev);
	return retval;

static int fw_load_from_user_helper(struct firmware *firmware,
				    const char *name, struct device *device,
				    unsigned int opt_flags, long timeout)
	struct firmware_priv *fw_priv;

	fw_priv = fw_create_instance(firmware, name, device, opt_flags);
	if (IS_ERR(fw_priv))
		return PTR_ERR(fw_priv);

	fw_priv->buf = firmware->priv;
	return _request_firmware_load(fw_priv, opt_flags, timeout);
#ifdef CONFIG_PM_SLEEP
/* kill pending requests without uevent to avoid blocking suspend */
static void kill_requests_without_uevent(void)
{
	struct firmware_buf *buf;
	struct firmware_buf *next;

	mutex_lock(&fw_lock);
	list_for_each_entry_safe(buf, next, &pending_fw_head, pending_list) {
		if (!buf->need_uevent)
#else /* CONFIG_FW_LOADER_USER_HELPER */
static inline int
fw_load_from_user_helper(struct firmware *firmware, const char *name,
			 struct device *device, unsigned int opt_flags,
			 long timeout)
{
	return -ENOENT;
}

/* No abort during direct loading */
#define is_fw_load_aborted(buf) false

#ifdef CONFIG_PM_SLEEP
static inline void kill_requests_without_uevent(void) { }
#endif /* CONFIG_FW_LOADER_USER_HELPER */

/* wait until the shared firmware_buf becomes ready (or error) */
static int sync_cached_firmware_buf(struct firmware_buf *buf)
	int ret = 0;

	mutex_lock(&fw_lock);
	while (!test_bit(FW_STATUS_DONE, &buf->status)) {
		if (is_fw_load_aborted(buf)) {
			ret = -ENOENT;
			break;
		}
		mutex_unlock(&fw_lock);
		wait_for_completion(&buf->completion);
		mutex_lock(&fw_lock);
	}
	mutex_unlock(&fw_lock);
	return ret;
/* prepare firmware and firmware_buf structs;
 * return 0 if a firmware is already assigned, 1 if need to load one,
 * or a negative error code
 */
static int
_request_firmware_prepare(struct firmware **firmware_p, const char *name,
			  struct device *device)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct firmware *firmware;
	struct firmware_buf *buf;
	int ret;