xref: /freebsd/sys/dev/altera/avgen/altera_avgen.c (revision ab2043b81eaba0d7d7769b4a58b2b6d17bc464a3)
1 /*-
2  * Copyright (c) 2012 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 static d_mmap_t altera_avgen_mmap;
64 static d_read_t altera_avgen_read;
65 static d_write_t altera_avgen_write;
66 
67 static struct cdevsw avg_cdevsw = {
68 	.d_version =	D_VERSION,
69 	.d_mmap =	altera_avgen_mmap,
70 	.d_read =	altera_avgen_read,
71 	.d_write =	altera_avgen_write,
72 	.d_name =	"altera_avgen",
73 };
74 
75 static int
76 altera_avgen_read(struct cdev *dev, struct uio *uio, int flag)
77 {
78 	struct altera_avgen_softc *sc;
79 	u_long offset, size;
80 #ifdef NOTYET
81 	uint64_t v8;
82 #endif
83 	uint32_t v4;
84 	uint16_t v2;
85 	uint8_t v1;
86 	u_int width;
87 	int error;
88 
89 	sc = dev->si_drv1;
90 	if ((sc->avg_flags & ALTERA_AVALON_FLAG_READ) == 0)
91 		return (EACCES);
92 	width = sc->avg_width;
93 	if (uio->uio_offset < 0 || uio->uio_offset % width != 0 ||
94 	    uio->uio_resid % width != 0)
95 		return (ENODEV);
96 	size = rman_get_size(sc->avg_res);
97 	if ((uio->uio_offset + uio->uio_resid < 0) ||
98 	    (uio->uio_offset + uio->uio_resid > size))
99 		return (ENODEV);
100 	while (uio->uio_resid > 0) {
101 		offset = uio->uio_offset;
102 		if (offset + width > size)
103 			return (ENODEV);
104 		switch (width) {
105 		case 1:
106 			v1 = bus_read_1(sc->avg_res, offset);
107 			error = uiomove(&v1, sizeof(v1), uio);
108 			break;
109 
110 		case 2:
111 			v2 = bus_read_2(sc->avg_res, offset);
112 			error = uiomove(&v2, sizeof(v2), uio);
113 			break;
114 
115 		case 4:
116 			v4 = bus_read_4(sc->avg_res, offset);
117 			error = uiomove(&v4, sizeof(v4), uio);
118 			break;
119 
120 #ifdef NOTYET
121 		case 8:
122 			v8 = bus_read_8(sc->avg_res, offset);
123 			error = uiomove(&v8, sizeof(v8), uio);
124 			break;
125 
126 #endif
127 
128 		default:
129 			panic("%s: unexpected widthment %u", __func__, width);
130 		}
131 		if (error)
132 			return (error);
133 	}
134 	return (0);
135 }
136 
137 static int
138 altera_avgen_write(struct cdev *dev, struct uio *uio, int flag)
139 {
140 	struct altera_avgen_softc *sc;
141 	u_long offset, size;
142 #ifdef NOTYET
143 	uint64_t v8;
144 #endif
145 	uint32_t v4;
146 	uint16_t v2;
147 	uint8_t v1;
148 	u_int width;
149 	int error;
150 
151 	sc = dev->si_drv1;
152 	if ((sc->avg_flags & ALTERA_AVALON_FLAG_WRITE) == 0)
153 		return (EACCES);
154 	width = sc->avg_width;
155 	if (uio->uio_offset < 0 || uio->uio_offset % width != 0 ||
156 	    uio->uio_resid % width != 0)
157 		return (ENODEV);
158 	size = rman_get_size(sc->avg_res);
159 	while (uio->uio_resid > 0) {
160 		offset = uio->uio_offset;
161 		if (offset + width > size)
162 			return (ENODEV);
163 		switch (width) {
164 		case 1:
165 			error = uiomove(&v1, sizeof(v1), uio);
166 			if (error)
167 				return (error);
168 			bus_write_1(sc->avg_res, offset, v1);
169 			break;
170 
171 		case 2:
172 			error = uiomove(&v2, sizeof(v2), uio);
173 			if (error)
174 				return (error);
175 			bus_write_2(sc->avg_res, offset, v2);
176 			break;
177 
178 		case 4:
179 			error = uiomove(&v4, sizeof(v4), uio);
180 			if (error)
181 				return (error);
182 			bus_write_4(sc->avg_res, offset, v4);
183 			break;
184 
185 #ifdef NOTYET
186 		case 8:
187 			error = uiomove(&v8, sizeof(v8), uio);
188 			if (error)
189 				return (error);
190 			bus_write_8(sc->avg_res, offset, v8);
191 			break;
192 #endif
193 
194 		default:
195 			panic("%s: unexpected width %u", __func__, width);
196 		}
197 	}
198 	return (0);
199 }
200 
201 static int
202 altera_avgen_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr,
203     int nprot, vm_memattr_t *memattr)
204 {
205 	struct altera_avgen_softc *sc;
206 
207 	sc = dev->si_drv1;
208 	if (nprot & VM_PROT_READ) {
209 		if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_READ) == 0)
210 			return (EACCES);
211 	}
212 	if (nprot & VM_PROT_WRITE) {
213 		if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_WRITE) == 0)
214 			return (EACCES);
215 	}
216 	if (nprot & VM_PROT_EXECUTE) {
217 		if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_EXEC) == 0)
218 			return (EACCES);
219 	}
220 	if (trunc_page(offset) == offset &&
221 	    rman_get_size(sc->avg_res) >= offset + PAGE_SIZE) {
222 		*paddr = rman_get_start(sc->avg_res) + offset;
223 		*memattr = VM_MEMATTR_UNCACHEABLE;
224 	} else
225 		return (ENODEV);
226 	return (0);
227 }
228 
229 static int
230 altera_avgen_nexus_probe(device_t dev)
231 {
232 
233 	device_set_desc(dev, "Generic Altera Avalon device attachment");
234 	return (BUS_PROBE_DEFAULT);
235 }
236 
237 static int
238 altera_avgen_process_options(struct altera_avgen_softc *sc,
239     const char *str_fileio, const char *str_mmapio, const char *str_devname,
240     int devunit)
241 {
242 	const char *cp;
243 	device_t dev = sc->avg_dev;
244 
245 	/*
246 	 * Check for valid combinations of options.
247 	 */
248 	if (str_fileio == NULL && str_mmapio == NULL) {
249 		device_printf(dev,
250 		    "at least one of %s or %s must be specified\n",
251 		    ALTERA_AVALON_STR_FILEIO, ALTERA_AVALON_STR_MMAPIO);
252 		return (ENXIO);
253 	}
254 	if (str_devname == NULL && devunit != -1) {
255 		device_printf(dev, "%s requires %s be specified\n",
256 		    ALTERA_AVALON_STR_DEVUNIT, ALTERA_AVALON_STR_DEVNAME);
257 		return (ENXIO);
258 	}
259 
260 	/*
261 	 * Extract, digest, and save values.
262 	 */
263 	switch (sc->avg_width) {
264 	case 1:
265 	case 2:
266 	case 4:
267 #ifdef NOTYET
268 	case 8:
269 #endif
270 		break;
271 
272 	default:
273 		device_printf(dev, "%s unsupported value %u\n",
274 		    ALTERA_AVALON_STR_WIDTH, sc->avg_width);
275 		return (ENXIO);
276 	}
277 	sc->avg_flags = 0;
278 	if (str_fileio != NULL) {
279 		for (cp = str_fileio; *cp != '\0'; cp++) {
280 			switch (*cp) {
281 			case ALTERA_AVALON_CHAR_READ:
282 				sc->avg_flags |= ALTERA_AVALON_FLAG_READ;
283 				break;
284 
285 			case ALTERA_AVALON_CHAR_WRITE:
286 				sc->avg_flags |= ALTERA_AVALON_FLAG_WRITE;
287 				break;
288 
289 			default:
290 				device_printf(dev,
291 				    "invalid %s character %c\n",
292 				    ALTERA_AVALON_STR_FILEIO, *cp);
293 				return (ENXIO);
294 			}
295 		}
296 	}
297 	if (str_mmapio != NULL) {
298 		for (cp = str_mmapio; *cp != '\0'; cp++) {
299 			switch (*cp) {
300 			case ALTERA_AVALON_CHAR_READ:
301 				sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_READ;
302 				break;
303 
304 			case ALTERA_AVALON_CHAR_WRITE:
305 				sc->avg_flags |=
306 				    ALTERA_AVALON_FLAG_MMAP_WRITE;
307 				break;
308 
309 			case ALTERA_AVALON_CHAR_EXEC:
310 				sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_EXEC;
311 				break;
312 
313 			default:
314 				device_printf(dev,
315 				    "invalid %s character %c\n",
316 				    ALTERA_AVALON_STR_MMAPIO, *cp);
317 				return (ENXIO);
318 			}
319 		}
320 	}
321 	return (0);
322 }
323 
324 static int
325 altera_avgen_nexus_attach(device_t dev)
326 {
327 	struct altera_avgen_softc *sc;
328 	const char *str_fileio, *str_mmapio;
329 	const char *str_devname;
330 	char devname[SPECNAMELEN + 1];
331 	int devunit, error;
332 
333 	sc = device_get_softc(dev);
334 	sc->avg_dev = dev;
335 	sc->avg_unit = device_get_unit(dev);
336 
337 	/*
338 	 * Query non-standard hints to find out what operations are permitted
339 	 * on the device, and whether it is cached.
340 	 */
341 	str_fileio = NULL;
342 	str_mmapio = NULL;
343 	str_devname = NULL;
344 	devunit = -1;
345 	sc->avg_width = 1;
346 	error = resource_int_value(device_get_name(dev), device_get_unit(dev),
347 	    ALTERA_AVALON_STR_WIDTH, &sc->avg_width);
348 	if (error != 0 && error != ENOENT) {
349 		device_printf(dev, "invalid %s\n", ALTERA_AVALON_STR_WIDTH);
350 		return (error);
351 	}
352 	(void)resource_string_value(device_get_name(dev),
353 	    device_get_unit(dev), ALTERA_AVALON_STR_FILEIO, &str_fileio);
354 	(void)resource_string_value(device_get_name(dev),
355 	    device_get_unit(dev), ALTERA_AVALON_STR_MMAPIO, &str_mmapio);
356 	(void)resource_string_value(device_get_name(dev),
357 	    device_get_unit(dev), ALTERA_AVALON_STR_DEVNAME, &str_devname);
358 	(void)resource_int_value(device_get_name(dev), device_get_unit(dev),
359 	    ALTERA_AVALON_STR_DEVUNIT, &devunit);
360 	error = altera_avgen_process_options(sc, str_fileio, str_mmapio,
361 	    str_devname, devunit);
362 	if (error)
363 		return (error);
364 
365 	/* Select a device name. */
366 	if (str_devname != NULL) {
367 		if (devunit != -1)
368 			(void)snprintf(devname, sizeof(devname), "%s%d",
369 			    str_devname, devunit);
370 		else
371 			(void)snprintf(devname, sizeof(devname), "%s",
372 			    str_devname);
373 	} else
374 		snprintf(devname, sizeof(devname), "%s%d", "avgen",
375 		    sc->avg_unit);
376 
377 	/* Memory allocation and checking. */
378 	sc->avg_rid = 0;
379 	sc->avg_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
380 	    &sc->avg_rid, RF_ACTIVE);
381 	if (sc->avg_res == NULL) {
382 		device_printf(dev, "couldn't map memory\n");
383 		return (ENXIO);
384 	}
385 	if (rman_get_size(sc->avg_res) >= PAGE_SIZE || str_mmapio != NULL) {
386 		if (rman_get_size(sc->avg_res) % PAGE_SIZE != 0) {
387 			device_printf(dev,
388 			    "memory region not even multiple of page size\n");
389 			error = ENXIO;
390 			goto error;
391 		}
392 		if (rman_get_start(sc->avg_res) % PAGE_SIZE != 0) {
393 			device_printf(dev, "memory region not page-aligned\n");
394 			error = ENXIO;
395 			goto error;
396 		}
397 	}
398 
399 	/* Device node allocation. */
400 	if (str_devname == NULL) {
401 		str_devname = "altera_avgen%d";
402 		devunit = sc->avg_unit;
403 	}
404 	if (devunit != -1)
405 		sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT,
406 		    GID_WHEEL, S_IRUSR | S_IWUSR, str_devname, devunit);
407 	else
408 		sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT,
409 		    GID_WHEEL, S_IRUSR | S_IWUSR, str_devname);
410 	if (sc->avg_cdev == NULL) {
411 		device_printf(sc->avg_dev, "%s: make_dev failed\n", __func__);
412 		error = ENXIO;
413 		goto error;
414 	}
415 	/* XXXRW: Slight race between make_dev(9) and here. */
416 	sc->avg_cdev->si_drv1 = sc;
417 	return (0);
418 
419 error:
420 	bus_release_resource(dev, SYS_RES_MEMORY, sc->avg_rid, sc->avg_res);
421 	return (error);
422 }
423 
424 static int
425 altera_avgen_nexus_detach(device_t dev)
426 {
427 	struct altera_avgen_softc *sc;
428 
429 	sc = device_get_softc(dev);
430 	destroy_dev(sc->avg_cdev);
431 	bus_release_resource(dev, SYS_RES_MEMORY, sc->avg_rid, sc->avg_res);
432 	return (0);
433 }
434 
435 static device_method_t altera_avgen_nexus_methods[] = {
436 	DEVMETHOD(device_probe,		altera_avgen_nexus_probe),
437 	DEVMETHOD(device_attach,	altera_avgen_nexus_attach),
438 	DEVMETHOD(device_detach,	altera_avgen_nexus_detach),
439 	{ 0, 0 }
440 };
441 
442 static driver_t altera_avgen_nexus_driver = {
443 	"altera_avgen",
444 	altera_avgen_nexus_methods,
445 	sizeof(struct altera_avgen_softc),
446 };
447 
448 static devclass_t altera_avgen_devclass;
449 
450 DRIVER_MODULE(avgen, nexus, altera_avgen_nexus_driver, altera_avgen_devclass,
451     0, 0);
452