request_firmware() backport to 2.4 kernels

From: Manuel Estrada Sainz (ranty@debian.org)
Date: Sat Jul 26 2003 - 07:16:38 EST


 Hi,
 
 A while back request_firmware() was added to the 2.5 kernel series to
 support firmware needing drivers keeping the firmware images in
 userspace. And I also backported it to the 2.4 kernel series on top of
 procfs, but Marcelo didn't answer emails relating to it (there where
 probably other more important matters back then).
 
 Since then, the 2.4 backport has been deployed and tested with
 orinoco_usb driver variant (http://orinoco-usb.alioth.debian.org/),
 as you can see in the download statistics in alioth, there has been
 more than 400 downloads of the request_firmware enabled version
 (0.2.1). And drivers on the 2.5/2.6 series are being ported to use
 request_firmware() interface.

 Would it be possible to include it in the -aa kernel tree? That would
 make it accessible to a wider audience for testing, and make it easier
 for developers to backport their drivers to the 2.4
 series.

 Attached goes the patch against current 2.4-bk-cvs.

 Have a nice day

         Manuel

-- 
--- Manuel Estrada Sainz <ranty@debian.org>
                         <ranty@bigfoot.com>
			 <ranty@users.sourceforge.net>
------------------------ <manuel.estrada@hispalinux.es> -------------------
Let us have the serenity to accept the things we cannot change, courage to
change the things we can, and wisdom to know the difference.

diff --exclude=CVS -urN linux-2.4.orig/Documentation/Configure.help linux-2.4.mine/Documentation/Configure.help --- linux-2.4.orig/Documentation/Configure.help 2003-07-26 13:44:38.000000000 +0200 +++ linux-2.4.mine/Documentation/Configure.help 2003-07-26 13:33:40.000000000 +0200 @@ -26945,6 +26945,12 @@ If unsure, say N. +Hotplug firmware loading support (EXPERIMENTAL) +CONFIG_FW_LOADER + This option is provided for the case where no in-kernel-tree modules require + hotplug firmware loading support, but a module built outside the kernel tree + does. + NatSemi SCx200 support CONFIG_SCx200 This provides basic support for the National Semiconductor SCx200 diff --exclude=CVS -urN linux-2.4.orig/Documentation/firmware_class/README linux-2.4.mine/Documentation/firmware_class/README --- linux-2.4.orig/Documentation/firmware_class/README 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.4.mine/Documentation/firmware_class/README 2003-06-15 16:29:12.000000000 +0200 @@ -0,0 +1,58 @@ + + request_firmware() hotplug interface: + ------------------------------------ + Copyright (C) 2003 Manuel Estrada Sainz <ranty@debian.org> + + Why: + --- + + Today, the most extended way to use firmware in the Linux kernel is linking + it statically in a header file. Which has political and technical issues: + + 1) Some firmware is not legal to redistribute. + 2) The firmware occupies memory permanently, even though it often is just + used once. + 3) Some people, like the Debian crowd, don't consider some firmware free + enough and remove entire drivers (e.g.: keyspan). + + about in-kernel persistence: + --------------------------- + Under some circumstances, as explained below, it would be interesting to keep + firmware images in non-swappable kernel memory or even in the kernel image + (probably within initramfs). + + Note that this functionality has not been implemented. + + - Why OPTIONAL in-kernel persistence may be a good idea sometimes: + + - If the device that needs the firmware is needed to access the + filesystem. When upon some error the device has to be reset and the + firmware reloaded, it won't be possible to get it from userspace. + e.g.: + - A diskless client with a network card that needs firmware. + - The filesystem is stored in a disk behind an scsi device + that needs firmware. + - Replacing buggy DSDT/SSDT ACPI tables on boot. + Note: this would require the persistent objects to be included + within the kernel image, probably within initramfs. + + And the same device can be needed to access the filesystem or not depending + on the setup, so I think that the choice on what firmware to make + persistent should be left to userspace. + + - Why register_firmware()+__init can be useful: + - For boot devices needing firmware. + - To make the transition easier: + The firmware can be declared __init and register_firmware() + called on module_init. Then the firmware is warranted to be + there even if "firmware hotplug userspace" is not there yet or + it doesn't yet provide the needed firmware. + Once the firmware is widely available in userspace, it can be + removed from the kernel. Or made optional (CONFIG_.*_FIRMWARE). + + In either case, if firmware hotplug support is there, it can move the + firmware out of kernel memory into the real filesystem for later + usage. + + Note: If persistence is implemented on top of initramfs, + register_firmware() may not be appropriate. diff --exclude=CVS -urN linux-2.4.orig/Documentation/firmware_class/firmware_sample_driver.c linux-2.4.mine/Documentation/firmware_class/firmware_sample_driver.c --- linux-2.4.orig/Documentation/firmware_class/firmware_sample_driver.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.4.mine/Documentation/firmware_class/firmware_sample_driver.c 2003-06-15 14:03:47.000000000 +0200 @@ -0,0 +1,121 @@ +/* + * firmware_sample_driver.c - + * + * Copyright (c) 2003 Manuel Estrada Sainz <ranty@debian.org> + * + * Sample code on how to use request_firmware() from drivers. + * + * Note that register_firmware() is currently useless. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/string.h> + +#include "linux/firmware.h" + +#define WE_CAN_NEED_FIRMWARE_BEFORE_USERSPACE_IS_AVAILABLE +#ifdef WE_CAN_NEED_FIRMWARE_BEFORE_USERSPACE_IS_AVAILABLE +char __init inkernel_firmware[] = "let's say that this is firmware\n"; +#endif + +static char ghost_device[] = "ghost0"; + +static void sample_firmware_load(char *firmware, int size) +{ + u8 buf[size+1]; + memcpy(buf, firmware, size); + buf[size] = '\0'; + printk("firmware_sample_driver: firmware: %s\n", buf); +} + +static void sample_probe_default(void) +{ + /* uses the default method to get the firmware */ + const struct firmware *fw_entry; + printk("firmware_sample_driver: a ghost device got inserted :)\n"); + + if(request_firmware(&fw_entry, "sample_driver_fw", ghost_device)!=0) + { + printk(KERN_ERR + "firmware_sample_driver: Firmware not available\n"); + return; + } + + sample_firmware_load(fw_entry->data, fw_entry->size); + + release_firmware(fw_entry); + + /* finish setting up the device */ +} +static void sample_probe_specific(void) +{ + /* Uses some specific hotplug support to get the firmware from + * userspace directly into the hardware, or via some sysfs file */ + + /* NOTE: This currently doesn't work */ + + printk("firmware_sample_driver: a ghost device got inserted :)\n"); + + if(request_firmware(NULL, "sample_driver_fw", ghost_device)!=0) + { + printk(KERN_ERR + "firmware_sample_driver: Firmware load failed\n"); + return; + } + + /* request_firmware blocks until userspace finished, so at + * this point the firmware should be already in the device */ + + /* finish setting up the device */ +} +static void sample_probe_async_cont(const struct firmware *fw, void *context) +{ + if(!fw){ + printk(KERN_ERR + "firmware_sample_driver: firmware load failed\n"); + return; + } + + printk("firmware_sample_driver: device pointer \"%s\"\n", + (char *)context); + sample_firmware_load(fw->data, fw->size); +} +static void sample_probe_async(void) +{ + /* Let's say that I can't sleep */ + int error; + error = request_firmware_nowait (THIS_MODULE, + "sample_driver_fw", ghost_device, + "my device pointer", + sample_probe_async_cont); + if(error){ + printk(KERN_ERR + "firmware_sample_driver:" + " request_firmware_nowait failed\n"); + } +} + +static int sample_init(void) +{ +#ifdef WE_CAN_NEED_FIRMWARE_BEFORE_USERSPACE_IS_AVAILABLE + register_firmware("sample_driver_fw", inkernel_firmware, + sizeof(inkernel_firmware)); +#endif + /* since there is no real hardware insertion I just call the + * sample probe functions here */ + sample_probe_specific(); + sample_probe_default(); + sample_probe_async(); + return 0; +} +static void __exit sample_exit(void) +{ +} + +module_init (sample_init); +module_exit (sample_exit); + +MODULE_LICENSE("GPL"); diff --exclude=CVS -urN linux-2.4.orig/Documentation/firmware_class/hotplug-script linux-2.4.mine/Documentation/firmware_class/hotplug-script --- linux-2.4.orig/Documentation/firmware_class/hotplug-script 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.4.mine/Documentation/firmware_class/hotplug-script 2003-06-15 13:45:00.000000000 +0200 @@ -0,0 +1,16 @@ +#!/bin/sh + +# Simple hotplug script sample: +# +# Both $DEVPATH and $FIRMWARE are already provided in the environment. + +HOTPLUG_FW_DIR=/usr/lib/hotplug/firmware/ + +echo 1 > /sysfs/$DEVPATH/loading +cat $HOTPLUG_FW_DIR/$FIRMWARE > /sysfs/$DEVPATH/data +echo 0 > /sysfs/$DEVPATH/loading + +# To cancel the load in case of error: +# +# echo -1 > /sysfs/$DEVPATH/loading +# diff --exclude=CVS -urN linux-2.4.orig/include/linux/firmware.h linux-2.4.mine/include/linux/firmware.h --- linux-2.4.orig/include/linux/firmware.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.4.mine/include/linux/firmware.h 2003-05-24 17:56:40.000000000 +0200 @@ -0,0 +1,20 @@ +#ifndef _LINUX_FIRMWARE_H +#define _LINUX_FIRMWARE_H +#include <linux/module.h> +#include <linux/types.h> +#define FIRMWARE_NAME_MAX 30 +struct firmware { + size_t size; + u8 *data; +}; +int request_firmware (const struct firmware **fw, const char *name, + const char *device); +int request_firmware_nowait ( + struct module *module, + const char *name, const char *device, void *context, + void (*cont)(const struct firmware *fw, void *context)); +/* On 2.5 'device' is 'struct device *' */ + +void release_firmware (const struct firmware *fw); +void register_firmware (const char *name, const u8 *data, size_t size); +#endif diff --exclude=CVS -urN linux-2.4.orig/lib/Config.in linux-2.4.mine/lib/Config.in --- linux-2.4.orig/lib/Config.in 2003-07-26 13:44:38.000000000 +0200 +++ linux-2.4.mine/lib/Config.in 2003-07-26 13:42:15.000000000 +0200 @@ -41,4 +41,8 @@ fi fi +if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then + tristate 'Hotplug firmware loading support (EXPERIMENTAL)' CONFIG_FW_LOADER +fi + endmenu diff --exclude=CVS -urN linux-2.4.orig/lib/Makefile linux-2.4.mine/lib/Makefile --- linux-2.4.orig/lib/Makefile 2003-07-26 13:44:38.000000000 +0200 +++ linux-2.4.mine/lib/Makefile 2003-06-21 19:58:58.000000000 +0200 @@ -9,11 +9,12 @@ L_TARGET := lib.a export-objs := cmdline.o dec_and_lock.o rwsem-spinlock.o rwsem.o \ - rbtree.o crc32.o + rbtree.o crc32.o firmware_class.o obj-y := errno.o ctype.o string.o vsprintf.o brlock.o cmdline.o \ bust_spinlocks.o rbtree.o dump_stack.o +obj-$(CONFIG_FW_LOADER) += firmware_class.o obj-$(CONFIG_RWSEM_GENERIC_SPINLOCK) += rwsem-spinlock.o obj-$(CONFIG_RWSEM_XCHGADD_ALGORITHM) += rwsem.o diff --exclude=CVS -urN linux-2.4.orig/lib/firmware_class.c linux-2.4.mine/lib/firmware_class.c --- linux-2.4.orig/lib/firmware_class.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.4.mine/lib/firmware_class.c 2003-06-24 23:39:07.000000000 +0200 @@ -0,0 +1,557 @@ +/* + * firmware_class.c - Multi purpose firmware loading support + * + * Copyright (c) 2003 Manuel Estrada Sainz <ranty@debian.org> + * + * Please see Documentation/firmware_class/ for more information. + * + */ +/* + * Based on kernel/kmod.c and drivers/usb/usb.c + */ +/* + kernel/kmod.c + Kirk Petersen + + Reorganized not to be a daemon by Adam Richter, with guidance + from Greg Zornetzer. + + Modified to avoid chroot and file sharing problems. + Mikael Pettersson + + Limit the concurrent number of kmod modprobes to catch loops from + "modprobe needs a service that is in a module". + Keith Owens <kaos@ocs.com.au> December 1999 + + Unblock all signals when we exec a usermode process. + Shuu Yamaguchi <shuu@wondernetworkresources.com> December 2000 +*/ +/* + * drivers/usb/usb.c + * + * (C) Copyright Linus Torvalds 1999 + * (C) Copyright Johannes Erdfelt 1999-2001 + * (C) Copyright Andreas Gal 1999 + * (C) Copyright Gregory P. Smith 1999 + * (C) Copyright Deti Fliegl 1999 (new USB architecture) + * (C) Copyright Randy Dunlap 2000 + * (C) Copyright David Brownell 2000 (kernel hotplug, usb_device_id) + * (C) Copyright Yggdrasil Computing, Inc. 2000 + * (usb_device_id matching changes by Adam J. Richter) + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/kmod.h> +#include <linux/proc_fs.h> +#include <linux/vmalloc.h> +#include <asm/hardirq.h> + +#include "linux/firmware.h" + +MODULE_AUTHOR("Manuel Estrada Sainz <ranty@debian.org>"); +MODULE_DESCRIPTION("Multi purpose firmware loading support"); +MODULE_LICENSE("GPL"); + +#define err(format, arg...) \ + printk(KERN_ERR "%s:%s: " format "\n",__FILE__, __FUNCTION__ , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING "%s:%s: " format "\n",__FILE__, __FUNCTION__ , ## arg) +#define dbg(format, arg...) \ + printk(KERN_DEBUG "%s:%s: " format "\n",__FILE__, __FUNCTION__ , ## arg) + +static int loading_timeout = 10; /* In seconds */ +static struct proc_dir_entry *proc_dir_timeout; +static struct proc_dir_entry *proc_dir; + +static int +call_helper(char *verb, const char *name, const char *device) +{ + char *argv[3], **envp, *buf, *scratch; + int i = 0; + + int retval = 0; + + if (!hotplug_path[0]) + return -ENOENT; + if (in_interrupt()) { + err("in_interrupt"); + return -EFAULT; + } + if (!current->fs->root) { + warn("call_policy %s -- no FS yet", verb); + return -EPERM; + } + + if (!(envp = (char **) kmalloc(20 * sizeof (char *), GFP_KERNEL))) { + err("unable to allocate envp"); + return -ENOMEM; + } + if (!(buf = kmalloc(256, GFP_KERNEL))) { + kfree(envp); + err("unable to allocate buf"); + return -ENOMEM; + } + + /* only one standardized param to hotplug command: type */ + argv[0] = hotplug_path; + argv[1] = "firmware"; + argv[2] = 0; + + /* minimal command environment */ + envp[i++] = "HOME=/"; + envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + +#ifdef DEBUG + /* hint that policy agent should enter no-stdout debug mode */ + envp[i++] = "DEBUG=kernel"; +#endif + scratch = buf; + + if (device) { + envp[i++] = scratch; + scratch += snprintf(scratch, FIRMWARE_NAME_MAX+25, + "DEVPATH=/driver/firmware/%s", device) + 1; + } + + envp[i++] = scratch; + scratch += sprintf(scratch, "ACTION=%s", verb) + 1; + + envp[i++] = scratch; + scratch += snprintf(scratch, FIRMWARE_NAME_MAX, + "FIRMWARE=%s", name) + 1; + + envp[i++] = 0; + + dbg("firmware: %s %s %s", argv[0], argv[1], verb); + + retval = call_usermodehelper(argv[0], argv, envp); + if (retval) { + printk("call_usermodehelper return %d\n", retval); + } + + kfree(buf); + kfree(envp); + return retval; +} + +struct firmware_priv { + struct completion completion; + struct proc_dir_entry *proc_dir; + struct proc_dir_entry *attr_data; + struct proc_dir_entry *attr_loading; + struct firmware *fw; + int loading; + int abort; + int alloc_size; + struct timer_list timeout; +}; + +static int +firmware_timeout_show(char *buf, char **start, off_t off, + int count, int *eof, void *data) +{ + return sprintf(buf, "%d\n", loading_timeout); +} + +/** + * firmware_timeout_store: + * Description: + * Sets the number of seconds to wait for the firmware. Once + * this expires an error will be return to the driver and no + * firmware will be provided. + * + * Note: zero means 'wait for ever' + * + **/ +static int +firmware_timeout_store(struct file *file, const char *buf, + unsigned long count, void *data) +{ + loading_timeout = simple_strtol(buf, NULL, 10); + return count; +} + +static ssize_t +firmware_loading_show(char *buf, char **start, off_t off, + int count, int *eof, void *data) +{ + struct firmware_priv *fw_priv = data; + return sprintf(buf, "%d\n", fw_priv->loading); +} + +/** + * firmware_loading_store: - loading control file + * Description: + * The relevant values are: + * + * 1: Start a load, discarding any previous partial load. + * 0: Conclude the load and handle the data to the driver code. + * -1: Conclude the load with an error and discard any written data. + **/ +static ssize_t +firmware_loading_store(struct file *file, const char *buf, + unsigned long count, void *data) +{ + struct firmware_priv *fw_priv = data; + int prev_loading = fw_priv->loading; + + fw_priv->loading = simple_strtol(buf, NULL, 10); + + switch (fw_priv->loading) { + case -1: + fw_priv->abort = 1; + wmb(); + complete(&fw_priv->completion); + break; + case 1: + kfree(fw_priv->fw->data); + fw_priv->fw->data = NULL; + fw_priv->fw->size = 0; + fw_priv->alloc_size = 0; + break; + case 0: + if (prev_loading == 1) + complete(&fw_priv->completion); + break; + } + + return count; +} + +static ssize_t +firmware_data_read(char *buffer, char **start, off_t offset, + int count, int *eof, void *data) +{ + struct firmware_priv *fw_priv = data; + struct firmware *fw = fw_priv->fw; + + if (offset > fw->size) + return 0; + if (offset + count > fw->size) + count = fw->size - offset; + + memcpy(buffer, fw->data + offset, count); + *start = (void*)count; + return count; +} +static int +fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) +{ + u8 *new_data; + int new_size; + + if (min_size <= fw_priv->alloc_size) + return 0; + if((min_size % PAGE_SIZE) == 0) + new_size = min_size; + else + new_size = (min_size + PAGE_SIZE) & PAGE_MASK; + new_data = vmalloc(new_size); + if (!new_data) { + printk(KERN_ERR "%s: unable to alloc buffer\n", __FUNCTION__); + /* Make sure that we don't keep incomplete data */ + fw_priv->abort = 1; + return -ENOMEM; + } + fw_priv->alloc_size = new_size; + if (fw_priv->fw->data) { + memcpy(new_data, fw_priv->fw->data, fw_priv->fw->size); + vfree(fw_priv->fw->data); + } + fw_priv->fw->data = new_data; + BUG_ON(min_size > fw_priv->alloc_size); + return 0; +} + +/** + * firmware_data_write: + * + * Description: + * + * Data written to the 'data' attribute will be later handled to + * the driver as a firmware image. + **/ +static ssize_t +firmware_data_write(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + struct firmware_priv *fw_priv = data; + struct firmware *fw = fw_priv->fw; + int offset = file->f_pos; + int retval; + + retval = fw_realloc_buffer(fw_priv, offset + count); + if (retval) { + printk("%s: retval:%d\n", __FUNCTION__, retval); + return retval; + } + + memcpy(fw->data + offset, buffer, count); + + fw->size = max_t(size_t, offset + count, fw->size); + file->f_pos += count; + return count; +} + +static void +firmware_class_timeout(u_long data) +{ + struct firmware_priv *fw_priv = (struct firmware_priv *) data; + fw_priv->abort = 1; + wmb(); + complete(&fw_priv->completion); +} +static int +fw_setup_class_device(struct firmware_priv **fw_priv_p, + const char *fw_name, const char *device) +{ + int retval; + struct firmware_priv *fw_priv = kmalloc(sizeof (struct firmware_priv), + GFP_KERNEL); + *fw_priv_p = fw_priv; + if (!fw_priv) { + retval = -ENOMEM; + goto out; + } + memset(fw_priv, 0, sizeof (*fw_priv)); + + init_completion(&fw_priv->completion); + + fw_priv->timeout.function = firmware_class_timeout; + fw_priv->timeout.data = (u_long) fw_priv; + init_timer(&fw_priv->timeout); + + retval = -EAGAIN; + fw_priv->proc_dir = create_proc_entry(device, 0644 | S_IFDIR, proc_dir); + if (!fw_priv->proc_dir) + goto err_free_fw_priv; + + fw_priv->attr_data = create_proc_entry("data", 0644 | S_IFREG, + fw_priv->proc_dir); + if (!fw_priv->attr_data) + goto err_remove_dir; + + fw_priv->attr_data->read_proc = firmware_data_read; + fw_priv->attr_data->write_proc = firmware_data_write; + fw_priv->attr_data->data = fw_priv; + + fw_priv->attr_loading = create_proc_entry("loading", 0644 | S_IFREG, + fw_priv->proc_dir); + if (!fw_priv->attr_loading) + goto err_remove_data; + + fw_priv->attr_loading->read_proc = firmware_loading_show; + fw_priv->attr_loading->write_proc = firmware_loading_store; + fw_priv->attr_loading->data = fw_priv; + + retval = 0; + fw_priv->fw = kmalloc(sizeof (struct firmware), GFP_KERNEL); + if (!fw_priv->fw) { + printk(KERN_ERR "%s: kmalloc(struct firmware) failed\n", + __FUNCTION__); + retval = -ENOMEM; + goto err_remove_loading; + } + memset(fw_priv->fw, 0, sizeof (*fw_priv->fw)); + + goto out; + +err_remove_loading: + remove_proc_entry("loading", fw_priv->proc_dir); +err_remove_data: + remove_proc_entry("data", fw_priv->proc_dir); +err_remove_dir: + remove_proc_entry(device, proc_dir); +err_free_fw_priv: + kfree(fw_priv); +out: + return retval; +} +static void +fw_remove_class_device(struct firmware_priv *fw_priv) +{ + remove_proc_entry("loading", fw_priv->proc_dir); + remove_proc_entry("data", fw_priv->proc_dir); + remove_proc_entry(fw_priv->proc_dir->name, proc_dir); +} + +/** + * request_firmware: - request firmware to hotplug and wait for it + * Description: + * @firmware will be used to return a firmware image by the name + * of @name for device @device. + * + * Should be called from user context where sleeping is allowed. + * + * @name will be use as $FIRMWARE in the hotplug environment and + * should be distinctive enough not to be confused with any other + * firmware image for this or any other device. + **/ +int +request_firmware(const struct firmware **firmware, const char *name, + const char *device) +{ + struct firmware_priv *fw_priv; + int retval; + + if (!firmware) { + retval = -EINVAL; + goto out; + } + *firmware = NULL; + + retval = fw_setup_class_device(&fw_priv, name, device); + if (retval) + goto out; + + retval = call_helper("add", name, device); + if (retval) + goto out; + if (loading_timeout) { + fw_priv->timeout.expires = jiffies + loading_timeout * HZ; + add_timer(&fw_priv->timeout); + } + + wait_for_completion(&fw_priv->completion); + + del_timer(&fw_priv->timeout); + fw_remove_class_device(fw_priv); + + if (fw_priv->fw->size && !fw_priv->abort) { + *firmware = fw_priv->fw; + } else { + retval = -ENOENT; + vfree(fw_priv->fw->data); + kfree(fw_priv->fw); + } +out: + kfree(fw_priv); + return retval; +} + +void +release_firmware(const struct firmware *fw) +{ + if (fw) { + vfree(fw->data); + kfree(fw); + } +} + +/** + * register_firmware: - provide a firmware image for later usage + * + * Description: + * Make sure that @data will be available by requesting firmware @name. + * + * Note: This will not be possible until some kind of persistence + * is available. + **/ +void +register_firmware(const char *name, const u8 *data, size_t size) +{ + /* This is meaningless without firmware caching, so until we + * decide if firmware caching is reasonable just leave it as a + * noop */ +} + +/* Async support */ +struct firmware_work { + struct tq_struct work; + struct module *module; + const char *name; + const char *device; + void *context; + void (*cont)(const struct firmware *fw, void *context); +}; + +static void +request_firmware_work_func(void *arg) +{ + struct firmware_work *fw_work = arg; + const struct firmware *fw; + if (!arg) + return; + request_firmware(&fw, fw_work->name, fw_work->device); + fw_work->cont(fw, fw_work->context); + release_firmware(fw); + __MOD_DEC_USE_COUNT(fw_work->module); + kfree(fw_work); +} + +/** + * request_firmware_nowait: + * + * Description: + * Asynchronous variant of request_firmware() for contexts where + * it is not possible to sleep. + * + * @cont will be called asynchronously when the firmware request is over. + * + * @context will be passed over to @cont. + * + * @fw may be %NULL if firmware request fails. + * + **/ +int +request_firmware_nowait( + struct module *module, + const char *name, const char *device, void *context, + void (*cont)(const struct firmware *fw, void *context)) +{ + struct firmware_work *fw_work = kmalloc(sizeof (struct firmware_work), + GFP_ATOMIC); + if (!fw_work) + return -ENOMEM; + if (!try_inc_mod_count(module)) { + kfree(fw_work); + return -EFAULT; + } + + *fw_work = (struct firmware_work) { + .module = module, + .name = name, + .device = device, + .context = context, + .cont = cont, + }; + INIT_TQUEUE(&fw_work->work, request_firmware_work_func, fw_work); + + schedule_task(&fw_work->work); + return 0; +} + +static int __init +firmware_class_init(void) +{ + proc_dir = create_proc_entry("driver/firmware", 0755 | S_IFDIR, NULL); + if (!proc_dir) + return -EAGAIN; + proc_dir_timeout = create_proc_entry("timeout", + 0644 | S_IFREG, proc_dir); + if (!proc_dir_timeout) { + remove_proc_entry("driver/firmware", NULL); + return -EAGAIN; + } + proc_dir_timeout->read_proc = firmware_timeout_show; + proc_dir_timeout->write_proc = firmware_timeout_store; + return 0; +} +static void __exit +firmware_class_exit(void) +{ + remove_proc_entry("timeout", proc_dir); + remove_proc_entry("driver/firmware", NULL); +} + +module_init(firmware_class_init); +module_exit(firmware_class_exit); + +EXPORT_SYMBOL(release_firmware); +EXPORT_SYMBOL(request_firmware); +EXPORT_SYMBOL(request_firmware_nowait); +EXPORT_SYMBOL(register_firmware);

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Thu Jul 31 2003 - 22:00:28 EST