fixup kernel loading firmware
[cascardo/linux.git] / drivers / base / firmware_class.c
index 5401814..84ab7d9 100644 (file)
 #include <linux/firmware.h>
 #include <linux/slab.h>
 #include <linux/sched.h>
+#include <linux/file.h>
+#include <linux/list.h>
+#include <linux/async.h>
+#include <linux/pm.h>
+#include <linux/suspend.h>
+#include <linux/syscore_ops.h>
+
+#include <generated/utsrelease.h>
+
+#include "base.h"
 
 #define to_dev(obj) container_of(obj, struct device, kobj)
 
@@ -28,6 +38,68 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
 MODULE_DESCRIPTION("Multi purpose firmware loading support");
 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)
+{
+       long size;
+       char *buf;
+
+       size = fw_file_size(file);
+       if (size < 0)
+               return false;
+       buf = vmalloc(size);
+       if (!buf)
+               return false;
+       if (kernel_read(file, 0, buf, size) != 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 */
 
 #ifdef CONFIG_FW_LOADER
@@ -199,11 +271,14 @@ static ssize_t firmware_loading_show(struct device *dev,
 static void firmware_free_data(const struct firmware *fw)
 {
        int i;
-       vunmap(fw->data);
        if (fw->pages) {
+               vunmap(fw->data);
                for (i = 0; i < PFN_UP(fw->size); i++)
                        __free_page(fw->pages[i]);
                kfree(fw->pages);
+       /* Loaded directly? */
+       } else {
+               vfree(fw->data);
        }
 }
 
@@ -492,6 +567,11 @@ _request_firmware_prepare(const struct firmware **firmware_p, const char *name,
                return NULL;
        }
 
+       if (fw_get_filesystem_firmware(firmware, name)) {
+               dev_dbg(device, "firmware: direct-loading firmware %s\n", name);
+               return NULL;
+       }
+
        fw_priv = fw_create_instance(firmware, name, device, uevent, nowait);
        if (IS_ERR(fw_priv)) {
                release_firmware(firmware);
@@ -594,7 +674,8 @@ request_firmware(const struct firmware **firmware_p, const char *name,
                return PTR_RET(fw_priv);
 
        ret = usermodehelper_read_trylock();
-       if (WARN_ON(ret)) {
+       if (ret) {
+               kfree(fw_priv);
                dev_err(device, "firmware: %s will not be loaded\n", name);
        } else {
                ret = _request_firmware_load(fw_priv, true,