diff options
author | Alessandro Rubini <rubini@gnudd.com> | 2013-06-18 17:48:07 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-06-18 18:42:15 -0400 |
commit | 4debfe409b6e550032bfef9733e9f6f7c5613617 (patch) | |
tree | c647e07600d964c6399e20f04c68ea8467c7b328 | |
parent | 6007b1bd0f752a5c022f7944c65fb96c39d6db3d (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-INDEX | 5 | ||||
-rw-r--r-- | Documentation/fmc/fmc-chardev.txt | 64 | ||||
-rw-r--r-- | drivers/fmc/Kconfig | 8 | ||||
-rw-r--r-- | drivers/fmc/Makefile | 1 | ||||
-rw-r--r-- | drivers/fmc/fmc-chardev.c | 197 |
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 | ||
34 | fmc-write-eeprom.txt | 34 | fmc-write-eeprom.txt |
35 | - about drivers/fmc/fmc-write-eeprom.ko | 35 | - about drivers/fmc/fmc-write-eeprom.ko |
36 | |||
37 | fmc-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 @@ | |||
1 | fmc-chardev | ||
2 | =========== | ||
3 | |||
4 | This is a simple generic driver, that allows user access by means of a | ||
5 | character device (actually, one for each mezzanine it takes hold of). | ||
6 | |||
7 | The char device is created as a misc device. Its name in /dev (as | ||
8 | created by udev) is the same name as the underlying FMC device. Thus, | ||
9 | the name can be a silly fmc-0000 look-alike if the device has no | ||
10 | identifiers nor bus_id, a more specific fmc-0400 if the device has a | ||
11 | bus-specific address but no associated name, or something like | ||
12 | fdelay-0400 if the FMC core can rely on both a mezzanine name and a bus | ||
13 | address. | ||
14 | |||
15 | Currently the driver only supports read and write: you can lseek to the | ||
16 | desired address and read or write a register. | ||
17 | |||
18 | The driver assumes all registers are 32-bit in size, and only accepts a | ||
19 | single read or write per system call. However, as a result of Unix read | ||
20 | and write semantics, users can simply fread or fwrite bigger areas in | ||
21 | order to dump or store bigger memory areas. | ||
22 | |||
23 | There is currently no support for mmap, user-space interrupt management | ||
24 | and DMA buffers. They may be added in later versions, if the need | ||
25 | arises. | ||
26 | |||
27 | The example below shows raw access to a SPEC card programmed with its | ||
28 | golden FPGA file, that features an SDB structure at offset 256 - i.e. | ||
29 | 64 words. The mezzanine's EEPROM in this case is not programmed, so the | ||
30 | default 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 | |||
44 | The simple program tools/fmc-mem in this package can access an FMC char | ||
45 | device and read or write a word or a whole area. Actually, the program | ||
46 | is not specific to FMC at all, it just uses lseek, read and write. | ||
47 | |||
48 | Its 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 | ||
50 | begin with "+" is the number of bytes to read or write. In case of | ||
51 | repeated reading data is written to stdout; repeated writes read from | ||
52 | stdin and the value argument is ignored. | ||
53 | |||
54 | The following examples show reading the SDB magic number and the first | ||
55 | SDB 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 | ||
43 | config 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 | |||
43 | endif # FMC | 51 | endif # 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 | |||
10 | obj-$(CONFIG_FMC_FAKEDEV) += fmc-fakedev.o | 10 | obj-$(CONFIG_FMC_FAKEDEV) += fmc-fakedev.o |
11 | obj-$(CONFIG_FMC_TRIVIAL) += fmc-trivial.o | 11 | obj-$(CONFIG_FMC_TRIVIAL) += fmc-trivial.o |
12 | obj-$(CONFIG_FMC_WRITE_EEPROM) += fmc-write-eeprom.o | 12 | obj-$(CONFIG_FMC_WRITE_EEPROM) += fmc-write-eeprom.o |
13 | obj-$(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 | |||
20 | static LIST_HEAD(fc_devices); | ||
21 | static DEFINE_SPINLOCK(fc_lock); | ||
22 | |||
23 | struct 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 */ | ||
30 | static 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 | |||
49 | static 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 */ | ||
57 | static 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 | |||
78 | static 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 | |||
99 | static 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 .. */ | ||
110 | static int fc_probe(struct fmc_device *fmc); | ||
111 | static int fc_remove(struct fmc_device *fmc); | ||
112 | |||
113 | static 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 */ | ||
122 | FMC_PARAM_BUSID(fc_drv); | ||
123 | |||
124 | /* probe and remove must allocate and release a misc device */ | ||
125 | static 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 | |||
158 | static 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 | |||
181 | static int fc_init(void) | ||
182 | { | ||
183 | int ret; | ||
184 | |||
185 | ret = fmc_driver_register(&fc_drv); | ||
186 | return ret; | ||
187 | } | ||
188 | |||
189 | static void fc_exit(void) | ||
190 | { | ||
191 | fmc_driver_unregister(&fc_drv); | ||
192 | } | ||
193 | |||
194 | module_init(fc_init); | ||
195 | module_exit(fc_exit); | ||
196 | |||
197 | MODULE_LICENSE("GPL"); | ||