lkml.org 
[lkml]   [2003]   [May]   [15]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
Date
From
Subjectrequest_firmware() hotplug interface, third round.
 Hi all,

This time, as Greg suggested, it is implemented on top of 'struct
class' and 'struct class_device' but the driver interface is the same
as last time.

Attached:
firmware.h
firmware_class.c:
The firmware support itself.
firmware_sample_driver.c:
Sample code on how to use from drivers.
hotplug:
A simple hotplug replacement for testing.
Makefile:
The obvious.
README:
Still pertinent pieces from the previous round.

How it works:
- Driver calls request_firmware()
- 'hotplug firmware' gets called with ACCTION=add
- /sysfs/class/firmware/dev_name/{data,loading} show up.

- echo 1 > /sysfs/class/firmware/dev_name/loading
- cat whatever_fw > /sysfs/class/firmware/dev_name/data
- echo 0 > /sysfs/class/firmware/dev_name/loading

- The call to request_firmware() returns with the firmware in a
memory buffer and the driver can finish loading.
- Driver loads the firmware.
- Driver calls release_firmware().

Notes:
- Drivers can use request_firmware() or register their own
'firmware' class_device and implement the same interface for
themselfs. If this code has better acceptance I'll also
provide a sample of the later, and make it actually possible.

- There is a race, the hotplug event gets generated on
class_device_register and before 'data' and 'loading' creation
takes place. Maybe the hotplug support could wait for
'loading' to show up.

- register_firmware can not be implemented without some form of
in-kernel firmware caching. And that is not implemented.

- fwfs could be used for firmware caching behind the scene
allowing register_firmware to be implemented and the other
uses. I could call it blobfs and make a subdirectory within
for firmware purposes.

Hope you like it

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.
#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);
/* Maybe 'device' should be 'struct device *' */

void release_firmware (const struct firmware *fw);
void register_firmware (const char *name, const u8 *data, size_t size);
#endif
#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
#include "firmware.h"

MODULE_AUTHOR("Manuel Estrada Sainz <ranty@debian.org>");
MODULE_DESCRIPTION("Hotplug Firmware Loading Support");
MODULE_LICENSE("GPL");

#define MAX( a, b ) ( ( ( a ) > ( b ) ) ? ( a ) : ( b ) )

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];
struct completion completion;
struct bin_attribute attr_data;
struct firmware *fw;
u8 loading;
int alloc_size;
};

int firmware_class_hotplug(struct class_device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);

struct class firmware_class = {
.name = "firmware",
.hotplug = firmware_class_hotplug,
};


int firmware_class_hotplug(struct class_device *class_dev, char **envp,
int num_envp, char *buffer, int buffer_size)
{
struct firmware_priv *fw_priv = class_get_devdata(class_dev);
int i=0;
char *scratch=buffer;

if (buffer_size < (FIRMWARE_NAME_MAX+10))
return -ENOMEM;

envp [i++] = scratch;
scratch += sprintf(scratch, "FIRMWARE=%s", fw_priv->fw_id) + 1;
return 0;
}

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)
{
int loading = simple_strtol(buf, NULL, 10);
struct firmware_priv *fw_priv = class_get_devdata(class_dev);

if(fw_priv->loading && !loading)
complete(&fw_priv->completion);
fw_priv->loading = loading;
return count;
}
CLASS_DEVICE_ATTR(loading, 0644,
firmware_loading_show, firmware_loading_store);

