|
@@ -21,18 +21,85 @@
|
|
#include <linux/firmware.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched.h>
|
|
|
|
+#include <linux/file.h>
|
|
#include <linux/list.h>
|
|
#include <linux/list.h>
|
|
#include <linux/async.h>
|
|
#include <linux/async.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/syscore_ops.h>
|
|
|
|
|
|
|
|
+#include <generated/utsrelease.h>
|
|
|
|
+
|
|
#include "base.h"
|
|
#include "base.h"
|
|
|
|
|
|
MODULE_AUTHOR("Manuel Estrada Sainz");
|
|
MODULE_AUTHOR("Manuel Estrada Sainz");
|
|
MODULE_DESCRIPTION("Multi purpose firmware loading support");
|
|
MODULE_DESCRIPTION("Multi purpose firmware loading support");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
|
|
+static const char *fw_path[] = {
|
|
|
|
+ "/lib/firmware/updates/" UTS_RELEASE,
|
|
|
|
+ "/lib/firmware/updates",
|
|
|
|
+ "/lib/firmware/" UTS_RELEASE,
|
|
|
|
+ "/lib/firmware"
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Don't inline this: 'struct kstat' is biggish */
|
|
|
|
+static noinline long fw_file_size(struct file *file)
|
|
|
|
+{
|
|
|
|
+ struct kstat st;
|
|
|
|
+ if (vfs_getattr(file->f_path.mnt, file->f_path.dentry, &st))
|
|
|
|
+ return -1;
|
|
|
|
+ if (!S_ISREG(st.mode))
|
|
|
|
+ return -1;
|
|
|
|
+ if (st.size != (long)st.size)
|
|
|
|
+ return -1;
|
|
|
|
+ return st.size;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static bool fw_read_file_contents(struct file *file, struct firmware *fw)
|
|
|
|
+{
|
|
|
|
+ loff_t pos;
|
|
|
|
+ long size;
|
|
|
|
+ char *buf;
|
|
|
|
+
|
|
|
|
+ size = fw_file_size(file);
|
|
|
|
+ if (size < 0)
|
|
|
|
+ return false;
|
|
|
|
+ buf = vmalloc(size);
|
|
|
|
+ if (!buf)
|
|
|
|
+ return false;
|
|
|
|
+ pos = 0;
|
|
|
|
+ if (vfs_read(file, buf, size, &pos) != size) {
|
|
|
|
+ vfree(buf);
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ fw->data = buf;
|
|
|
|
+ fw->size = size;
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static bool fw_get_filesystem_firmware(struct firmware *fw, const char *name)
|
|
|
|
+{
|
|
|
|
+ int i;
|
|
|
|
+ bool success = false;
|
|
|
|
+ char *path = __getname();
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < ARRAY_SIZE(fw_path); i++) {
|
|
|
|
+ struct file *file;
|
|
|
|
+ snprintf(path, PATH_MAX, "%s/%s", fw_path[i], name);
|
|
|
|
+
|
|
|
|
+ file = filp_open(path, O_RDONLY, 0);
|
|
|
|
+ if (IS_ERR(file))
|
|
|
|
+ continue;
|
|
|
|
+ success = fw_read_file_contents(file, fw);
|
|
|
|
+ fput(file);
|
|
|
|
+ if (success)
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ __putname(path);
|
|
|
|
+ return success;
|
|
|
|
+}
|
|
|
|
+
|
|
/* Builtin firmware support */
|
|
/* Builtin firmware support */
|
|
|
|
|
|
#ifdef CONFIG_FW_LOADER
|
|
#ifdef CONFIG_FW_LOADER
|
|
@@ -346,7 +413,11 @@ static ssize_t firmware_loading_show(struct device *dev,
|
|
/* firmware holds the ownership of pages */
|
|
/* firmware holds the ownership of pages */
|
|
static void firmware_free_data(const struct firmware *fw)
|
|
static void firmware_free_data(const struct firmware *fw)
|
|
{
|
|
{
|
|
- WARN_ON(!fw->priv);
|
|
|
|
|
|
+ /* Loaded directly? */
|
|
|
|
+ if (!fw->priv) {
|
|
|
|
+ vfree(fw->data);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
fw_free_buf(fw->priv);
|
|
fw_free_buf(fw->priv);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -709,6 +780,11 @@ _request_firmware_prepare(const struct firmware **firmware_p, const char *name,
|
|
return NULL;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (fw_get_filesystem_firmware(firmware, name)) {
|
|
|
|
+ dev_dbg(device, "firmware: direct-loading firmware %s\n", name);
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+
|
|
ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf);
|
|
ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf);
|
|
if (!ret)
|
|
if (!ret)
|
|
fw_priv = fw_create_instance(firmware, name, device,
|
|
fw_priv = fw_create_instance(firmware, name, device,
|