FMC: add a char-device mezzanine driver
This driver exports the memory area associated with the mezzanine card as a misc device, so users can access registers. Signed-off-by: Alessandro Rubini <rubini@gnudd.com> Acked-by: Juan David Gonzalez Cobas <dcobas@cern.ch> Acked-by: Emilio G. Cota <cota@braap.org> Acked-by: Samuel Iglesias Gonsalvez <siglesias@igalia.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
6007b1bd0f
commit
4debfe409b
5 changed files with 274 additions and 1 deletions
|
@ -32,4 +32,7 @@ fmc-trivial.txt
|
|||
- about drivers/fmc/fmc-trivial.ko
|
||||
|
||||
fmc-write-eeprom.txt
|
||||
- about drivers/fmc/fmc-write-eeprom.ko
|
||||
- about drivers/fmc/fmc-write-eeprom.ko
|
||||
|
||||
fmc-chardev.txt
|
||||
- about drivers/fmc/fmc-chardev.ko
|
||||
|
|
64
Documentation/fmc/fmc-chardev.txt
Normal file
64
Documentation/fmc/fmc-chardev.txt
Normal file
|
@ -0,0 +1,64 @@
|
|||
fmc-chardev
|
||||
===========
|
||||
|
||||
This is a simple generic driver, that allows user access by means of a
|
||||
character device (actually, one for each mezzanine it takes hold of).
|
||||
|
||||
The char device is created as a misc device. Its name in /dev (as
|
||||
created by udev) is the same name as the underlying FMC device. Thus,
|
||||
the name can be a silly fmc-0000 look-alike if the device has no
|
||||
identifiers nor bus_id, a more specific fmc-0400 if the device has a
|
||||
bus-specific address but no associated name, or something like
|
||||
fdelay-0400 if the FMC core can rely on both a mezzanine name and a bus
|
||||
address.
|
||||
|
||||
Currently the driver only supports read and write: you can lseek to the
|
||||
desired address and read or write a register.
|
||||
|
||||
The driver assumes all registers are 32-bit in size, and only accepts a
|
||||
single read or write per system call. However, as a result of Unix read
|
||||
and write semantics, users can simply fread or fwrite bigger areas in
|
||||
order to dump or store bigger memory areas.
|
||||
|
||||
There is currently no support for mmap, user-space interrupt management
|
||||
and DMA buffers. They may be added in later versions, if the need
|
||||
arises.
|
||||
|
||||
The example below shows raw access to a SPEC card programmed with its
|
||||
golden FPGA file, that features an SDB structure at offset 256 - i.e.
|
||||
64 words. The mezzanine's EEPROM in this case is not programmed, so the
|
||||
default name is fmc-<bus><devfn>, and there are two cards in the system:
|
||||
|
||||
spusa.root# insmod fmc-chardev.ko
|
||||
[ 1073.339332] spec 0000:02:00.0: Driver has no ID: matches all
|
||||
[ 1073.345051] spec 0000:02:00.0: Created misc device "fmc-0200"
|
||||
[ 1073.350821] spec 0000:04:00.0: Driver has no ID: matches all
|
||||
[ 1073.356525] spec 0000:04:00.0: Created misc device "fmc-0400"
|
||||
spusa.root# ls -l /dev/fmc*
|
||||
crw------- 1 root root 10, 58 Nov 20 19:23 /dev/fmc-0200
|
||||
crw------- 1 root root 10, 57 Nov 20 19:23 /dev/fmc-0400
|
||||
spusa.root# dd bs=4 skip=64 count=1 if=/dev/fmc-0200 2> /dev/null | od -t x1z
|
||||
0000000 2d 42 44 53 >-BDS<
|
||||
0000004
|
||||
|
||||
The simple program tools/fmc-mem in this package can access an FMC char
|
||||
device and read or write a word or a whole area. Actually, the program
|
||||
is not specific to FMC at all, it just uses lseek, read and write.
|
||||
|
||||
Its first argument is the device name, the second the offset, the third
|
||||
(if any) the value to write and the optional last argument that must
|
||||
begin with "+" is the number of bytes to read or write. In case of
|
||||
repeated reading data is written to stdout; repeated writes read from
|
||||
stdin and the value argument is ignored.
|
||||
|
||||
The following examples show reading the SDB magic number and the first
|
||||
SDB record from a SPEC device programmed with its golden image:
|
||||
|
||||
spusa.root# ./fmc-mem /dev/fmc-0200 100
|
||||
5344422d
|
||||
spusa.root# ./fmc-mem /dev/fmc-0200 100 +40 | od -Ax -t x1z
|
||||
000000 2d 42 44 53 00 01 02 00 00 00 00 00 00 00 00 00 >-BDS............<
|
||||
000010 00 00 00 00 ff 01 00 00 00 00 00 00 51 06 00 00 >............Q...<
|
||||
000020 c9 42 a5 e6 02 00 00 00 11 05 12 20 2d 34 42 57 >.B......... -4BW<
|
||||
000030 73 6f 72 43 72 61 62 73 49 53 47 2d 00 20 20 20 >sorCrabsISG-. <
|
||||
000040
|
|
@ -40,4 +40,12 @@ config FMC_WRITE_EEPROM
|
|||
its binary and the function carrier->reprogram to actually do it.
|
||||
It is useful when the mezzanines are produced.
|
||||
|
||||
config FMC_CHARDEV
|
||||
tristate "FMC mezzanine driver that registers a char device"
|
||||
help
|
||||
This driver matches every mezzanine device and allows user
|
||||
space to read and write registers using a char device. It
|
||||
can be used to write user-space drivers, or just get
|
||||
aquainted with a mezzanine before writing its specific driver.
|
||||
|
||||
endif # FMC
|
||||
|
|
|
@ -10,3 +10,4 @@ fmc-y += fmc-dump.o
|
|||
obj-$(CONFIG_FMC_FAKEDEV) += fmc-fakedev.o
|
||||
obj-$(CONFIG_FMC_TRIVIAL) += fmc-trivial.o
|
||||
obj-$(CONFIG_FMC_WRITE_EEPROM) += fmc-write-eeprom.o
|
||||
obj-$(CONFIG_FMC_CHARDEV) += fmc-chardev.o
|
||||
|
|
197
drivers/fmc/fmc-chardev.c
Normal file
197
drivers/fmc/fmc-chardev.c
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Copyright (C) 2012 CERN (www.cern.ch)
|
||||
* Author: Alessandro Rubini <rubini@gnudd.com>
|
||||
*
|
||||
* Released according to the GNU GPL, version 2 or any later version.
|
||||
*
|
||||
* This work is part of the White Rabbit project, a research effort led
|
||||
* by CERN, the European Institute for Nuclear Research.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/fmc.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
static LIST_HEAD(fc_devices);
|
||||
static DEFINE_SPINLOCK(fc_lock);
|
||||
|
||||
struct fc_instance {
|
||||
struct list_head list;
|
||||
struct fmc_device *fmc;
|
||||
struct miscdevice misc;
|
||||
};
|
||||
|
||||
/* at open time, we must identify our device */
|
||||
static int fc_open(struct inode *ino, struct file *f)
|
||||
{
|
||||
struct fmc_device *fmc;
|
||||
struct fc_instance *fc;
|
||||
int minor = iminor(ino);
|
||||
|
||||
list_for_each_entry(fc, &fc_devices, list)
|
||||
if (fc->misc.minor == minor)
|
||||
break;
|
||||
if (fc->misc.minor != minor)
|
||||
return -ENODEV;
|
||||
fmc = fc->fmc;
|
||||
if (try_module_get(fmc->owner) == 0)
|
||||
return -ENODEV;
|
||||
|
||||
f->private_data = fmc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fc_release(struct inode *ino, struct file *f)
|
||||
{
|
||||
struct fmc_device *fmc = f->private_data;
|
||||
module_put(fmc->owner);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* read and write are simple after the default llseek has been used */
|
||||
static ssize_t fc_read(struct file *f, char __user *buf, size_t count,
|
||||
loff_t *offp)
|
||||
{
|
||||
struct fmc_device *fmc = f->private_data;
|
||||
unsigned long addr;
|
||||
uint32_t val;
|
||||
|
||||
if (count < sizeof(val))
|
||||
return -EINVAL;
|
||||
count = sizeof(val);
|
||||
|
||||
addr = *offp;
|
||||
if (addr > fmc->memlen)
|
||||
return -ESPIPE; /* Illegal seek */
|
||||
val = fmc_readl(fmc, addr);
|
||||
if (copy_to_user(buf, &val, count))
|
||||
return -EFAULT;
|
||||
*offp += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t fc_write(struct file *f, const char __user *buf, size_t count,
|
||||
loff_t *offp)
|
||||
{
|
||||
struct fmc_device *fmc = f->private_data;
|
||||
unsigned long addr;
|
||||
uint32_t val;
|
||||
|
||||
if (count < sizeof(val))
|
||||
return -EINVAL;
|
||||
count = sizeof(val);
|
||||
|
||||
addr = *offp;
|
||||
if (addr > fmc->memlen)
|
||||
return -ESPIPE; /* Illegal seek */
|
||||
if (copy_from_user(&val, buf, count))
|
||||
return -EFAULT;
|
||||
fmc_writel(fmc, val, addr);
|
||||
*offp += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations fc_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = fc_open,
|
||||
.release = fc_release,
|
||||
.llseek = generic_file_llseek,
|
||||
.read = fc_read,
|
||||
.write = fc_write,
|
||||
};
|
||||
|
||||
|
||||
/* Device part .. */
|
||||
static int fc_probe(struct fmc_device *fmc);
|
||||
static int fc_remove(struct fmc_device *fmc);
|
||||
|
||||
static struct fmc_driver fc_drv = {
|
||||
.version = FMC_VERSION,
|
||||
.driver.name = KBUILD_MODNAME,
|
||||
.probe = fc_probe,
|
||||
.remove = fc_remove,
|
||||
/* no table: we want to match everything */
|
||||
};
|
||||
|
||||
/* We accept the generic busid parameter */
|
||||
FMC_PARAM_BUSID(fc_drv);
|
||||
|
||||
/* probe and remove must allocate and release a misc device */
|
||||
static int fc_probe(struct fmc_device *fmc)
|
||||
{
|
||||
int ret;
|
||||
int index = 0;
|
||||
|
||||
struct fc_instance *fc;
|
||||
|
||||
if (fmc->op->validate)
|
||||
index = fmc->op->validate(fmc, &fc_drv);
|
||||
if (index < 0)
|
||||
return -EINVAL; /* not our device: invalid */
|
||||
|
||||
/* Create a char device: we want to create it anew */
|
||||
fc = kzalloc(sizeof(*fc), GFP_KERNEL);
|
||||
fc->fmc = fmc;
|
||||
fc->misc.minor = MISC_DYNAMIC_MINOR;
|
||||
fc->misc.fops = &fc_fops;
|
||||
fc->misc.name = kstrdup(dev_name(&fmc->dev), GFP_KERNEL);
|
||||
|
||||
spin_lock(&fc_lock);
|
||||
ret = misc_register(&fc->misc);
|
||||
if (ret < 0) {
|
||||
kfree(fc->misc.name);
|
||||
kfree(fc);
|
||||
} else {
|
||||
list_add(&fc->list, &fc_devices);
|
||||
}
|
||||
spin_unlock(&fc_lock);
|
||||
dev_info(&fc->fmc->dev, "Created misc device \"%s\"\n",
|
||||
fc->misc.name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fc_remove(struct fmc_device *fmc)
|
||||
{
|
||||
struct fc_instance *fc;
|
||||
|
||||
list_for_each_entry(fc, &fc_devices, list)
|
||||
if (fc->fmc == fmc)
|
||||
break;
|
||||
if (fc->fmc != fmc) {
|
||||
dev_err(&fmc->dev, "remove called but not found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
spin_lock(&fc_lock);
|
||||
list_del(&fc->list);
|
||||
misc_deregister(&fc->misc);
|
||||
kfree(fc->misc.name);
|
||||
kfree(fc);
|
||||
spin_unlock(&fc_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int fc_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = fmc_driver_register(&fc_drv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void fc_exit(void)
|
||||
{
|
||||
fmc_driver_unregister(&fc_drv);
|
||||
}
|
||||
|
||||
module_init(fc_init);
|
||||
module_exit(fc_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Reference in a new issue