Commit e20de807 authored by Alessandro Rubini's avatar Alessandro Rubini

kernel, tools, doc: fmc-chardev and fmc-mem

Signed-off-by: Alessandro Rubini's avatarAlessandro Rubini <rubini@gnudd.com>
parent c8f7b4c3
......@@ -792,6 +792,81 @@ spusa.root# insmod fmc-fakedev.ko
[ 132.899872] fake-fmc: Product name: FmcDelay1ns4cha
@end smallexample
@c ==========================================================================
@node fmc-chardev
@section 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 @i{misc} device. Its name in @t{/dev}
(as created by @i{udev}) is the same name as the underlying @sc{fmc}
device. Thus, the name can be a silly @t{fmc-0000} look-alike if the
device has no identifiers nor @i{bus_id}, a more specific @t{fmc-0400}
if the device has a bus-specific address but no associated name, or
something like @t{fdelay-0400} if the @sc{fmc} core can rely on both a
mezzanine name and a bus address.
Currently the driver only supports @i{read} and @i{write}: you can
@i{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 @i{fread} or @i{fwrite}
bigger areas on order to dump or store bigger memory areas.
There is currently no support for @i{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 @sc{spec}
card programmed with it's @i{golden} @sc{fpga} file, that features
an @sc{sdb} structure at offset 256 -- i.e. 64 words. The
The mezzanine's @sc{eeprom} in this case is not programmed, so the
default name is @t{fmc-@i{<bus><devfn>}}, and there are two
cards in the system:
@smallexample
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
@end smallexample
The simple program @i{tools/fmc-mem} in this package can access
an @sc{fmc} char device and read or write a word or a whole
area. Actually, the program is not specific to @sc{fmc} at all,
it just uses @i{lseek}, @i{read} and @i{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 ``@t{+}'' is the number of bytes to read or write.
In case of repeated reading data is written to @i{stdout}; repeated
writes read from @i{stdin} and the value argument is ignored.
The following examples show reading the @sc{sdb} magic number
and the first @sc{sdb} record from a @sc{spec} device programmed
with its @i{golden} image:
@smallexample
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
@end smallexample
@c ##########################################################################
@node Writing your FMC Driver
@chapter Writing your FMC Driver
......
......@@ -7,6 +7,7 @@ obj-m = fmc.o
obj-m += fmc-fakedev.o
obj-m += fmc-trivial.o
obj-m += fmc-write-eeprom.o
obj-m += fmc-chardev.o
fmc-y = fmc-core.o
fmc-y += fmc-match.o
......
/*
* 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>
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 struct file_operations fc_fops = {
.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->hwdev, "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->hwdev, "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");
fru-dump
fmc-mem
\ No newline at end of file
CFLAGS = -Wall -ggdb -O2 -I../kernel/include
all: fru-dump
all: fru-dump fmc-mem
all clean:
$(MAKE) -C libipmi $@
......
/*
* 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 <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
static void help(char *prgname)
{
fprintf(stderr, "%s: use "
"\"%s <device> <addr> [<value>] [+<nbytes>]\"\n"
" <device> is a file name, all others are hex numbers\n"
" bursts of \"nbytes\" use stdin/stdout (value ignored)\n",
prgname, prgname);
exit(1);
}
static void w_loop(int fd, char **argv, int nbytes)
{
uint32_t reg;
while (nbytes >= sizeof(reg)) {
if (read(0, &reg, sizeof(reg)) != sizeof(reg)) {
fprintf(stderr, "%s: <stdin>: short read\n", argv[0]);
exit(1);
}
if (write(fd, &reg, sizeof(reg)) != sizeof(reg)) {
fprintf(stderr, "%s: write(%s): %s\n", argv[0],
argv[1], strerror(errno));
exit(1);
}
nbytes -= sizeof(reg);
}
exit(0);
}
static void r_loop(int fd, char **argv, int nbytes)
{
uint32_t reg;
while (nbytes >= sizeof(reg)) {
if (read(fd, &reg, sizeof(reg)) != sizeof(reg)) {
fprintf(stderr, "%s: read(%s): %s\n", argv[0],
argv[1], strerror(errno));
exit(1);
}
if (write(1, &reg, sizeof(reg)) != sizeof(reg)) {
fprintf(stderr, "%s: <stdout>: %s\n", argv[0],
strerror(errno));
exit(1);
}
nbytes -= sizeof(reg);
}
exit(0);
}
int main(int argc, char **argv)
{
int fd;
unsigned long addr, value, nbytes = 0;
uint32_t reg;
int dowrite = 0, doloop = 0;
char c;
if (argc < 3 || argv[1][0] == '-') /* -h, --help, whatever */
help(argv[0]);
/* boring scanning of arguments */
if (sscanf(argv[2], "%lx%c", &addr, &c) != 1) {
fprintf(stderr, "%s: Not an hex address \"%s\"\n",
argv[0], argv[2]);
exit(1);
}
if (argv[argc - 1][0] == '+') {
if (sscanf(argv[argc - 1], "+%lx%c", &nbytes, &c) != 1) {
fprintf(stderr, "%s: Not +<hex-number> \"%s\"\n",
argv[0], argv[argc - 1]);
exit(1);
}
doloop = 1;
argc--;
}
if (argc > 4)
help(argv[0]);
if (argc == 4) {
if (sscanf(argv[3], "%lx%c", &value, &c) != 1) {
fprintf(stderr, "%s: Not an hex value \"%s\"\n",
argv[0], argv[3]);
exit(1);
}
dowrite = 1;
}
if (0) { /* want to verify? */
if (dowrite)
printf("write %lx to %s:%lx (loop %i %lx)\n",
value, argv[1], addr, doloop, nbytes);
else
printf("read from %s:%lx (loop %i %lx)\n",
argv[1], addr, doloop, nbytes);
exit(0);
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
fprintf(stderr, "%s: %s: %s\n", argv[0], argv[1],
strerror(errno));
exit(1);
}
if (lseek(fd, addr, SEEK_SET) < 0) {
fprintf(stderr, "%s: %s: lseek: %s\n", argv[0], argv[1],
strerror(errno));
exit(1);
}
/* each loop function here exits when it's done */
if (doloop && dowrite)
w_loop(fd, argv, nbytes);
if (doloop)
r_loop(fd, argv, nbytes);
/* one write only */
if (dowrite) {
reg = value;
if (write(fd, &reg, sizeof(reg)) != sizeof(reg)) {
fprintf(stderr, "%s: write(): %s\n", argv[0],
strerror(errno));
exit(1);
}
exit(0);
}
/* one read only */
if (read(fd, &reg, sizeof(reg)) != sizeof(reg)) {
fprintf(stderr, "%s: read(): %s\n", argv[0],
strerror(errno));
exit(1);
}
printf("%08x\n", reg);
exit(0);
}
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