altera_avgen.c revision 245380
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 245380 2013-01-13 16:57:11Z rwatson $");
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	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
376void
377altera_avgen_detach(struct altera_avgen_softc *sc)
378{
379
380	destroy_dev(sc->avg_cdev);
381}
382