1 /*-
2 * Copyright (c) 2012-2013 Robert N. M. Watson
3 * All rights reserved.
4 *
5 * This software was developed by SRI International and the University of
6 * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
7 * ("CTSRD"), as part of the DARPA CRASH research programme.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD: stable/10/sys/dev/altera/avgen/altera_avgen.c 275428 2014-12-02 21:23:13Z brooks $");
33
34 #include <sys/param.h>
35 #include <sys/bus.h>
36 #include <sys/condvar.h>
37 #include <sys/conf.h>
38 #include <sys/kernel.h>
39 #include <sys/lock.h>
40 #include <sys/malloc.h>
41 #include <sys/module.h>
42 #include <sys/mutex.h>
43 #include <sys/rman.h>
44 #include <sys/stat.h>
45 #include <sys/systm.h>
46 #include <sys/uio.h>
47
48 #include <machine/bus.h>
49 #include <machine/resource.h>
50 #include <machine/vm.h>
51
52 #include <vm/vm.h>
53
54 #include <dev/altera/avgen/altera_avgen.h>
55
56 /*
57 * Generic device driver for allowing read(), write(), and mmap() on
58 * memory-mapped, Avalon-attached devices. There is no actual dependence on
59 * Avalon, so conceivably this should just be soc_dev or similar, since many
60 * system-on-chip bus environments would work fine with the same code.
61 */
62
63 devclass_t altera_avgen_devclass;
64
65 static d_mmap_t altera_avgen_mmap;
66 static d_read_t altera_avgen_read;
67 static d_write_t altera_avgen_write;
68
69 static struct cdevsw avg_cdevsw = {
70 .d_version = D_VERSION,
71 .d_mmap = altera_avgen_mmap,
72 .d_read = altera_avgen_read,
73 .d_write = altera_avgen_write,
74 .d_name = "altera_avgen",
75 };
76
77 static int
altera_avgen_read(struct cdev * dev,struct uio * uio,int flag)78 altera_avgen_read(struct cdev *dev, struct uio *uio, int flag)
79 {
80 struct altera_avgen_softc *sc;
81 u_long offset, size;
82 #ifdef NOTYET
83 uint64_t v8;
84 #endif
85 uint32_t v4;
86 uint16_t v2;
87 uint8_t v1;
88 u_int width;
89 int error;
90
91 sc = dev->si_drv1;
92 if ((sc->avg_flags & ALTERA_AVALON_FLAG_READ) == 0)
93 return (EACCES);
94 width = sc->avg_width;
95 if (uio->uio_offset < 0 || uio->uio_offset % width != 0 ||
96 uio->uio_resid % width != 0)
97 return (ENODEV);
98 size = rman_get_size(sc->avg_res);
99 if ((uio->uio_offset + uio->uio_resid < 0) ||
100 (uio->uio_offset + uio->uio_resid > size))
101 return (ENODEV);
102 while (uio->uio_resid > 0) {
103 offset = uio->uio_offset;
104 if (offset + width > size)
105 return (ENODEV);
106 switch (width) {
107 case 1:
108 v1 = bus_read_1(sc->avg_res, offset);
109 error = uiomove(&v1, sizeof(v1), uio);
110 break;
111
112 case 2:
113 v2 = bus_read_2(sc->avg_res, offset);
114 error = uiomove(&v2, sizeof(v2), uio);
115 break;
116
117 case 4:
118 v4 = bus_read_4(sc->avg_res, offset);
119 error = uiomove(&v4, sizeof(v4), uio);
120 break;
121
122 #ifdef NOTYET
123 case 8:
124 v8 = bus_read_8(sc->avg_res, offset);
125 error = uiomove(&v8, sizeof(v8), uio);
126 break;
127
128 #endif
129
130 default:
131 panic("%s: unexpected widthment %u", __func__, width);
132 }
133 if (error)
134 return (error);
135 }
136 return (0);
137 }
138
139 static int
altera_avgen_write(struct cdev * dev,struct uio * uio,int flag)140 altera_avgen_write(struct cdev *dev, struct uio *uio, int flag)
141 {
142 struct altera_avgen_softc *sc;
143 u_long offset, size;
144 #ifdef NOTYET
145 uint64_t v8;
146 #endif
147 uint32_t v4;
148 uint16_t v2;
149 uint8_t v1;
150 u_int width;
151 int error;
152
153 sc = dev->si_drv1;
154 if ((sc->avg_flags & ALTERA_AVALON_FLAG_WRITE) == 0)
155 return (EACCES);
156 width = sc->avg_width;
157 if (uio->uio_offset < 0 || uio->uio_offset % width != 0 ||
158 uio->uio_resid % width != 0)
159 return (ENODEV);
160 size = rman_get_size(sc->avg_res);
161 while (uio->uio_resid > 0) {
162 offset = uio->uio_offset;
163 if (offset + width > size)
164 return (ENODEV);
165 switch (width) {
166 case 1:
167 error = uiomove(&v1, sizeof(v1), uio);
168 if (error)
169 return (error);
170 bus_write_1(sc->avg_res, offset, v1);
171 break;
172
173 case 2:
174 error = uiomove(&v2, sizeof(v2), uio);
175 if (error)
176 return (error);
177 bus_write_2(sc->avg_res, offset, v2);
178 break;
179
180 case 4:
181 error = uiomove(&v4, sizeof(v4), uio);
182 if (error)
183 return (error);
184 bus_write_4(sc->avg_res, offset, v4);
185 break;
186
187 #ifdef NOTYET
188 case 8:
189 error = uiomove(&v8, sizeof(v8), uio);
190 if (error)
191 return (error);
192 bus_write_8(sc->avg_res, offset, v8);
193 break;
194 #endif
195
196 default:
197 panic("%s: unexpected width %u", __func__, width);
198 }
199 }
200 return (0);
201 }
202
203 static int
altera_avgen_mmap(struct cdev * dev,vm_ooffset_t offset,vm_paddr_t * paddr,int nprot,vm_memattr_t * memattr)204 altera_avgen_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr,
205 int nprot, vm_memattr_t *memattr)
206 {
207 struct altera_avgen_softc *sc;
208
209 sc = dev->si_drv1;
210 if (nprot & VM_PROT_READ) {
211 if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_READ) == 0)
212 return (EACCES);
213 }
214 if (nprot & VM_PROT_WRITE) {
215 if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_WRITE) == 0)
216 return (EACCES);
217 }
218 if (nprot & VM_PROT_EXECUTE) {
219 if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_EXEC) == 0)
220 return (EACCES);
221 }
222 if (trunc_page(offset) == offset &&
223 rman_get_size(sc->avg_res) >= offset + PAGE_SIZE) {
224 *paddr = rman_get_start(sc->avg_res) + offset;
225 *memattr = VM_MEMATTR_UNCACHEABLE;
226 } else
227 return (ENODEV);
228 return (0);
229 }
230
231
232 static int
altera_avgen_process_options(struct altera_avgen_softc * sc,const char * str_fileio,const char * str_mmapio,const char * str_devname,int devunit)233 altera_avgen_process_options(struct altera_avgen_softc *sc,
234 const char *str_fileio, const char *str_mmapio, const char *str_devname,
235 int devunit)
236 {
237 const char *cp;
238 device_t dev = sc->avg_dev;
239
240 /*
241 * Check for valid combinations of options.
242 */
243 if (str_fileio == NULL && str_mmapio == NULL) {
244 device_printf(dev,
245 "at least one of %s or %s must be specified\n",
246 ALTERA_AVALON_STR_FILEIO, ALTERA_AVALON_STR_MMAPIO);
247 return (ENXIO);
248 }
249 if (str_devname == NULL && devunit != -1) {
250 device_printf(dev, "%s requires %s be specified\n",
251 ALTERA_AVALON_STR_DEVUNIT, ALTERA_AVALON_STR_DEVNAME);
252 return (ENXIO);
253 }
254
255 /*
256 * Extract, digest, and save values.
257 */
258 switch (sc->avg_width) {
259 case 1:
260 case 2:
261 case 4:
262 #ifdef NOTYET
263 case 8:
264 #endif
265 break;
266
267 default:
268 device_printf(dev, "%s unsupported value %u\n",
269 ALTERA_AVALON_STR_WIDTH, sc->avg_width);
270 return (ENXIO);
271 }
272 sc->avg_flags = 0;
273 if (str_fileio != NULL) {
274 for (cp = str_fileio; *cp != '\0'; cp++) {
275 switch (*cp) {
276 case ALTERA_AVALON_CHAR_READ:
277 sc->avg_flags |= ALTERA_AVALON_FLAG_READ;
278 break;
279
280 case ALTERA_AVALON_CHAR_WRITE:
281 sc->avg_flags |= ALTERA_AVALON_FLAG_WRITE;
282 break;
283
284 default:
285 device_printf(dev,
286 "invalid %s character %c\n",
287 ALTERA_AVALON_STR_FILEIO, *cp);
288 return (ENXIO);
289 }
290 }
291 }
292 if (str_mmapio != NULL) {
293 for (cp = str_mmapio; *cp != '\0'; cp++) {
294 switch (*cp) {
295 case ALTERA_AVALON_CHAR_READ:
296 sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_READ;
297 break;
298
299 case ALTERA_AVALON_CHAR_WRITE:
300 sc->avg_flags |=
301 ALTERA_AVALON_FLAG_MMAP_WRITE;
302 break;
303
304 case ALTERA_AVALON_CHAR_EXEC:
305 sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_EXEC;
306 break;
307
308 default:
309 device_printf(dev,
310 "invalid %s character %c\n",
311 ALTERA_AVALON_STR_MMAPIO, *cp);
312 return (ENXIO);
313 }
314 }
315 }
316 return (0);
317 }
318
319 int
altera_avgen_attach(struct altera_avgen_softc * sc,const char * str_fileio,const char * str_mmapio,const char * str_devname,int devunit)320 altera_avgen_attach(struct altera_avgen_softc *sc, const char *str_fileio,
321 const char *str_mmapio, const char *str_devname, int devunit)
322 {
323 device_t dev = sc->avg_dev;
324 int error;
325
326 error = altera_avgen_process_options(sc, str_fileio, str_mmapio,
327 str_devname, devunit);
328 if (error)
329 return (error);
330
331 if (rman_get_size(sc->avg_res) >= PAGE_SIZE || str_mmapio != NULL) {
332 if (rman_get_size(sc->avg_res) % PAGE_SIZE != 0) {
333 device_printf(dev,
334 "memory region not even multiple of page size\n");
335 return (ENXIO);
336 }
337 if (rman_get_start(sc->avg_res) % PAGE_SIZE != 0) {
338 device_printf(dev, "memory region not page-aligned\n");
339 return (ENXIO);
340 }
341 }
342
343 /* Device node allocation. */
344 if (str_devname == NULL) {
345 str_devname = "altera_avgen%d";
346 devunit = sc->avg_unit;
347 }
348 if (devunit != -1)
349 sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT,
350 GID_WHEEL, S_IRUSR | S_IWUSR, str_devname, devunit);
351 else
352 sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT,
353 GID_WHEEL, S_IRUSR | S_IWUSR, str_devname);
354 if (sc->avg_cdev == NULL) {
355 device_printf(sc->avg_dev, "%s: make_dev failed\n", __func__);
356 return (ENXIO);
357 }
358 /* XXXRW: Slight race between make_dev(9) and here. */
359 sc->avg_cdev->si_drv1 = sc;
360 return (0);
361 }
362
363 void
altera_avgen_detach(struct altera_avgen_softc * sc)364 altera_avgen_detach(struct altera_avgen_softc *sc)
365 {
366
367 destroy_dev(sc->avg_cdev);
368 }
369