]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/altera/avgen/altera_avgen.c
Import libucl 20160812
[FreeBSD/FreeBSD.git] / sys / dev / altera / avgen / altera_avgen.c
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$");
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
51 #include <vm/vm.h>
52
53 #include <dev/altera/avgen/altera_avgen.h>
54
55 /*
56  * Generic device driver for allowing read(), write(), and mmap() on
57  * memory-mapped, Avalon-attached devices.  There is no actual dependence on
58  * Avalon, so conceivably this should just be soc_dev or similar, since many
59  * system-on-chip bus environments would work fine with the same code.
60  */
61
62 devclass_t altera_avgen_devclass;
63
64 static d_mmap_t altera_avgen_mmap;
65 static d_read_t altera_avgen_read;
66 static d_write_t altera_avgen_write;
67
68 static struct cdevsw avg_cdevsw = {
69         .d_version =    D_VERSION,
70         .d_mmap =       altera_avgen_mmap,
71         .d_read =       altera_avgen_read,
72         .d_write =      altera_avgen_write,
73         .d_name =       "altera_avgen",
74 };
75
76 static int
77 altera_avgen_read(struct cdev *dev, struct uio *uio, int flag)
78 {
79         struct altera_avgen_softc *sc;
80         u_long offset, size;
81 #ifdef NOTYET
82         uint64_t v8;
83 #endif
84         uint32_t v4;
85         uint16_t v2;
86         uint8_t v1;
87         u_int width;
88         int error;
89
90         sc = dev->si_drv1;
91         if ((sc->avg_flags & ALTERA_AVALON_FLAG_READ) == 0)
92                 return (EACCES);
93         width = sc->avg_width;
94         if (uio->uio_offset < 0 || uio->uio_offset % width != 0 ||
95             uio->uio_resid % width != 0)
96                 return (ENODEV);
97         size = rman_get_size(sc->avg_res);
98         if ((uio->uio_offset + uio->uio_resid < 0) ||
99             (uio->uio_offset + uio->uio_resid > size))
100                 return (ENODEV);
101         while (uio->uio_resid > 0) {
102                 offset = uio->uio_offset;
103                 if (offset + width > size)
104                         return (ENODEV);
105                 switch (width) {
106                 case 1:
107                         v1 = bus_read_1(sc->avg_res, offset);
108                         error = uiomove(&v1, sizeof(v1), uio);
109                         break;
110                         
111                 case 2:
112                         v2 = bus_read_2(sc->avg_res, offset);
113                         error = uiomove(&v2, sizeof(v2), uio);
114                         break;
115                         
116                 case 4:
117                         v4 = bus_read_4(sc->avg_res, offset);
118                         error = uiomove(&v4, sizeof(v4), uio);
119                         break;
120                         
121 #ifdef NOTYET
122                 case 8:
123                         v8 = bus_read_8(sc->avg_res, offset);
124                         error = uiomove(&v8, sizeof(v8), uio);
125                         break;
126                         
127 #endif
128
129                 default:
130                         panic("%s: unexpected widthment %u", __func__, width);
131                 }
132                 if (error)
133                         return (error);
134         }
135         return (0);
136 }
137
138 static int
139 altera_avgen_write(struct cdev *dev, struct uio *uio, int flag)
140 {
141         struct altera_avgen_softc *sc;
142         u_long offset, size;
143 #ifdef NOTYET
144         uint64_t v8;
145 #endif
146         uint32_t v4;
147         uint16_t v2;
148         uint8_t v1;
149         u_int width;
150         int error;
151
152         sc = dev->si_drv1;
153         if ((sc->avg_flags & ALTERA_AVALON_FLAG_WRITE) == 0)
154                 return (EACCES);
155         width = sc->avg_width;
156         if (uio->uio_offset < 0 || uio->uio_offset % width != 0 ||
157             uio->uio_resid % width != 0)
158                 return (ENODEV);
159         size = rman_get_size(sc->avg_res);
160         while (uio->uio_resid > 0) {
161                 offset = uio->uio_offset;
162                 if (offset + width > size)
163                         return (ENODEV);
164                 switch (width) {
165                 case 1:
166                         error = uiomove(&v1, sizeof(v1), uio);
167                         if (error)
168                                 return (error);
169                         bus_write_1(sc->avg_res, offset, v1);
170                         break;
171
172                 case 2:
173                         error = uiomove(&v2, sizeof(v2), uio);
174                         if (error)
175                                 return (error);
176                         bus_write_2(sc->avg_res, offset, v2);
177                         break;
178
179                 case 4:
180                         error = uiomove(&v4, sizeof(v4), uio);
181                         if (error)
182                                 return (error);
183                         bus_write_4(sc->avg_res, offset, v4);
184                         break;
185
186 #ifdef NOTYET
187                 case 8:
188                         error = uiomove(&v8, sizeof(v8), uio);
189                         if (error)
190                                 return (error);
191                         bus_write_8(sc->avg_res, offset, v8);
192                         break;
193 #endif
194
195                 default:
196                         panic("%s: unexpected width %u", __func__, width);
197                 }
198         }
199         return (0);
200 }
201
202 static int
203 altera_avgen_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr,
204     int nprot, vm_memattr_t *memattr)
205 {
206         struct altera_avgen_softc *sc;
207
208         sc = dev->si_drv1;
209         if (nprot & VM_PROT_READ) {
210                 if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_READ) == 0)
211                         return (EACCES);
212         }
213         if (nprot & VM_PROT_WRITE) {
214                 if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_WRITE) == 0)
215                         return (EACCES);
216         }
217         if (nprot & VM_PROT_EXECUTE) {
218                 if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_EXEC) == 0)
219                         return (EACCES);
220         }
221         if (trunc_page(offset) == offset &&
222             rman_get_size(sc->avg_res) >= offset + PAGE_SIZE) {
223                 *paddr = rman_get_start(sc->avg_res) + offset;
224                 *memattr = VM_MEMATTR_UNCACHEABLE;
225         } else
226                 return (ENODEV);
227         return (0);
228 }
229
230
231 static int
232 altera_avgen_process_options(struct altera_avgen_softc *sc,
233     const char *str_fileio, const char *str_mmapio, const char *str_devname,
234     int devunit)
235 {
236         const char *cp;
237         device_t dev = sc->avg_dev;
238
239         /*
240          * Check for valid combinations of options.
241          */
242         if (str_fileio == NULL && str_mmapio == NULL) {
243                 device_printf(dev,
244                     "at least one of %s or %s must be specified\n",
245                     ALTERA_AVALON_STR_FILEIO, ALTERA_AVALON_STR_MMAPIO);
246                 return (ENXIO);
247         }
248         if (str_devname == NULL && devunit != -1) {
249                 device_printf(dev, "%s requires %s be specified\n",
250                     ALTERA_AVALON_STR_DEVUNIT, ALTERA_AVALON_STR_DEVNAME);
251                 return (ENXIO);
252         }
253
254         /*
255          * Extract, digest, and save values.
256          */
257         switch (sc->avg_width) {
258         case 1:
259         case 2:
260         case 4:
261 #ifdef NOTYET
262         case 8:
263 #endif
264                 break;
265
266         default:
267                 device_printf(dev, "%s unsupported value %u\n",
268                     ALTERA_AVALON_STR_WIDTH, sc->avg_width);
269                 return (ENXIO);
270         }
271         sc->avg_flags = 0;
272         if (str_fileio != NULL) {
273                 for (cp = str_fileio; *cp != '\0'; cp++) {
274                         switch (*cp) {
275                         case ALTERA_AVALON_CHAR_READ:
276                                 sc->avg_flags |= ALTERA_AVALON_FLAG_READ;
277                                 break;
278
279                         case ALTERA_AVALON_CHAR_WRITE:
280                                 sc->avg_flags |= ALTERA_AVALON_FLAG_WRITE;
281                                 break;
282
283                         default:
284                                 device_printf(dev,
285                                     "invalid %s character %c\n", 
286                                     ALTERA_AVALON_STR_FILEIO, *cp);
287                                 return (ENXIO);
288                         }
289                 }
290         }
291         if (str_mmapio != NULL) {
292                 for (cp = str_mmapio; *cp != '\0'; cp++) {
293                         switch (*cp) {
294                         case ALTERA_AVALON_CHAR_READ:
295                                 sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_READ;
296                                 break;
297
298                         case ALTERA_AVALON_CHAR_WRITE:
299                                 sc->avg_flags |=
300                                     ALTERA_AVALON_FLAG_MMAP_WRITE;
301                                 break;
302
303                         case ALTERA_AVALON_CHAR_EXEC:
304                                 sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_EXEC;
305                                 break;
306
307                         default:
308                                 device_printf(dev,
309                                     "invalid %s character %c\n",
310                                     ALTERA_AVALON_STR_MMAPIO, *cp);
311                                 return (ENXIO);
312                         }
313                 }
314         }
315         return (0);
316 }
317
318 int
319 altera_avgen_attach(struct altera_avgen_softc *sc, const char *str_fileio,
320     const char *str_mmapio, const char *str_devname, int devunit)
321 {
322         device_t dev = sc->avg_dev;
323         int error;
324
325         error = altera_avgen_process_options(sc, str_fileio, str_mmapio,
326             str_devname, devunit);
327         if (error)
328                 return (error);
329
330         if (rman_get_size(sc->avg_res) >= PAGE_SIZE || str_mmapio != NULL) {
331                 if (rman_get_size(sc->avg_res) % PAGE_SIZE != 0) {
332                         device_printf(dev,
333                             "memory region not even multiple of page size\n");
334                         return (ENXIO);
335                 }
336                 if (rman_get_start(sc->avg_res) % PAGE_SIZE != 0) {
337                         device_printf(dev, "memory region not page-aligned\n");
338                         return (ENXIO);
339                 }
340         }
341
342         /* Device node allocation. */
343         if (str_devname == NULL) {
344                 str_devname = "altera_avgen%d";
345                 devunit = sc->avg_unit;
346         }
347         if (devunit != -1)
348                 sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT,
349                     GID_WHEEL, S_IRUSR | S_IWUSR, str_devname, devunit);
350         else
351                 sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT,
352                     GID_WHEEL, S_IRUSR | S_IWUSR, str_devname);
353         if (sc->avg_cdev == NULL) {
354                 device_printf(sc->avg_dev, "%s: make_dev failed\n", __func__);
355                 return (ENXIO);
356         }
357         /* XXXRW: Slight race between make_dev(9) and here. */
358         sc->avg_cdev->si_drv1 = sc;
359         return (0);
360 }
361
362 void
363 altera_avgen_detach(struct altera_avgen_softc *sc)
364 {
365
366         destroy_dev(sc->avg_cdev);
367 }