Skip to content

Commit

Permalink
firmware: teach the kernel to load firmware files directly from the f…
Browse files Browse the repository at this point in the history
…ilesystem

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 <[email protected]>
Acked-by: Greg KH <[email protected]>
Acked-by: Al Viro <[email protected]>
Cc: Mauro Carvalho Chehab <[email protected]>
Cc: Kay Sievers <[email protected]>
Cc: Ming Lei <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
torvalds committed Oct 3, 2012
1 parent df5a2a1 commit abb139e
Showing 1 changed file with 77 additions and 1 deletion.
78 changes: 77 additions & 1 deletion drivers/base/firmware_class.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,85 @@
#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"

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
Expand Down Expand Up @@ -346,7 +413,11 @@ static ssize_t firmware_loading_show(struct device *dev,
/* firmware holds the ownership of pages */
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);
}

Expand Down Expand Up @@ -709,6 +780,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;
}

ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf);
if (!ret)
fw_priv = fw_create_instance(firmware, name, device,
Expand Down

0 comments on commit abb139e

Please sign in to comment.