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

Commit c59a264c authored by Linus Torvalds's avatar Linus Torvalds
Browse files
parents 6cb8a911 6e03a201
Loading
Loading
Loading
Loading
+103 −26
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@
#include <linux/bitops.h>
#include <linux/mutex.h>
#include <linux/kthread.h>

#include <linux/highmem.h>
#include <linux/firmware.h>
#include "base.h"

@@ -45,7 +45,10 @@ struct firmware_priv {
	struct bin_attribute attr_data;
	struct firmware *fw;
	unsigned long status;
	int alloc_size;
	struct page **pages;
	int nr_pages;
	int page_array_size;
	const char *vdata;
	struct timer_list timeout;
};

@@ -122,6 +125,10 @@ static ssize_t firmware_loading_show(struct device *dev,
	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
/**
 * firmware_loading_store - set value in the 'loading' control file
 * @dev: device pointer
@@ -141,6 +148,7 @@ static ssize_t firmware_loading_store(struct device *dev,
{
	struct firmware_priv *fw_priv = dev_get_drvdata(dev);
	int loading = simple_strtol(buf, NULL, 10);
	int i;

	switch (loading) {
	case 1:
@@ -151,13 +159,30 @@ static ssize_t firmware_loading_store(struct device *dev,
		}
		vfree(fw_priv->fw->data);
		fw_priv->fw->data = NULL;
		for (i = 0; i < fw_priv->nr_pages; i++)
			__free_page(fw_priv->pages[i]);
		kfree(fw_priv->pages);
		fw_priv->pages = NULL;
		fw_priv->page_array_size = 0;
		fw_priv->nr_pages = 0;
		fw_priv->fw->size = 0;
		fw_priv->alloc_size = 0;
		set_bit(FW_STATUS_LOADING, &fw_priv->status);
		mutex_unlock(&fw_lock);
		break;
	case 0:
		if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) {
			vfree(fw_priv->fw->data);
			fw_priv->fw->data = vmap(fw_priv->pages,
						 fw_priv->nr_pages,
						 0, PAGE_KERNEL_RO);
			if (!fw_priv->fw->data) {
				dev_err(dev, "%s: vmap() failed\n", __func__);
				goto err;
			}
			/* Pages will be freed by vfree() */
			fw_priv->pages = NULL;
			fw_priv->page_array_size = 0;
			fw_priv->nr_pages = 0;
			complete(&fw_priv->completion);
			clear_bit(FW_STATUS_LOADING, &fw_priv->status);
			break;
@@ -167,6 +192,7 @@ static ssize_t firmware_loading_store(struct device *dev,
		dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading);
		/* fallthrough */
	case -1:
	err:
		fw_load_abort(fw_priv);
		break;
	}
@@ -191,8 +217,28 @@ firmware_data_read(struct kobject *kobj, struct bin_attribute *bin_attr,
		ret_count = -ENODEV;
		goto out;
	}
	ret_count = memory_read_from_buffer(buffer, count, &offset,
						fw->data, fw->size);
	if (offset > fw->size)
		return 0;
	if (count > fw->size - offset)
		count = fw->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(fw_priv->pages[page_nr]);

		memcpy(buffer, page_data + page_ofs, page_cnt);

		kunmap(fw_priv->pages[page_nr]);
		buffer += page_cnt;
		offset += page_cnt;
		count -= page_cnt;
	}
out:
	mutex_unlock(&fw_lock);
	return ret_count;
@@ -201,27 +247,39 @@ out:
static int
fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
{
	u8 *new_data;
	int new_size = fw_priv->alloc_size;
	int pages_needed = ALIGN(min_size, PAGE_SIZE) >> PAGE_SHIFT;

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

	new_size = ALIGN(min_size, PAGE_SIZE);
	new_data = vmalloc(new_size);
	if (!new_data) {
		printk(KERN_ERR "%s: unable to alloc buffer\n", __func__);
		/* Make sure that we don't keep incomplete data */
		new_pages = kmalloc(new_array_size * sizeof(void *),
				    GFP_KERNEL);
		if (!new_pages) {
			fw_load_abort(fw_priv);
			return -ENOMEM;
		}
	fw_priv->alloc_size = new_size;
	if (fw_priv->fw->data) {
		memcpy(new_data, fw_priv->fw->data, fw_priv->fw->size);
		vfree(fw_priv->fw->data);
		memcpy(new_pages, fw_priv->pages,
		       fw_priv->page_array_size * sizeof(void *));
		memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) *
		       (new_array_size - fw_priv->page_array_size));
		kfree(fw_priv->pages);
		fw_priv->pages = new_pages;
		fw_priv->page_array_size = new_array_size;
	}

	while (fw_priv->nr_pages < pages_needed) {
		fw_priv->pages[fw_priv->nr_pages] =
			alloc_page(GFP_KERNEL | __GFP_HIGHMEM);

		if (!fw_priv->pages[fw_priv->nr_pages]) {
			fw_load_abort(fw_priv);
			return -ENOMEM;
		}
		fw_priv->nr_pages++;
	}
	fw_priv->fw->data = new_data;
	BUG_ON(min_size > fw_priv->alloc_size);
	return 0;
}

@@ -258,10 +316,25 @@ firmware_data_write(struct kobject *kobj, struct bin_attribute *bin_attr,
	if (retval)
		goto out;

	memcpy((u8 *)fw->data + offset, buffer, count);

	fw->size = max_t(size_t, offset + count, fw->size);
	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(fw_priv->pages[page_nr]);

		memcpy(page_data + page_ofs, buffer, page_cnt);

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

	fw->size = max_t(size_t, offset, fw->size);
out:
	mutex_unlock(&fw_lock);
	return retval;
@@ -277,7 +350,11 @@ static struct bin_attribute firmware_attr_data_tmpl = {
static void fw_dev_release(struct device *dev)
{
	struct firmware_priv *fw_priv = dev_get_drvdata(dev);
	int i;

	for (i = 0; i < fw_priv->nr_pages; i++)
		__free_page(fw_priv->pages[i]);
	kfree(fw_priv->pages);
	kfree(fw_priv);
	kfree(dev);