aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlessandro Rubini <rubini@gnudd.com>2013-06-18 17:48:07 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-06-18 18:42:15 -0400
commit4debfe409b6e550032bfef9733e9f6f7c5613617 (patch)
treec647e07600d964c6399e20f04c68ea8467c7b328
parent6007b1bd0f752a5c022f7944c65fb96c39d6db3d (diff)
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>
-rw-r--r--Documentation/fmc/00-INDEX5
-rw-r--r--Documentation/fmc/fmc-chardev.txt64
-rw-r--r--drivers/fmc/Kconfig8
-rw-r--r--drivers/fmc/Makefile1
-rw-r--r--drivers/fmc/fmc-chardev.c197
5 files changed, 274 insertions, 1 deletions
diff --git a/Documentation/fmc/00-INDEX b/Documentation/fmc/00-INDEX
index 177c3e4a9511..431c69570f43 100644
--- a/Documentation/fmc/00-INDEX
+++ b/Documentation/fmc/00-INDEX
@@ -32,4 +32,7 @@ fmc-trivial.txt
32 - about drivers/fmc/fmc-trivial.ko 32 - about drivers/fmc/fmc-trivial.ko
33 33
34fmc-write-eeprom.txt 34fmc-write-eeprom.txt
35 - about drivers/fmc/fmc-write-eeprom.ko 35 - about drivers/fmc/fmc-write-eeprom.ko
36
37fmc-chardev.txt
38 - about drivers/fmc/fmc-chardev.ko
diff --git a/Documentation/fmc/fmc-chardev.txt b/Documentation/fmc/fmc-chardev.txt
new file mode 100644
index 000000000000..d9ccb278e597
--- /dev/null
+++ b/Documentation/fmc/fmc-chardev.txt
@@ -0,0 +1,64 @@
1fmc-chardev
2===========
3
4This is a simple generic driver, that allows user access by means of a
5character device (actually, one for each mezzanine it takes hold of).
6
7The char device is created as a misc device. Its name in /dev (as
8created by udev) is the same name as the underlying FMC device. Thus,
9the name can be a silly fmc-0000 look-alike if the device has no
10identifiers nor bus_id, a more specific fmc-0400 if the device has a
11bus-specific address but no associated name, or something like
12fdelay-0400 if the FMC core can rely on both a mezzanine name and a bus
13address.
14
15Currently the driver only supports read and write: you can lseek to the
16desired address and read or write a register.
17
18The driver assumes all registers are 32-bit in size, and only accepts a
19single read or write per system call. However, as a result of Unix read
20and write semantics, users can simply fread or fwrite bigger areas in
21order to dump or store bigger memory areas.
22
23There is currently no support for mmap, user-space interrupt management
24and DMA buffers. They may be added in later versions, if the need
25arises.
26
27The example below shows raw access to a SPEC card programmed with its
28golden FPGA file, that features an SDB structure at offset 256 - i.e.
2964 words. The mezzanine's EEPROM in this case is not programmed, so the
30default name is fmc-<bus><devfn>, and there are two cards in the system:
31
32 spusa.root# insmod fmc-chardev.ko
33 [ 1073.339332] spec 0000:02:00.0: Driver has no ID: matches all
34 [ 1073.345051] spec 0000:02:00.0: Created misc device "fmc-0200"
35 [ 1073.350821] spec 0000:04:00.0: Driver has no ID: matches all
36 [ 1073.356525] spec 0000:04:00.0: Created misc device "fmc-0400"
37 spusa.root# ls -l /dev/fmc*
38 crw------- 1 root root 10, 58 Nov 20 19:23 /dev/fmc-0200
39 crw------- 1 root root 10, 57 Nov 20 19:23 /dev/fmc-0400
40 spusa.root# dd bs=4 skip=64 count=1 if=/dev/fmc-0200 2> /dev/null | od -t x1z
41 0000000 2d 42 44 53 >-BDS<
42 0000004
43
44The simple program tools/fmc-mem in this package can access an FMC char
45device and read or write a word or a whole area. Actually, the program
46is not specific to FMC at all, it just uses lseek, read and write.
47
48Its first argument is the device name, the second the offset, the third
49(if any) the value to write and the optional last argument that must
50begin with "+" is the number of bytes to read or write. In case of
51repeated reading data is written to stdout; repeated writes read from
52stdin and the value argument is ignored.
53
54The following examples show reading the SDB magic number and the first
55SDB record from a SPEC device programmed with its golden image:
56
57 spusa.root# ./fmc-mem /dev/fmc-0200 100
58 5344422d
59 spusa.root# ./fmc-mem /dev/fmc-0200 100 +40 | od -Ax -t x1z
60 000000 2d 42 44 53 00 01 02 00 00 00 00 00 00 00 00 00 >-BDS............<
61 000010 00 00 00 00 ff 01 00 00 00 00 00 00 51 06 00 00 >............Q...<
62 000020 c9 42 a5 e6 02 00 00 00 11 05 12 20 2d 34 42 57 >.B......... -4BW<
63 000030 73 6f 72 43 72 61 62 73 49 53 47 2d 00 20 20 20 >sorCrabsISG-. <
64 000040
diff --git a/drivers/fmc/Kconfig b/drivers/fmc/Kconfig
index 2bb1953c9681..c01cf45bc3d8 100644
--- a/drivers/fmc/Kconfig
+++ b/drivers/fmc/Kconfig
@@ -40,4 +40,12 @@ config FMC_WRITE_EEPROM
40 its binary and the function carrier->reprogram to actually do it. 40 its binary and the function carrier->reprogram to actually do it.
41 It is useful when the mezzanines are produced. 41 It is useful when the mezzanines are produced.
42 42
43config FMC_CHARDEV
44 tristate "FMC mezzanine driver that registers a char device"
45 help
46 This driver matches every mezzanine device and allows user
47 space to read and write registers using a char device. It
48 can be used to write user-space drivers, or just get
49 aquainted with a mezzanine before writing its specific driver.
50
43endif # FMC 51endif # FMC
diff --git a/drivers/fmc/Makefile b/drivers/fmc/Makefile
index 13701fa6db79..b9452919739f 100644
--- a/drivers/fmc/Makefile
+++ b/drivers/fmc/Makefile
@@ -10,3 +10,4 @@ fmc-y += fmc-dump.o
10obj-$(CONFIG_FMC_FAKEDEV) += fmc-fakedev.o 10obj-$(CONFIG_FMC_FAKEDEV) += fmc-fakedev.o
11obj-$(CONFIG_FMC_TRIVIAL) += fmc-trivial.o 11obj-$(CONFIG_FMC_TRIVIAL) += fmc-trivial.o
12obj-$(CONFIG_FMC_WRITE_EEPROM) += fmc-write-eeprom.o 12obj-$(CONFIG_FMC_WRITE_EEPROM) += fmc-write-eeprom.o
13obj-$(CONFIG_FMC_CHARDEV) += fmc-chardev.o
diff --git a/drivers/fmc/fmc-chardev.c b/drivers/fmc/fmc-chardev.c
new file mode 100644
index 000000000000..b0710393ede6
--- /dev/null
+++ b/drivers/fmc/fmc-chardev.c
@@ -0,0 +1,197 @@
1/*
2 * Copyright (C) 2012 CERN (www.cern.ch)
3 * Author: Alessandro Rubini <rubini@gnudd.com>
4 *
5 * Released according to the GNU GPL, version 2 or any later version.
6 *
7 * This work is part of the White Rabbit project, a research effort led
8 * by CERN, the European Institute for Nuclear Research.
9 */
10#include <linux/module.h>
11#include <linux/init.h>
12#include <linux/list.h>
13#include <linux/slab.h>
14#include <linux/fs.h>
15#include <linux/miscdevice.h>
16#include <linux/spinlock.h>
17#include <linux/fmc.h>
18#include <linux/uaccess.h>
19
20static LIST_HEAD(fc_devices);
21static DEFINE_SPINLOCK(fc_lock);
22
23struct fc_instance {
24 struct list_head list;
25 struct fmc_device *fmc;
26 struct miscdevice misc;
27};
28
29/* at open time, we must identify our device */
30static int fc_open(struct inode *ino, struct file *f)
31{
32 struct fmc_device *fmc;
33 struct fc_instance *fc;
34 int minor = iminor(ino);
35
36 list_for_each_entry(fc, &fc_devices, list)
37 if (fc->misc.minor == minor)
38 break;
39 if (fc->misc.minor != minor)
40 return -ENODEV;
41 fmc = fc->fmc;
42 if (try_module_get(fmc->owner) == 0)
43 return -ENODEV;
44
45 f->private_data = fmc;
46 return 0;
47}
48
49static int fc_release(struct inode *ino, struct file *f)
50{
51 struct fmc_device *fmc = f->private_data;
52 module_put(fmc->owner);
53 return 0;
54}
55
56/* read and write are simple after the default llseek has been used */
57static ssize_t fc_read(struct file *f, char __user *buf, size_t count,
58 loff_t *offp)
59{
60 struct fmc_device *fmc = f->private_data;
61 unsigned long addr;
62 uint32_t val;
63
64 if (count < sizeof(val))
65 return -EINVAL;
66 count = sizeof(val);
67
68 addr = *offp;
69 if (addr > fmc->memlen)
70 return -ESPIPE; /* Illegal seek */
71 val = fmc_readl(fmc, addr);
72 if (copy_to_user(buf, &val, count))
73 return -EFAULT;
74 *offp += count;
75 return count;
76}
77
78static ssize_t fc_write(struct file *f, const char __user *buf, size_t count,
79 loff_t *offp)
80{
81 struct fmc_device *fmc = f->private_data;
82 unsigned long addr;
83 uint32_t val;
84
85 if (count < sizeof(val))
86 return -EINVAL;
87 count = sizeof(val);
88
89 addr = *offp;
90 if (addr > fmc->memlen)
91 return -ESPIPE; /* Illegal seek */
92 if (copy_from_user(&val, buf, count))
93 return -EFAULT;
94 fmc_writel(fmc, val, addr);
95 *offp += count;
96 return count;
97}
98
99static const struct file_operations fc_fops = {
100 .owner = THIS_MODULE,
101 .open = fc_open,
102 .release = fc_release,
103 .llseek = generic_file_llseek,
104 .read = fc_read,
105 .write = fc_write,
106};
107
108
109/* Device part .. */
110static int fc_probe(struct fmc_device *fmc);
111static int fc_remove(struct fmc_device *fmc);
112
113static struct fmc_driver fc_drv = {
114 .version = FMC_VERSION,
115 .driver.name = KBUILD_MODNAME,
116 .probe = fc_probe,
117 .remove = fc_remove,
118 /* no table: we want to match everything */
119};
120
121/* We accept the generic busid parameter */
122FMC_PARAM_BUSID(fc_drv);
123
124/* probe and remove must allocate and release a misc device */
125static int fc_probe(struct fmc_device *fmc)
126{
127 int ret;
128 int index = 0;
129
130 struct fc_instance *fc;
131
132 if (fmc->op->validate)
133 index = fmc->op->validate(fmc, &fc_drv);
134 if (index < 0)
135 return -EINVAL; /* not our device: invalid */
136
137 /* Create a char device: we want to create it anew */
138 fc = kzalloc(sizeof(*fc), GFP_KERNEL);
139 fc->fmc = fmc;
140 fc->misc.minor = MISC_DYNAMIC_MINOR;
141 fc->misc.fops = &fc_fops;
142 fc->misc.name = kstrdup(dev_name(&fmc->dev), GFP_KERNEL);
143
144 spin_lock(&fc_lock);
145 ret = misc_register(&fc->misc);
146 if (ret < 0) {
147 kfree(fc->misc.name);
148 kfree(fc);
149 } else {
150 list_add(&fc->list, &fc_devices);
151 }
152 spin_unlock(&fc_lock);
153 dev_info(&fc->fmc->dev, "Created misc device \"%s\"\n",
154 fc->misc.name);
155 return ret;
156}
157
158static int fc_remove(struct fmc_device *fmc)
159{
160 struct fc_instance *fc;
161
162 list_for_each_entry(fc, &fc_devices, list)
163 if (fc->fmc == fmc)
164 break;
165 if (fc->fmc != fmc) {
166 dev_err(&fmc->dev, "remove called but not found\n");
167 return -ENODEV;
168 }
169
170 spin_lock(&fc_lock);
171 list_del(&fc->list);
172 misc_deregister(&fc->misc);
173 kfree(fc->misc.name);
174 kfree(fc);
175 spin_unlock(&fc_lock);
176
177 return 0;
178}
179
180
181static int fc_init(void)
182{
183 int ret;
184
185 ret = fmc_driver_register(&fc_drv);
186 return ret;
187}
188
189static void fc_exit(void)
190{
191 fmc_driver_unregister(&fc_drv);
192}
193
194module_init(fc_init);
195module_exit(fc_exit);
196
197MODULE_LICENSE("GPL");