altera_avgen.c revision 274820
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: head/sys/dev/altera/avgen/altera_avgen.c 274820 2014-11-21 21:10:02Z 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
63devclass_t altera_avgen_devclass;
64
65static d_mmap_t altera_avgen_mmap;
66static d_read_t altera_avgen_read;
67static d_write_t altera_avgen_write;
68
69static 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
77static int
78altera_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
139static int
140altera_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
203static int
204altera_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
232static int
233altera_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
319int
320altera_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
363void
364altera_avgen_detach(struct altera_avgen_softc *sc)
365{
366
367	destroy_dev(sc->avg_cdev);
368}
369