From a168da208a9533d820270f4534790619f0ee3b42 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 3 Oct 2012 15:58:32 -0700 Subject: [PATCH] firmware: teach the kernel to load firmware files directly from the filesystem This is a first step in allowing people to by-pass udev for loading device firmware. Current versions of udev will deadlock (causing us to block for the 30 second timeout) under some circumstances if the firmware is loaded as part of the module initialization path, and this is causing problems for media drivers in particular. The current patch hardcodes the firmware path that udev uses by default, and will fall back to the legacy udev mode if the firmware cannot be found there. We'd like to add support for both configuring the paths and the fallback behaviour, but in the meantime this hopefully fixes the immediate problem, while also giving us a way forward. [ v2: Some VFS layer interface cleanups suggested by Al Viro ] [ v3: use the default udev paths suggested by Kay Sievers ] Suggested-by: Ivan Kalvachev Acked-by: Greg KH Acked-by: Al Viro Cc: Mauro Carvalho Chehab Cc: Kay Sievers Cc: Ming Lei Signed-off-by: Linus Torvalds --- drivers/base/firmware_class.c | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index a36ea43628d1..e390fff37975 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -21,6 +21,16 @@ #include #include #include +#include +#include +#include +#include +#include +#include + +#include + +#include "base.h" #define to_dev(obj) container_of(obj, struct device, kobj) @@ -28,6 +38,70 @@ 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) +{ + 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 */ #ifdef CONFIG_FW_LOADER @@ -199,6 +273,11 @@ static ssize_t firmware_loading_show(struct device *dev, static void firmware_free_data(const struct firmware *fw) { int i; + /* Loaded directly? */ + if (!fw->priv) { + vfree(fw->data); + return; + } vunmap(fw->data); if (fw->pages) { for (i = 0; i < PFN_UP(fw->size); i++) @@ -492,6 +571,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); -- 2.20.1