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
62devclass_t altera_avgen_devclass;
63
64static d_mmap_t altera_avgen_mmap;
65static d_read_t altera_avgen_read;
66static d_write_t altera_avgen_write;
67
68static 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
76static int
77altera_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
138static int
139altera_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
202static int
203altera_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
231static int
232altera_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
318int
319altera_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
362void
363altera_avgen_detach(struct altera_avgen_softc *sc)
364{
365
366	destroy_dev(sc->avg_cdev);
367}
368