|
@@ -27,6 +27,52 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
|
|
|
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
|
|
|
+
|
|
|
enum {
|
|
|
FW_STATUS_LOADING,
|
|
|
FW_STATUS_DONE,
|
|
@@ -40,7 +86,6 @@ static int loading_timeout = 60; /* In seconds */
|
|
|
static DEFINE_MUTEX(fw_lock);
|
|
|
|
|
|
struct firmware_priv {
|
|
|
- char *fw_id;
|
|
|
struct completion completion;
|
|
|
struct bin_attribute attr_data;
|
|
|
struct firmware *fw;
|
|
@@ -48,18 +93,11 @@ struct firmware_priv {
|
|
|
struct page **pages;
|
|
|
int nr_pages;
|
|
|
int page_array_size;
|
|
|
- const char *vdata;
|
|
|
struct timer_list timeout;
|
|
|
+ bool nowait;
|
|
|
+ char fw_id[];
|
|
|
};
|
|
|
|
|
|
-#ifdef CONFIG_FW_LOADER
|
|
|
-extern struct builtin_fw __start_builtin_fw[];
|
|
|
-extern struct builtin_fw __end_builtin_fw[];
|
|
|
-#else /* Module case. Avoid ifdefs later; it'll all optimise out */
|
|
|
-static struct builtin_fw *__start_builtin_fw;
|
|
|
-static struct builtin_fw *__end_builtin_fw;
|
|
|
-#endif
|
|
|
-
|
|
|
static void
|
|
|
fw_load_abort(struct firmware_priv *fw_priv)
|
|
|
{
|
|
@@ -100,9 +138,25 @@ firmware_timeout_store(struct class *class,
|
|
|
return count;
|
|
|
}
|
|
|
|
|
|
-static CLASS_ATTR(timeout, 0644, firmware_timeout_show, firmware_timeout_store);
|
|
|
+static struct class_attribute firmware_class_attrs[] = {
|
|
|
+ __ATTR(timeout, S_IWUSR | S_IRUGO,
|
|
|
+ firmware_timeout_show, firmware_timeout_store),
|
|
|
+ __ATTR_NULL
|
|
|
+};
|
|
|
+
|
|
|
+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);
|
|
|
|
|
|
-static void fw_dev_release(struct device *dev);
|
|
|
+ module_put(THIS_MODULE);
|
|
|
+}
|
|
|
|
|
|
static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
|
{
|
|
@@ -112,12 +166,15 @@ static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
|
return -ENOMEM;
|
|
|
if (add_uevent_var(env, "TIMEOUT=%i", loading_timeout))
|
|
|
return -ENOMEM;
|
|
|
+ if (add_uevent_var(env, "ASYNC=%d", fw_priv->nowait))
|
|
|
+ return -ENOMEM;
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
static struct class firmware_class = {
|
|
|
.name = "firmware",
|
|
|
+ .class_attrs = firmware_class_attrs,
|
|
|
.dev_uevent = firmware_uevent,
|
|
|
.dev_release = fw_dev_release,
|
|
|
};
|
|
@@ -130,6 +187,17 @@ static ssize_t firmware_loading_show(struct device *dev,
|
|
|
return sprintf(buf, "%d\n", loading);
|
|
|
}
|
|
|
|
|
|
+static void firmware_free_data(const struct firmware *fw)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ vunmap(fw->data);
|
|
|
+ if (fw->pages) {
|
|
|
+ for (i = 0; i < PFN_UP(fw->size); i++)
|
|
|
+ __free_page(fw->pages[i]);
|
|
|
+ kfree(fw->pages);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/* Some architectures don't have PAGE_KERNEL_RO */
|
|
|
#ifndef PAGE_KERNEL_RO
|
|
|
#define PAGE_KERNEL_RO PAGE_KERNEL
|
|
@@ -162,21 +230,21 @@ static ssize_t firmware_loading_store(struct device *dev,
|
|
|
mutex_unlock(&fw_lock);
|
|
|
break;
|
|
|
}
|
|
|
- vfree(fw_priv->fw->data);
|
|
|
- fw_priv->fw->data = NULL;
|
|
|
+ firmware_free_data(fw_priv->fw);
|
|
|
+ memset(fw_priv->fw, 0, sizeof(struct firmware));
|
|
|
+ /* If the pages are not owned by 'struct firmware' */
|
|
|
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;
|
|
|
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);
|
|
|
+ vunmap(fw_priv->fw->data);
|
|
|
fw_priv->fw->data = vmap(fw_priv->pages,
|
|
|
fw_priv->nr_pages,
|
|
|
0, PAGE_KERNEL_RO);
|
|
@@ -184,7 +252,10 @@ static ssize_t firmware_loading_store(struct device *dev,
|
|
|
dev_err(dev, "%s: vmap() failed\n", __func__);
|
|
|
goto err;
|
|
|
}
|
|
|
- /* Pages will be freed by vfree() */
|
|
|
+ /* Pages are now owned by 'struct firmware' */
|
|
|
+ fw_priv->fw->pages = fw_priv->pages;
|
|
|
+ fw_priv->pages = NULL;
|
|
|
+
|
|
|
fw_priv->page_array_size = 0;
|
|
|
fw_priv->nr_pages = 0;
|
|
|
complete(&fw_priv->completion);
|
|
@@ -207,8 +278,9 @@ static ssize_t firmware_loading_store(struct device *dev,
|
|
|
static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store);
|
|
|
|
|
|
static ssize_t
|
|
|
-firmware_data_read(struct kobject *kobj, struct bin_attribute *bin_attr,
|
|
|
- char *buffer, loff_t offset, size_t count)
|
|
|
+firmware_data_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 = dev_get_drvdata(dev);
|
|
@@ -291,6 +363,7 @@ fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
|
|
|
|
|
|
/**
|
|
|
* 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
|
|
@@ -301,8 +374,9 @@ fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
|
|
|
* the driver as a firmware image.
|
|
|
**/
|
|
|
static ssize_t
|
|
|
-firmware_data_write(struct kobject *kobj, struct bin_attribute *bin_attr,
|
|
|
- char *buffer, loff_t offset, size_t count)
|
|
|
+firmware_data_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 = dev_get_drvdata(dev);
|
|
@@ -353,21 +427,6 @@ static struct bin_attribute firmware_attr_data_tmpl = {
|
|
|
.write = firmware_data_write,
|
|
|
};
|
|
|
|
|
|
-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->fw_id);
|
|
|
- kfree(fw_priv);
|
|
|
- kfree(dev);
|
|
|
-
|
|
|
- module_put(THIS_MODULE);
|
|
|
-}
|
|
|
-
|
|
|
static void
|
|
|
firmware_class_timeout(u_long data)
|
|
|
{
|
|
@@ -379,8 +438,8 @@ static int fw_register_device(struct device **dev_p, const char *fw_name,
|
|
|
struct device *device)
|
|
|
{
|
|
|
int retval;
|
|
|
- struct firmware_priv *fw_priv = kzalloc(sizeof(*fw_priv),
|
|
|
- GFP_KERNEL);
|
|
|
+ struct firmware_priv *fw_priv =
|
|
|
+ kzalloc(sizeof(*fw_priv) + strlen(fw_name) + 1 , GFP_KERNEL);
|
|
|
struct device *f_dev = kzalloc(sizeof(*f_dev), GFP_KERNEL);
|
|
|
|
|
|
*dev_p = NULL;
|
|
@@ -391,16 +450,9 @@ static int fw_register_device(struct device **dev_p, const char *fw_name,
|
|
|
goto error_kfree;
|
|
|
}
|
|
|
|
|
|
+ strcpy(fw_priv->fw_id, fw_name);
|
|
|
init_completion(&fw_priv->completion);
|
|
|
fw_priv->attr_data = firmware_attr_data_tmpl;
|
|
|
- fw_priv->fw_id = kstrdup(fw_name, GFP_KERNEL);
|
|
|
- if (!fw_priv->fw_id) {
|
|
|
- dev_err(device, "%s: Firmware name allocation failed\n",
|
|
|
- __func__);
|
|
|
- retval = -ENOMEM;
|
|
|
- goto error_kfree;
|
|
|
- }
|
|
|
-
|
|
|
fw_priv->timeout.function = firmware_class_timeout;
|
|
|
fw_priv->timeout.data = (u_long) fw_priv;
|
|
|
init_timer(&fw_priv->timeout);
|
|
@@ -427,7 +479,7 @@ error_kfree:
|
|
|
|
|
|
static int fw_setup_device(struct firmware *fw, struct device **dev_p,
|
|
|
const char *fw_name, struct device *device,
|
|
|
- int uevent)
|
|
|
+ int uevent, bool nowait)
|
|
|
{
|
|
|
struct device *f_dev;
|
|
|
struct firmware_priv *fw_priv;
|
|
@@ -443,6 +495,8 @@ static int fw_setup_device(struct firmware *fw, struct device **dev_p,
|
|
|
|
|
|
fw_priv = dev_get_drvdata(f_dev);
|
|
|
|
|
|
+ fw_priv->nowait = nowait;
|
|
|
+
|
|
|
fw_priv->fw = fw;
|
|
|
sysfs_bin_attr_init(&fw_priv->attr_data);
|
|
|
retval = sysfs_create_bin_file(&f_dev->kobj, &fw_priv->attr_data);
|
|
@@ -470,12 +524,11 @@ out:
|
|
|
|
|
|
static int
|
|
|
_request_firmware(const struct firmware **firmware_p, const char *name,
|
|
|
- struct device *device, int uevent)
|
|
|
+ struct device *device, int uevent, bool nowait)
|
|
|
{
|
|
|
struct device *f_dev;
|
|
|
struct firmware_priv *fw_priv;
|
|
|
struct firmware *firmware;
|
|
|
- struct builtin_fw *builtin;
|
|
|
int retval;
|
|
|
|
|
|
if (!firmware_p)
|
|
@@ -489,21 +542,16 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
- for (builtin = __start_builtin_fw; builtin != __end_builtin_fw;
|
|
|
- builtin++) {
|
|
|
- if (strcmp(name, builtin->name))
|
|
|
- continue;
|
|
|
- dev_info(device, "firmware: using built-in firmware %s\n",
|
|
|
- name);
|
|
|
- firmware->size = builtin->size;
|
|
|
- firmware->data = builtin->data;
|
|
|
+ if (fw_get_builtin_firmware(firmware, name)) {
|
|
|
+ dev_dbg(device, "firmware: using built-in firmware %s\n", name);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
if (uevent)
|
|
|
- dev_info(device, "firmware: requesting %s\n", name);
|
|
|
+ dev_dbg(device, "firmware: requesting %s\n", name);
|
|
|
|
|
|
- retval = fw_setup_device(firmware, &f_dev, name, device, uevent);
|
|
|
+ retval = fw_setup_device(firmware, &f_dev, name, device,
|
|
|
+ uevent, nowait);
|
|
|
if (retval)
|
|
|
goto error_kfree_fw;
|
|
|
|
|
@@ -560,26 +608,18 @@ request_firmware(const struct firmware **firmware_p, const char *name,
|
|
|
struct device *device)
|
|
|
{
|
|
|
int uevent = 1;
|
|
|
- return _request_firmware(firmware_p, name, device, uevent);
|
|
|
+ return _request_firmware(firmware_p, name, device, uevent, false);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* release_firmware: - release the resource associated with a firmware image
|
|
|
* @fw: firmware resource to release
|
|
|
**/
|
|
|
-void
|
|
|
-release_firmware(const struct firmware *fw)
|
|
|
+void release_firmware(const struct firmware *fw)
|
|
|
{
|
|
|
- struct builtin_fw *builtin;
|
|
|
-
|
|
|
if (fw) {
|
|
|
- for (builtin = __start_builtin_fw; builtin != __end_builtin_fw;
|
|
|
- builtin++) {
|
|
|
- if (fw->data == builtin->data)
|
|
|
- goto free_fw;
|
|
|
- }
|
|
|
- vfree(fw->data);
|
|
|
- free_fw:
|
|
|
+ if (!fw_is_builtin_firmware(fw))
|
|
|
+ firmware_free_data(fw);
|
|
|
kfree(fw);
|
|
|
}
|
|
|
}
|
|
@@ -606,7 +646,7 @@ request_firmware_work_func(void *arg)
|
|
|
return 0;
|
|
|
}
|
|
|
ret = _request_firmware(&fw, fw_work->name, fw_work->device,
|
|
|
- fw_work->uevent);
|
|
|
+ fw_work->uevent, true);
|
|
|
|
|
|
fw_work->cont(fw, fw_work->context);
|
|
|
|
|
@@ -670,26 +710,12 @@ request_firmware_nowait(
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static int __init
|
|
|
-firmware_class_init(void)
|
|
|
+static int __init firmware_class_init(void)
|
|
|
{
|
|
|
- int error;
|
|
|
- error = class_register(&firmware_class);
|
|
|
- if (error) {
|
|
|
- printk(KERN_ERR "%s: class_register failed\n", __func__);
|
|
|
- return error;
|
|
|
- }
|
|
|
- error = class_create_file(&firmware_class, &class_attr_timeout);
|
|
|
- if (error) {
|
|
|
- printk(KERN_ERR "%s: class_create_file failed\n",
|
|
|
- __func__);
|
|
|
- class_unregister(&firmware_class);
|
|
|
- }
|
|
|
- return error;
|
|
|
-
|
|
|
+ return class_register(&firmware_class);
|
|
|
}
|
|
|
-static void __exit
|
|
|
-firmware_class_exit(void)
|
|
|
+
|
|
|
+static void __exit firmware_class_exit(void)
|
|
|
{
|
|
|
class_unregister(&firmware_class);
|
|
|
}
|