Commit c8f7b4c3 authored by Alessandro Rubini's avatar Alessandro Rubini

kernel and doc: fmc-fakedev, a software-only device

Signed-off-by: Alessandro Rubini's avatarAlessandro Rubini <rubini@gnudd.com>
parent c5cc01ff
......@@ -455,6 +455,42 @@ polarity. Especially reading inputs is not expected to be common. If
your device has GPIO capabilities in the hot path, you should consider
using the kernel's GPIO mechanisms.
@c ==========================================================================
@node fmc-fakedev
@section fmc-fakedev
This package includes a software-only device, called @t{fmc-fakedev}.
Unlike the @sc{spec} driver, which creates an @sc{fmc} device for each
PCI cards it manages, this module creates a single instance of its
device.
It is meant as the simplest possible example of how a driver should be
written, and it includes a fake @sc{eeprom} image (built using the
tools described in @ref{FMC Identification}).
You can also use this device to verify the match algorithms, by
asking it to test your own @sc{eeprom} image. You can provide the image by
means of the @t{eeprom=} module parameter: the new @sc{eeprom} image
is loaded, as usual, by means of the @i{firmware loader}.
This example shows the defaults and a custom @sc{eeprom} image:
@smallexample
spusa.root# insmod fmc-fakedev.ko
[ 158.355436] fake-fmc: Manufacturer: fake-vendor
[ 158.360106] fake-fmc: Product name: fake-design-for-testing
spusa.root# rmmod fmc-fakedev
spusa.root# insmod fmc-fakedev.ko eeprom=fdelay-eeprom.bin
[ 197.182682] fake-fmc: Manufacturer: CERN
[ 197.186764] fake-fmc: Product name: FmcDelay1ns4cha
spusa.root# rmmod fmc-fakedev
@end smallexample
After loading the device, you can use the @i{write_ee} method do
modify its own internal fake @sc{eeprom}: whenever the image is
overwritten starting at offset 0, the module will unregister and
register again the @sc{fmc} device. This is shown later, in
section @ref{fmc-write-eeprom}.
@c ==========================================================================
@node FMC Device Incompatibilities
@section FMC Device Incompatibilities
......@@ -729,11 +765,33 @@ This is a real example: that writes 5 bytes at position 0x110:
[19983.414615] spec 0000:03:00.0: write_eeprom: success
@end smallexample
Please note that you'll most likely want to use @sc{sdb}FS to build your
Please note that you'll most likely want to use @sc{sdbfs} to build your
@sc{eeprom} image, at least if your mezzanines are being used in the White Rabbit
environment. For this reason the TLV format is not expected to be used much
and is not expected to be developed further.
If you want to experiment with reflashing fake @sc{eeprom} devices,
you cn use the @t{fmc-fakedev.ko} module (see @ref{fmc-fakedev}).
Whenever you change the image starting at offset 0, it will deregister
and register again after two seconds. Please note, however, that if
@i{fmc-write-eeprom} is still loaded, the system will associate it to
the new device, which will be reprogrammed and thus will be unloaded
after two seconds. The following example removes the module after
it reflashed @i{fakedev} the first time.
@smallexample
spusa.root# insmod fmc-fakedev.ko
[ 72.984733] fake-fmc: Manufacturer: fake-vendor
[ 72.989434] fake-fmc: Product name: fake-design-for-testing
spusa.root# insmod fmc-write-eeprom.ko busid=0 file=fdelay-eeprom.bin; \
rmmod fmc-write-eeprom
[ 130.874098] fake-fmc: Matching a generic driver (no ID)
[ 130.887845] fake-fmc: programming 6155 bytes
[ 130.894567] fake-fmc: write_eeprom: success
[ 132.895794] fake-fmc: Manufacturer: CERN
[ 132.899872] fake-fmc: Product name: FmcDelay1ns4cha
@end smallexample
@c ##########################################################################
@node Writing your FMC Driver
@chapter Writing your FMC Driver
......
......@@ -4,6 +4,7 @@ LINUX ?= /lib/modules/$(shell uname -r)/build
ccflags-y += -I$M/include
obj-m = fmc.o
obj-m += fmc-fakedev.o
obj-m += fmc-trivial.o
obj-m += fmc-write-eeprom.o
......
/*
* Copyright (C) 2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released to the public domain, as example to be reused
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/device.h>
#include <linux/firmware.h>
#include <linux/workqueue.h>
#include <linux/fmc.h>
static char *ff_eeprom;
module_param_named(eeprom, ff_eeprom, charp, 0444);
/* Lazily, don't support the "standard" module parameters */
#define FF_EEPROM_SIZE 8192
/*
* Eeprom built from these commands:
../fru-generator -v fake-vendor -n fake-design-for-testing \
-s 01234 -p none > IPMI-FRU
gensdbfs . ../fake-eeprom.bin
*/
static char ff_eeimg[FF_EEPROM_SIZE] = {
0x01, 0x00, 0x00, 0x01, 0x00, 0x0c, 0x00, 0xf2, 0x01, 0x0b, 0x00, 0xb2,
0x86, 0x87, 0xcb, 0x66, 0x61, 0x6b, 0x65, 0x2d, 0x76, 0x65, 0x6e, 0x64,
0x6f, 0x72, 0xd7, 0x66, 0x61, 0x6b, 0x65, 0x2d, 0x64, 0x65, 0x73, 0x69,
0x67, 0x6e, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x69,
0x6e, 0x67, 0xc5, 0x30, 0x31, 0x32, 0x33, 0x34, 0xc4, 0x6e, 0x6f, 0x6e,
0x65, 0xda, 0x32, 0x30, 0x31, 0x32, 0x2d, 0x31, 0x31, 0x2d, 0x31, 0x39,
0x20, 0x32, 0x32, 0x3a, 0x34, 0x32, 0x3a, 0x33, 0x30, 0x2e, 0x30, 0x37,
0x34, 0x30, 0x35, 0x35, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87,
0x02, 0x02, 0x0d, 0xf7, 0xf8, 0x02, 0xb0, 0x04, 0x74, 0x04, 0xec, 0x04,
0x00, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x02, 0x02, 0x0d, 0x5c, 0x93, 0x01,
0x4a, 0x01, 0x39, 0x01, 0x5a, 0x01, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x0b,
0x02, 0x02, 0x0d, 0x63, 0x8c, 0x00, 0xfa, 0x00, 0xed, 0x00, 0x06, 0x01,
0x00, 0x00, 0x00, 0x00, 0xa0, 0x0f, 0x01, 0x02, 0x0d, 0xfb, 0xf5, 0x05,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x02, 0x0d, 0xfc, 0xf4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x0d, 0xfd, 0xf3, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xfa, 0x82, 0x0b, 0xea, 0x8f, 0xa2, 0x12, 0x00, 0x00, 0x1e, 0x44, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x53, 0x44, 0x42, 0x2d, 0x00, 0x03, 0x01, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0xc4, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61,
0x2e, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x2e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc4, 0x46, 0x69, 0x6c, 0x65,
0x44, 0x61, 0x74, 0x61, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf,
0x46, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x49, 0x50, 0x4d, 0x49,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x49, 0x50, 0x4d, 0x49,
0x2d, 0x46, 0x52, 0x55, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x01, 0x66, 0x61, 0x6b, 0x65, 0x0a,
};
struct ff_dev {
struct fmc_device fmc;
struct device dev;
struct delayed_work work;
};
static int ff_reprogram(struct fmc_device *fmc, struct fmc_driver *drv,
char *gw)
{
const struct firmware *fw;
int ret;
if (!gw) {
/* program golden: success */
fmc->flags &= ~FMC_DEVICE_HAS_CUSTOM;
fmc->flags |= FMC_DEVICE_HAS_GOLDEN;
return 0;
}
dev_info(fmc->hwdev, "reprogramming with %s\n", gw);
ret = request_firmware(&fw, gw, fmc->hwdev);
if (ret < 0) {
dev_warn(fmc->hwdev, "request firmware \"%s\": error %i\n",
gw, ret);
goto out;
}
fmc->flags &= ~FMC_DEVICE_HAS_GOLDEN;
fmc->flags |= FMC_DEVICE_HAS_CUSTOM;
out:
release_firmware(fw);
return ret;
}
static int ff_irq_request(struct fmc_device *fmc, irq_handler_t handler,
char *name, int flags)
{
return -EOPNOTSUPP;
}
/*
* FIXME: add some fake GPIO mapping
*/
static struct fmc_device ff_template_fmc; /* defined later */
/* unregister and register again, after we changed eeprom */
static void ff_work_fn(struct work_struct *work)
{
struct delayed_work *dw = to_delayed_work(work);
struct ff_dev *ff = container_of(dw, struct ff_dev, work);
int ret;
fmc_device_unregister(&ff->fmc);
/* copy the template, so all lists are empty */
ff->fmc = ff_template_fmc;
ret = fmc_device_register(&ff->fmc);
if (ret < 0) {
/* Serious problem: we will crash at rmmod time */
pr_err("%s: can't re-register FMC device\n", __func__);
return;
}
}
/* low-level i2c */
int ff_eeprom_read(struct fmc_device *fmc, uint32_t offset,
void *buf, size_t size)
{
if (offset > FF_EEPROM_SIZE)
return -EINVAL;
if (offset + size > FF_EEPROM_SIZE)
size = FF_EEPROM_SIZE - offset;
memcpy(buf, ff_eeimg + offset, size);
return size;
}
int ff_eeprom_write(struct fmc_device *fmc, uint32_t offset,
const void *buf, size_t size)
{
struct ff_dev *ff = container_of(fmc, struct ff_dev, fmc);
if (offset > FF_EEPROM_SIZE)
return -EINVAL;
if (offset + size > FF_EEPROM_SIZE)
size = FF_EEPROM_SIZE - offset;
printk("size %i\n", size);
memcpy(ff_eeimg + offset, buf, size);
schedule_delayed_work(&ff->work, HZ * 2); /* remove, replug, in 2s */
return size;
}
/* i2c operations for fmc */
static int ff_read_ee(struct fmc_device *fmc, int pos, void *data, int len)
{
if (!(fmc->flags & FMC_DEVICE_HAS_GOLDEN))
return -EOPNOTSUPP;
return ff_eeprom_read(fmc, pos, data, len);
}
static int ff_write_ee(struct fmc_device *fmc, int pos,
const void *data, int len)
{
if (!(fmc->flags & FMC_DEVICE_HAS_GOLDEN))
return -EOPNOTSUPP;
return ff_eeprom_write(fmc, pos, data, len);
}
/* readl and writel do not do anything. Don't waste RAM with "base" */
uint32_t ff_readl(struct fmc_device *fmc, int offset)
{
return 0;
}
void ff_writel(struct fmc_device *fmc, uint32_t value, int offset)
{
return;
}
static struct fmc_operations ff_fmc_operations = {
.readl = ff_readl,
.writel = ff_writel,
.reprogram = ff_reprogram,
.irq_request = ff_irq_request,
.read_ee = ff_read_ee,
.write_ee = ff_write_ee,
};
/* Every device must have a release method: provide a default */
static void __ff_release(struct device *dev)
{
printk("%s\n", __func__);
memset(dev, 0, sizeof(*dev));
dev->release = __ff_release;
}
static struct ff_dev ff_static_dev = {
.dev = {
.release = __ff_release,
}, .fmc = {
/* the template below will be copied herein */
}
};
static struct fmc_device ff_template_fmc = {
.version = FMC_VERSION,
.carrier_name = "fake",
.device_id = 0xf001, /* fool */
.eeprom = ff_eeimg,
.eeprom_len = sizeof(ff_eeimg),
.op = &ff_fmc_operations,
.hwdev = &ff_static_dev.dev,
.flags = FMC_DEVICE_HAS_GOLDEN,
};
/* init and exit */
int ff_init(void)
{
struct ff_dev *ff = &ff_static_dev;
const struct firmware *fw;
int len, ret = 0;
INIT_DELAYED_WORK(&ff->work, ff_work_fn);
dev_set_name(&ff->dev, "fake-fmc");
ff->fmc = ff_template_fmc;
ret = device_register(&ff->dev);
if (ret)
return ret;
/* If the user passed "eeprom=" as a parameter, fetch it */
if (ff_eeprom) {
ret = request_firmware(&fw, ff_eeprom, &ff->dev);
if (ret < 0) {
dev_err(&ff->dev, "Can't load \"%s\" (error %i)\n",
ff_eeprom, -ret);
} else {
len = min(fw->size, (size_t)FF_EEPROM_SIZE);
memcpy(ff_eeimg, fw->data, len);
release_firmware(fw);
}
}
ret = fmc_device_register(&ff->fmc);
if (ret) {
device_unregister(&ff->dev);
return ret;
}
//schedule_delayed_work(&ff->work, HZ * 2); /* remove, replug, in 2s */
return ret;
}
void ff_exit(void)
{
struct ff_dev *ff = &ff_static_dev;
cancel_delayed_work_sync(&ff->work);
fmc_device_unregister(&ff->fmc);
device_unregister(&ff->dev);
}
module_init(ff_init);
module_exit(ff_exit);
MODULE_LICENSE("GPL and additional rights");
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