static ssize_t firmware_data_read(struct kobject *kobj,
struct sysfs_bin_buffer *buffer)
{
struct class_device *class_dev = to_class_dev(kobj);
struct firmware_priv *fw_priv = class_get_devdata(class_dev);
struct firmware *fw = fw_priv->fw;

printk("%s: size:%d count:%d offset:%lld\n", __FUNCTION__,
buffer->size, buffer->count, buffer->offset);

buffer->data = kmalloc(fw->size, GFP_KERNEL);
if(!buffer->data)
return -ENOMEM;
buffer->size = fw->size;
memcpy(buffer->data, fw->data, fw->size);
return buffer->count;
}
static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
{
u8 *new_data;

if (min_size <= fw_priv->alloc_size)
return 0;

new_data = kmalloc(fw_priv->alloc_size+PAGE_SIZE, GFP_KERNEL);
if(!new_data){
printk(KERN_ERR "%s: unable to alloc buffer\n",
__FUNCTION__);
/* Make sure that we don't keep incomplete data */
kfree(fw_priv->fw->data);
fw_priv->fw->data=NULL;
fw_priv->fw->size=0;
return -ENOMEM;
}
fw_priv->alloc_size += PAGE_SIZE;
if(fw_priv->fw->data){
memcpy(new_data, fw_priv->fw->data, fw_priv->fw->size);
kfree(fw_priv->fw->data);
}
fw_priv->fw->data=new_data;
BUG_ON(min_size > fw_priv->alloc_size);
return 0;
}
static ssize_t firmware_data_write(struct kobject *kobj,
struct sysfs_bin_buffer *buffer)
{
struct class_device *class_dev = to_class_dev(kobj);
struct firmware_priv *fw_priv = class_get_devdata(class_dev);
struct firmware *fw = fw_priv->fw;
int retval;

printk("%s: size:%d count:%d offset:%lld\n", __FUNCTION__,
buffer->size, buffer->count, buffer->offset);
retval = fw_realloc_buffer(fw_priv, buffer->offset+buffer->count);
if(retval)
return retval;

memcpy(fw->data+buffer->offset, buffer->data, buffer->count);

buffer->offset += buffer->count;
buffer->size = MAX(buffer->offset, buffer->size);
fw->size=buffer->size;
return buffer->count;
}
static struct bin_attribute firmware_attr_data_tmpl = {
.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,
struct firmware_priv *fw_priv,
const char *fw_name,
const char *dev_name)
{
int retval = 0;

memset(fw_priv, 0, sizeof(*fw_priv));

init_completion(&fw_priv->completion);
memcpy(&fw_priv->attr_data, &firmware_attr_data_tmpl,
sizeof(firmware_attr_data_tmpl));

strncpy(fw_priv->fw_id, fw_name, FIRMWARE_NAME_MAX);
fw_priv->fw_id[FIRMWARE_NAME_MAX-1] = '\0';

strncpy(class_dev->class_id, dev_name, BUS_ID_SIZE);
class_dev->class_id[BUS_ID_SIZE-1] = '\0';

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 out;
}

retval = sysfs_create_bin_file(&class_dev->kobj,
&fw_priv->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;
}

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 error_remove_loading;
}
memset(fw_priv->fw, 0, sizeof(*fw_priv->fw));

goto out;

error_remove_loading:
class_device_remove_file(class_dev, &class_device_attr_loading);
error_remove_data:
sysfs_remove_bin_file(&class_dev->kobj, &fw_priv->attr_data);
error_unreg_class_dev:
class_device_unregister(class_dev);
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, &fw_priv->attr_data);
class_device_unregister(class_dev);
}

int request_firmware (const struct firmware **firmware, const char *name,
const char *device)
/* Maybe 'device' should be 'struct device *' */
{
static struct class_device class_dev = {
.class = &firmware_class,
};
struct firmware_priv fw_priv;
int retval;

if(!firmware){
retval = -EINVAL;
goto out;
}
*firmware=NULL;

retval = fw_setup_class_device(&class_dev, &fw_priv,
name, device);
if(retval)
goto out;

wait_for_completion(&fw_priv.completion);

fw_remove_class_device(&class_dev);

if(fw_priv.fw->size)
*firmware = fw_priv.fw;
else {
retval = -ENOENT;
kfree(fw_priv.fw->data);
kfree(fw_priv.fw);
}
out:
return retval;
}
void release_firmware (const struct firmware *fw)
{
kfree(fw->data);
kfree(fw);
}

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 */
}

static int __init firmware_class_init(void)
{
int error;
error = class_register(&firmware_class);
if (error) {
printk(KERN_ERR "class_register failed\n");
}

return error;

}
static void __exit firmware_class_exit(void)
{
class_unregister(&firmware_class);
}
module_init(firmware_class_init);
module_exit(firmware_class_exit);

