Commit 89cbae1f authored by Manuel Estrada Sainz's avatar Manuel Estrada Sainz Committed by Greg Kroah-Hartman

[PATCH] DRIVER: request_firmware() hotplug interface documentation

parent 58aae7d9
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.
/*
* 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/device.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 struct device ghost_device = {
.name = "Ghost Device",
.bus_id = "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
device_initialize(&ghost_device);
/* 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");
/*
* firmware_sample_firmware_class.c -
*
* Copyright (c) 2003 Manuel Estrada Sainz <ranty@debian.org>
*
* NOTE: This is just a probe of concept, if you think that your driver would
* be well served by this mechanism please contact me first.
*
* DON'T USE THIS CODE AS IS
*
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <asm/hardirq.h>
#include "linux/firmware.h"
MODULE_AUTHOR("Manuel Estrada Sainz <ranty@debian.org>");
MODULE_DESCRIPTION("Hackish sample for using firmware class directly");
MODULE_LICENSE("GPL");
static inline struct class_device *to_class_dev(struct kobject *obj)
{
return container_of(obj,struct class_device,kobj);
}
static inline
struct class_device_attribute *to_class_dev_attr(struct attribute *_attr)
{
return container_of(_attr,struct class_device_attribute,attr);
}
int sysfs_create_bin_file(struct kobject * kobj, struct bin_attribute * attr);
int sysfs_remove_bin_file(struct kobject * kobj, struct bin_attribute * attr);
struct firmware_priv {
char fw_id[FIRMWARE_NAME_MAX];
s32 loading:2;
u32 abort:1;
};
extern struct class firmware_class;
static ssize_t firmware_loading_show(struct class_device *class_dev, char *buf)
{
struct firmware_priv *fw_priv = class_get_devdata(class_dev);
return sprintf(buf, "%d\n", fw_priv->loading);
}
static ssize_t firmware_loading_store(struct class_device *class_dev,
const char *buf, size_t count)
{
struct firmware_priv *fw_priv = class_get_devdata(class_dev);
int prev_loading = fw_priv->loading;
fw_priv->loading = simple_strtol(buf, NULL, 10);
switch(fw_priv->loading){
case -1:
/* abort load an panic */
break;
case 1:
/* setup load */
break;
case 0:
if(prev_loading==1){
/* finish load and get the device back to working
* state */
}
break;
}
return count;
}
static CLASS_DEVICE_ATTR(loading, 0644,
firmware_loading_show, firmware_loading_store);
static ssize_t firmware_data_read(struct kobject *kobj,
char *buffer, loff_t offset, size_t count)
{
struct class_device *class_dev = to_class_dev(kobj);
struct firmware_priv *fw_priv = class_get_devdata(class_dev);
/* read from the devices firmware memory */
return count;
}
static ssize_t firmware_data_write(struct kobject *kobj,
char *buffer, loff_t offset, size_t count)
{
struct class_device *class_dev = to_class_dev(kobj);
struct firmware_priv *fw_priv = class_get_devdata(class_dev);
/* write to the devices firmware memory */
return count;
}
static struct bin_attribute firmware_attr_data = {
.attr = {.name = "data", .mode = 0644},
.size = 0,
.read = firmware_data_read,
.write = firmware_data_write,
};
static int fw_setup_class_device(struct class_device *class_dev,
const char *fw_name,
struct device *device)
{
int retval = 0;
struct firmware_priv *fw_priv = kmalloc(sizeof(struct firmware_priv),
GFP_KERNEL);
if(!fw_priv){
retval = -ENOMEM;
goto out;
}
memset(fw_priv, 0, sizeof(*fw_priv));
memset(class_dev, 0, sizeof(*class_dev));
strncpy(fw_priv->fw_id, fw_name, FIRMWARE_NAME_MAX);
fw_priv->fw_id[FIRMWARE_NAME_MAX-1] = '\0';
strncpy(class_dev->class_id, device->bus_id, BUS_ID_SIZE);
class_dev->class_id[BUS_ID_SIZE-1] = '\0';
class_dev->dev = device;
class_dev->class = &firmware_class,
class_set_devdata(class_dev, fw_priv);
retval = class_device_register(class_dev);
if (retval){
printk(KERN_ERR "%s: class_device_register failed\n",
__FUNCTION__);
goto error_free_fw_priv;
}
retval = sysfs_create_bin_file(&class_dev->kobj, &firmware_attr_data);
if (retval){
printk(KERN_ERR "%s: sysfs_create_bin_file failed\n",
__FUNCTION__);
goto error_unreg_class_dev;
}
retval = class_device_create_file(class_dev,
&class_device_attr_loading);
if (retval){
printk(KERN_ERR "%s: class_device_create_file failed\n",
__FUNCTION__);
goto error_remove_data;
}
goto out;
error_remove_data:
sysfs_remove_bin_file(&class_dev->kobj, &firmware_attr_data);
error_unreg_class_dev:
class_device_unregister(class_dev);
error_free_fw_priv:
kfree(fw_priv);
out:
return retval;
}
static void fw_remove_class_device(struct class_device *class_dev)
{
struct firmware_priv *fw_priv = class_get_devdata(class_dev);
class_device_remove_file(class_dev, &class_device_attr_loading);
sysfs_remove_bin_file(&class_dev->kobj, &firmware_attr_data);
class_device_unregister(class_dev);
}
static struct class_device *class_dev;
static struct device my_device = {
.name = "Sample Device",
.bus_id = "my_dev0",
};
static int __init firmware_sample_init(void)
{
int error;
device_initialize(&my_device);
class_dev = kmalloc(sizeof(struct class_device), GFP_KERNEL);
if(!class_dev)
return -ENOMEM;
error = fw_setup_class_device(class_dev, "my_firmware_image",
&my_device);
if(error){
kfree(class_dev);
return error;
}
return 0;
}
static void __exit firmware_sample_exit(void)
{
struct firmware_priv *fw_priv = class_get_devdata(class_dev);
fw_remove_class_device(class_dev);
kfree(fw_priv);
kfree(class_dev);
}
module_init(firmware_sample_init);
module_exit(firmware_sample_exit);
#!/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
#
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment