xref: /freebsd/sys/dev/altera/avgen/altera_avgen.c (revision 38f0b757fd84d17d0fc24739a7cda160c4516d81)
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 #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
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
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
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
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
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 	char devname[SPECNAMELEN + 1];
325 	int error;
326 
327 	error = altera_avgen_process_options(sc, str_fileio, str_mmapio,
328 	    str_devname, devunit);
329 	if (error)
330 		return (error);
331 
332 	/* Select a device name. */
333 	if (str_devname != NULL) {
334 		if (devunit != -1)
335 			(void)snprintf(devname, sizeof(devname), "%s%d",
336 			    str_devname, devunit);
337 		else
338 			(void)snprintf(devname, sizeof(devname), "%s",
339 			    str_devname);
340 	} else
341 		snprintf(devname, sizeof(devname), "%s%d", "avgen",
342 		    sc->avg_unit);
343 
344 	if (rman_get_size(sc->avg_res) >= PAGE_SIZE || str_mmapio != NULL) {
345 		if (rman_get_size(sc->avg_res) % PAGE_SIZE != 0) {
346 			device_printf(dev,
347 			    "memory region not even multiple of page size\n");
348 			return (ENXIO);
349 		}
350 		if (rman_get_start(sc->avg_res) % PAGE_SIZE != 0) {
351 			device_printf(dev, "memory region not page-aligned\n");
352 			return (ENXIO);
353 		}
354 	}
355 
356 	/* Device node allocation. */
357 	if (str_devname == NULL) {
358 		str_devname = "altera_avgen%d";
359 		devunit = sc->avg_unit;
360 	}
361 	if (devunit != -1)
362 		sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT,
363 		    GID_WHEEL, S_IRUSR | S_IWUSR, str_devname, devunit);
364 	else
365 		sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT,
366 		    GID_WHEEL, S_IRUSR | S_IWUSR, str_devname);
367 	if (sc->avg_cdev == NULL) {
368 		device_printf(sc->avg_dev, "%s: make_dev failed\n", __func__);
369 		return (ENXIO);
370 	}
371 	/* XXXRW: Slight race between make_dev(9) and here. */
372 	sc->avg_cdev->si_drv1 = sc;
373 	return (0);
374 }
375 
376 void
377 altera_avgen_detach(struct altera_avgen_softc *sc)
378 {
379 
380 	destroy_dev(sc->avg_cdev);
381 }
382