EXPORT_SYMBOL(release_firmware);
EXPORT_SYMBOL(request_firmware);
EXPORT_SYMBOL(register_firmware);
EXPORT_SYMBOL(firmware_class);
#!/bin/dash
sleep 1
OUTPUT=/dev/console
exec > $OUTPUT 2> $OUTPUT

TYPE="$1"
echo "TYPE: $TYPE"
if [ "$TYPE" != "firmware" ]; then
echo wrong param
exit 1
fi
if [ "$ACTION" = "add" ]; then
echo 1 > /sysfs/$DEVPATH/loading
echo "Hi there" > /sysfs/$DEVPATH/data
echo 0 > /sysfs/$DEVPATH/loading
ls -lR /sysfs/$DEVPATH
fi
set

request_firmware() hotplug interface:
------------------------------------

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).

Notes:
-----

- Why OPTIONALLY caching the firmware in-kernel 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.
- On embedded systems (like install floppies) where there is no
userspace hotplug support, 'cp firmware_file /firmware/' can be
handy.

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 cache 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 jet or
it doesn't jet 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 (like the provided hotplug scripts do).
# Taken from orinoco-0.13d
VERSION = 0.13d

TOPDIR = $(shell pwd)
KERNEL_VERSION = $(shell uname -r)
#KERNEL_SRC = /lib/modules/$(KERNEL_VERSION)/build
KERNEL_SRC = /storage/pub/src/kernel/bk-cvs/linux-2.5

# if Rules.make exists in the kernel tree, we assume 2.4 style modules
# if it doesn't assume 2.5 style
OLDMAKE = $(wildcard $(KERNEL_SRC)/Rules.make)

MODULES = firmware_class.o firmware_sample_driver.o

all: modules

clean:
rm -f core *.o *~ a.out *.d
rm -f *.s *.i userhermes bufparse
rm -f *.ko *.mod.c .*.o.cmd

ifeq (,$(OLDMAKE))
# 2.5 style modules, get the kernel makefiles to do the work
KBUILD_VERBOSE = 0 # default

obj-m := $(MODULES)

modules:
$(MAKE) -C $(KERNEL_SRC) SUBDIRS=$(TOPDIR) KBUILD_VERBOSE=$(KBUILD_VERBOSE) modules

install: all
$(MAKE) -C $(KERNEL_SRC) SUBDIRS=$(TOPDIR) KBUILD_VERBOSE=$(KBUILD_VERBOSE) modules_install

else
# 2.4 style modules
KERNEL_HEADERS = -I$(KERNEL_SRC)/include

MODULE_DIR_TOP = /lib/modules/$(KERNEL_VERSION)
MODULE_DIR_WIRELESS = $(MODULE_DIR_TOP)/kernel/drivers/net/wireless
CPPFLAGS = -D__KERNEL__ -DPCMCIA_DEBUG=1 \
-DMODULE -DEXPORT_SYMTAB \
$(PCMCIA_HEADERS) $(KERNEL_HEADERS)
CFLAGS = -O2 -Wall -Wstrict-prototypes -fno-common -pipe $(EXTRACFLAGS)

MODVER = $(shell if cat $(KERNEL_SRC)/include/linux/autoconf.h 2>/dev/null | \
grep -q '^[[:space:]]*\#define[[:space:]]*CONFIG_MODVERSIONS[[:space:]]*1'; \
then echo 1; else echo 0; fi)

ifeq ($(MODVER),1)
MFLAG = -DMODVERSIONS -include $(KERNEL_SRC)/include/linux/modversions.h
endif

modules: $(MODULES)

%.o: %.c
$(CC) -MD $(CFLAGS) $(CPPFLAGS) $(MFLAG) -c $<

%.s: %.c
$(CC) -MD $(CFLAGS) $(CPPFLAGS) -S $<

%.i: %.c
$(CC) -MD $(CPPFLAGS) -E $< -o $@

endif
\
 
 \ /
  Last update: 2005-03-22 13:35    [W:0.127 / U:1.360 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site