altera_avgen_fdt.c revision 245375
1275963Srpaulo/*-
2275963Srpaulo * Copyright (c) 2012 Robert N. M. Watson
3275963Srpaulo * All rights reserved.
4275963Srpaulo *
5275963Srpaulo * This software was developed by SRI International and the University of
6275963Srpaulo * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
7275963Srpaulo * ("CTSRD"), as part of the DARPA CRASH research programme.
8275963Srpaulo *
9275963Srpaulo * Redistribution and use in source and binary forms, with or without
10275963Srpaulo * modification, are permitted provided that the following conditions
11275963Srpaulo * are met:
12275963Srpaulo * 1. Redistributions of source code must retain the above copyright
13275963Srpaulo *    notice, this list of conditions and the following disclaimer.
14275963Srpaulo * 2. Redistributions in binary form must reproduce the above copyright
15275963Srpaulo *    notice, this list of conditions and the following disclaimer in the
16275963Srpaulo *    documentation and/or other materials provided with the distribution.
17275963Srpaulo *
18275963Srpaulo * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19275963Srpaulo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20275963Srpaulo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21275963Srpaulo * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22275963Srpaulo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23275963Srpaulo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24275963Srpaulo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25275963Srpaulo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26275963Srpaulo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27275963Srpaulo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28275963Srpaulo * SUCH DAMAGE.
29275963Srpaulo */
30275963Srpaulo
31275963Srpaulo#include <sys/cdefs.h>
32275963Srpaulo__FBSDID("$FreeBSD: head/sys/dev/altera/avgen/altera_avgen_nexus.c 245375 2013-01-13 16:41:25Z rwatson $");
33275963Srpaulo
34275963Srpaulo#include <sys/param.h>
35275963Srpaulo#include <sys/bus.h>
36275963Srpaulo#include <sys/condvar.h>
37275963Srpaulo#include <sys/conf.h>
38275963Srpaulo#include <sys/kernel.h>
39275963Srpaulo#include <sys/lock.h>
40275963Srpaulo#include <sys/malloc.h>
41275963Srpaulo#include <sys/module.h>
42275963Srpaulo#include <sys/mutex.h>
43275963Srpaulo#include <sys/rman.h>
44275963Srpaulo#include <sys/stat.h>
45275963Srpaulo#include <sys/systm.h>
46275963Srpaulo#include <sys/uio.h>
47275963Srpaulo
48275963Srpaulo#include <machine/bus.h>
49275963Srpaulo#include <machine/resource.h>
50275963Srpaulo#include <machine/vm.h>
51275963Srpaulo
52275963Srpaulo#include <vm/vm.h>
53275963Srpaulo
54275963Srpaulo#include <dev/altera/avgen/altera_avgen.h>
55322724Smarius
56322724Smarius/*
57322724Smarius * Generic device driver for allowing read(), write(), and mmap() on
58322724Smarius * memory-mapped, Avalon-attached devices.  There is no actual dependence on
59322724Smarius * Avalon, so conceivably this should just be soc_dev or similar, since many
60322724Smarius * system-on-chip bus environments would work fine with the same code.
61322724Smarius */
62322724Smarius
63322724Smariusstatic d_mmap_t altera_avgen_mmap;
64322724Smariusstatic d_read_t altera_avgen_read;
65322724Smariusstatic d_write_t altera_avgen_write;
66322724Smarius
67322724Smariusstatic struct cdevsw avg_cdevsw = {
68322724Smarius	.d_version =	D_VERSION,
69322724Smarius	.d_mmap =	altera_avgen_mmap,
70322724Smarius	.d_read =	altera_avgen_read,
71322724Smarius	.d_write =	altera_avgen_write,
72322724Smarius	.d_name =	"altera_avgen",
73322724Smarius};
74322724Smarius
75322724Smariusstatic int
76322724Smariusaltera_avgen_read(struct cdev *dev, struct uio *uio, int flag)
77322724Smarius{
78322724Smarius	struct altera_avgen_softc *sc;
79322724Smarius	u_long offset, size;
80322724Smarius#ifdef NOTYET
81322724Smarius	uint64_t v8;
82322724Smarius#endif
83322724Smarius	uint32_t v4;
84322724Smarius	uint16_t v2;
85322724Smarius	uint8_t v1;
86322724Smarius	u_int width;
87322724Smarius	int error;
88322724Smarius
89322724Smarius	sc = dev->si_drv1;
90322724Smarius	if ((sc->avg_flags & ALTERA_AVALON_FLAG_READ) == 0)
91322724Smarius		return (EACCES);
92322724Smarius	width = sc->avg_width;
93322724Smarius	if (uio->uio_offset < 0 || uio->uio_offset % width != 0 ||
94322724Smarius	    uio->uio_resid % width != 0)
95322724Smarius		return (ENODEV);
96322724Smarius	size = rman_get_size(sc->avg_res);
97322724Smarius	if ((uio->uio_offset + uio->uio_resid < 0) ||
98322724Smarius	    (uio->uio_offset + uio->uio_resid > size))
99322724Smarius		return (ENODEV);
100322724Smarius	while (uio->uio_resid > 0) {
101322724Smarius		offset = uio->uio_offset;
102322724Smarius		if (offset + width > size)
103322724Smarius			return (ENODEV);
104322724Smarius		switch (width) {
105322724Smarius		case 1:
106322724Smarius			v1 = bus_read_1(sc->avg_res, offset);
107322724Smarius			error = uiomove(&v1, sizeof(v1), uio);
108322724Smarius			break;
109322724Smarius
110322724Smarius		case 2:
111275963Srpaulo			v2 = bus_read_2(sc->avg_res, offset);
112275963Srpaulo			error = uiomove(&v2, sizeof(v2), uio);
113275963Srpaulo			break;
114275963Srpaulo
115275963Srpaulo		case 4:
116275963Srpaulo			v4 = bus_read_4(sc->avg_res, offset);
117275963Srpaulo			error = uiomove(&v4, sizeof(v4), uio);
118275963Srpaulo			break;
119275963Srpaulo
120275963Srpaulo#ifdef NOTYET
121275963Srpaulo		case 8:
122275963Srpaulo			v8 = bus_read_8(sc->avg_res, offset);
123275963Srpaulo			error = uiomove(&v8, sizeof(v8), uio);
124275963Srpaulo			break;
125275963Srpaulo
126275963Srpaulo#endif
127275963Srpaulo
128275963Srpaulo		default:
129275963Srpaulo			panic("%s: unexpected widthment %u", __func__, width);
130275963Srpaulo		}
131275963Srpaulo		if (error)
132275963Srpaulo			return (error);
133275963Srpaulo	}
134275963Srpaulo	return (0);
135275963Srpaulo}
136275963Srpaulo
137275963Srpaulostatic int
138275963Srpauloaltera_avgen_write(struct cdev *dev, struct uio *uio, int flag)
139275963Srpaulo{
140275963Srpaulo	struct altera_avgen_softc *sc;
141275963Srpaulo	u_long offset, size;
142275963Srpaulo#ifdef NOTYET
143275963Srpaulo	uint64_t v8;
144275963Srpaulo#endif
145275963Srpaulo	uint32_t v4;
146275963Srpaulo	uint16_t v2;
147275963Srpaulo	uint8_t v1;
148275963Srpaulo	u_int width;
149275963Srpaulo	int error;
150275963Srpaulo
151275963Srpaulo	sc = dev->si_drv1;
152275963Srpaulo	if ((sc->avg_flags & ALTERA_AVALON_FLAG_WRITE) == 0)
153275963Srpaulo		return (EACCES);
154275963Srpaulo	width = sc->avg_width;
155275963Srpaulo	if (uio->uio_offset < 0 || uio->uio_offset % width != 0 ||
156275963Srpaulo	    uio->uio_resid % width != 0)
157275963Srpaulo		return (ENODEV);
158275963Srpaulo	size = rman_get_size(sc->avg_res);
159275963Srpaulo	while (uio->uio_resid > 0) {
160275963Srpaulo		offset = uio->uio_offset;
161275963Srpaulo		if (offset + width > size)
162275963Srpaulo			return (ENODEV);
163275963Srpaulo		switch (width) {
164275963Srpaulo		case 1:
165275963Srpaulo			error = uiomove(&v1, sizeof(v1), uio);
166275963Srpaulo			if (error)
167275963Srpaulo				return (error);
168275963Srpaulo			bus_write_1(sc->avg_res, offset, v1);
169275963Srpaulo			break;
170275963Srpaulo
171275963Srpaulo		case 2:
172275963Srpaulo			error = uiomove(&v2, sizeof(v2), uio);
173275963Srpaulo			if (error)
174275963Srpaulo				return (error);
175275963Srpaulo			bus_write_2(sc->avg_res, offset, v2);
176275963Srpaulo			break;
177275963Srpaulo
178275963Srpaulo		case 4:
179275963Srpaulo			error = uiomove(&v4, sizeof(v4), uio);
180275963Srpaulo			if (error)
181275963Srpaulo				return (error);
182275963Srpaulo			bus_write_4(sc->avg_res, offset, v4);
183275963Srpaulo			break;
184275963Srpaulo
185275963Srpaulo#ifdef NOTYET
186275963Srpaulo		case 8:
187275963Srpaulo			error = uiomove(&v8, sizeof(v8), uio);
188322724Smarius			if (error)
189322724Smarius				return (error);
190275963Srpaulo			bus_write_8(sc->avg_res, offset, v8);
191275963Srpaulo			break;
192275963Srpaulo#endif
193275963Srpaulo
194275963Srpaulo		default:
195275963Srpaulo			panic("%s: unexpected width %u", __func__, width);
196275963Srpaulo		}
197275963Srpaulo	}
198275963Srpaulo	return (0);
199275963Srpaulo}
200275963Srpaulo
201275963Srpaulostatic int
202275963Srpauloaltera_avgen_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr,
203275963Srpaulo    int nprot, vm_memattr_t *memattr)
204275963Srpaulo{
205275963Srpaulo	struct altera_avgen_softc *sc;
206275963Srpaulo
207275963Srpaulo	sc = dev->si_drv1;
208275963Srpaulo	if (nprot & VM_PROT_READ) {
209275963Srpaulo		if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_READ) == 0)
210275963Srpaulo			return (EACCES);
211275963Srpaulo	}
212275963Srpaulo	if (nprot & VM_PROT_WRITE) {
213275963Srpaulo		if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_WRITE) == 0)
214275963Srpaulo			return (EACCES);
215275963Srpaulo	}
216275963Srpaulo	if (nprot & VM_PROT_EXECUTE) {
217275963Srpaulo		if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_EXEC) == 0)
218275963Srpaulo			return (EACCES);
219275963Srpaulo	}
220275963Srpaulo	if (trunc_page(offset) == offset &&
221275963Srpaulo	    rman_get_size(sc->avg_res) >= offset + PAGE_SIZE) {
222275963Srpaulo		*paddr = rman_get_start(sc->avg_res) + offset;
223275963Srpaulo		*memattr = VM_MEMATTR_UNCACHEABLE;
224275963Srpaulo	} else
225275963Srpaulo		return (ENODEV);
226275963Srpaulo	return (0);
227275963Srpaulo}
228275963Srpaulo
229275963Srpaulostatic int
230275963Srpauloaltera_avgen_nexus_probe(device_t dev)
231275963Srpaulo{
232275963Srpaulo
233275963Srpaulo	device_set_desc(dev, "Generic Altera Avalon device attachment");
234275963Srpaulo	return (BUS_PROBE_DEFAULT);
235275963Srpaulo}
236275963Srpaulo
237275963Srpaulostatic int
238275963Srpauloaltera_avgen_process_options(struct altera_avgen_softc *sc,
239275963Srpaulo    const char *str_fileio, const char *str_mmapio, const char *str_devname,
240275963Srpaulo    int devunit)
241275963Srpaulo{
242275963Srpaulo	const char *cp;
243275963Srpaulo	device_t dev = sc->avg_dev;
244275963Srpaulo
245275963Srpaulo	/*
246275963Srpaulo	 * Check for valid combinations of options.
247275963Srpaulo	 */
248275963Srpaulo	if (str_fileio == NULL && str_mmapio == NULL) {
249275963Srpaulo		device_printf(dev,
250275963Srpaulo		    "at least one of %s or %s must be specified\n",
251275963Srpaulo		    ALTERA_AVALON_STR_FILEIO, ALTERA_AVALON_STR_MMAPIO);
252275963Srpaulo		return (ENXIO);
253275963Srpaulo	}
254275963Srpaulo	if (str_devname == NULL && devunit != -1) {
255275963Srpaulo		device_printf(dev, "%s requires %s be specified\n",
256275963Srpaulo		    ALTERA_AVALON_STR_DEVUNIT, ALTERA_AVALON_STR_DEVNAME);
257275963Srpaulo		return (ENXIO);
258275963Srpaulo	}
259275963Srpaulo
260275963Srpaulo	/*
261275963Srpaulo	 * Extract, digest, and save values.
262275963Srpaulo	 */
263275963Srpaulo	switch (sc->avg_width) {
264275963Srpaulo	case 1:
265275963Srpaulo	case 2:
266275963Srpaulo	case 4:
267275963Srpaulo#ifdef NOTYET
268275963Srpaulo	case 8:
269275963Srpaulo#endif
270275963Srpaulo		break;
271275963Srpaulo
272275963Srpaulo	default:
273275963Srpaulo		device_printf(dev, "%s unsupported value %u\n",
274275963Srpaulo		    ALTERA_AVALON_STR_WIDTH, sc->avg_width);
275275963Srpaulo		return (ENXIO);
276275963Srpaulo	}
277275963Srpaulo	sc->avg_flags = 0;
278275963Srpaulo	if (str_fileio != NULL) {
279275963Srpaulo		for (cp = str_fileio; *cp != '\0'; cp++) {
280275963Srpaulo			switch (*cp) {
281275963Srpaulo			case ALTERA_AVALON_CHAR_READ:
282275963Srpaulo				sc->avg_flags |= ALTERA_AVALON_FLAG_READ;
283275963Srpaulo				break;
284275963Srpaulo
285275963Srpaulo			case ALTERA_AVALON_CHAR_WRITE:
286275963Srpaulo				sc->avg_flags |= ALTERA_AVALON_FLAG_WRITE;
287275963Srpaulo				break;
288275963Srpaulo
289275963Srpaulo			default:
290275963Srpaulo				device_printf(dev,
291275963Srpaulo				    "invalid %s character %c\n",
292275963Srpaulo				    ALTERA_AVALON_STR_FILEIO, *cp);
293275963Srpaulo				return (ENXIO);
294275963Srpaulo			}
295275963Srpaulo		}
296275963Srpaulo	}
297275963Srpaulo	if (str_mmapio != NULL) {
298275963Srpaulo		for (cp = str_mmapio; *cp != '\0'; cp++) {
299275963Srpaulo			switch (*cp) {
300275963Srpaulo			case ALTERA_AVALON_CHAR_READ:
301275963Srpaulo				sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_READ;
302275963Srpaulo				break;
303275963Srpaulo
304275963Srpaulo			case ALTERA_AVALON_CHAR_WRITE:
305275963Srpaulo				sc->avg_flags |=
306275963Srpaulo				    ALTERA_AVALON_FLAG_MMAP_WRITE;
307275963Srpaulo				break;
308275963Srpaulo
309275963Srpaulo			case ALTERA_AVALON_CHAR_EXEC:
310275963Srpaulo				sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_EXEC;
311275963Srpaulo				break;
312275963Srpaulo
313275963Srpaulo			default:
314275963Srpaulo				device_printf(dev,
315275963Srpaulo				    "invalid %s character %c\n",
316275963Srpaulo				    ALTERA_AVALON_STR_MMAPIO, *cp);
317275963Srpaulo				return (ENXIO);
318275963Srpaulo			}
319275963Srpaulo		}
320275963Srpaulo	}
321275963Srpaulo	return (0);
322275963Srpaulo}
323275963Srpaulo
324275963Srpaulostatic int
325275963Srpauloaltera_avgen_nexus_attach(device_t dev)
326275963Srpaulo{
327275963Srpaulo	struct altera_avgen_softc *sc;
328275963Srpaulo	const char *str_fileio, *str_mmapio;
329275963Srpaulo	const char *str_devname;
330275963Srpaulo	char devname[SPECNAMELEN + 1];
331322724Smarius	int devunit, error;
332322724Smarius
333322724Smarius	sc = device_get_softc(dev);
334322724Smarius	sc->avg_dev = dev;
335322724Smarius	sc->avg_unit = device_get_unit(dev);
336322724Smarius
337322724Smarius	/*
338322724Smarius	 * Query non-standard hints to find out what operations are permitted
339322724Smarius	 * on the device, and whether it is cached.
340322724Smarius	 */
341322724Smarius	str_fileio = NULL;
342322724Smarius	str_mmapio = NULL;
343322724Smarius	str_devname = NULL;
344322724Smarius	devunit = -1;
345322724Smarius	sc->avg_width = 1;
346322724Smarius	error = resource_int_value(device_get_name(dev), device_get_unit(dev),
347322724Smarius	    ALTERA_AVALON_STR_WIDTH, &sc->avg_width);
348322724Smarius	if (error != 0 && error != ENOENT) {
349322724Smarius		device_printf(dev, "invalid %s\n", ALTERA_AVALON_STR_WIDTH);
350322724Smarius		return (error);
351322724Smarius	}
352322724Smarius	(void)resource_string_value(device_get_name(dev),
353322724Smarius	    device_get_unit(dev), ALTERA_AVALON_STR_FILEIO, &str_fileio);
354322724Smarius	(void)resource_string_value(device_get_name(dev),
355322724Smarius	    device_get_unit(dev), ALTERA_AVALON_STR_MMAPIO, &str_mmapio);
356322724Smarius	(void)resource_string_value(device_get_name(dev),
357322724Smarius	    device_get_unit(dev), ALTERA_AVALON_STR_DEVNAME, &str_devname);
358322724Smarius	(void)resource_int_value(device_get_name(dev), device_get_unit(dev),
359322724Smarius	    ALTERA_AVALON_STR_DEVUNIT, &devunit);
360322724Smarius	error = altera_avgen_process_options(sc, str_fileio, str_mmapio,
361322724Smarius	    str_devname, devunit);
362322724Smarius	if (error)
363322724Smarius		return (error);
364322724Smarius
365322724Smarius	/* Select a device name. */
366322724Smarius	if (str_devname != NULL) {
367322724Smarius		if (devunit != -1)
368322724Smarius			(void)snprintf(devname, sizeof(devname), "%s%d",
369322724Smarius			    str_devname, devunit);
370322724Smarius		else
371322724Smarius			(void)snprintf(devname, sizeof(devname), "%s",
372322724Smarius			    str_devname);
373322724Smarius	} else
374322724Smarius		snprintf(devname, sizeof(devname), "%s%d", "avgen",
375322724Smarius		    sc->avg_unit);
376322724Smarius
377322724Smarius	/* Memory allocation and checking. */
378322724Smarius	sc->avg_rid = 0;
379322724Smarius	sc->avg_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
380322724Smarius	    &sc->avg_rid, RF_ACTIVE);
381322724Smarius	if (sc->avg_res == NULL) {
382322724Smarius		device_printf(dev, "couldn't map memory\n");
383322724Smarius		return (ENXIO);
384322724Smarius	}
385322724Smarius	if (rman_get_size(sc->avg_res) >= PAGE_SIZE || str_mmapio != NULL) {
386322724Smarius		if (rman_get_size(sc->avg_res) % PAGE_SIZE != 0) {
387322724Smarius			device_printf(dev,
388322724Smarius			    "memory region not even multiple of page size\n");
389322724Smarius			error = ENXIO;
390322724Smarius			goto error;
391322724Smarius		}
392322724Smarius		if (rman_get_start(sc->avg_res) % PAGE_SIZE != 0) {
393322724Smarius			device_printf(dev, "memory region not page-aligned\n");
394322724Smarius			error = ENXIO;
395322724Smarius			goto error;
396322724Smarius		}
397322724Smarius	}
398322724Smarius
399322724Smarius	/* Device node allocation. */
400322724Smarius	if (str_devname == NULL) {
401322724Smarius		str_devname = "altera_avgen%d";
402322724Smarius		devunit = sc->avg_unit;
403322724Smarius	}
404322724Smarius	if (devunit != -1)
405322724Smarius		sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT,
406322724Smarius		    GID_WHEEL, S_IRUSR | S_IWUSR, str_devname, devunit);
407322724Smarius	else
408322724Smarius		sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT,
409322724Smarius		    GID_WHEEL, S_IRUSR | S_IWUSR, str_devname);
410322724Smarius	if (sc->avg_cdev == NULL) {
411322724Smarius		device_printf(sc->avg_dev, "%s: make_dev failed\n", __func__);
412322724Smarius		error = ENXIO;
413322724Smarius		goto error;
414322724Smarius	}
415322724Smarius	/* XXXRW: Slight race between make_dev(9) and here. */
416322724Smarius	sc->avg_cdev->si_drv1 = sc;
417322724Smarius	return (0);
418322724Smarius
419322724Smariuserror:
420322724Smarius	bus_release_resource(dev, SYS_RES_MEMORY, sc->avg_rid, sc->avg_res);
421322724Smarius	return (error);
422322724Smarius}
423322724Smarius
424322724Smariusstatic int
425322724Smariusaltera_avgen_nexus_detach(device_t dev)
426322724Smarius{
427322724Smarius	struct altera_avgen_softc *sc;
428322724Smarius
429322724Smarius	sc = device_get_softc(dev);
430322724Smarius	destroy_dev(sc->avg_cdev);
431322724Smarius	bus_release_resource(dev, SYS_RES_MEMORY, sc->avg_rid, sc->avg_res);
432322724Smarius	return (0);
433322724Smarius}
434322724Smarius
435322724Smariusstatic device_method_t altera_avgen_nexus_methods[] = {
436322724Smarius	DEVMETHOD(device_probe,		altera_avgen_nexus_probe),
437322724Smarius	DEVMETHOD(device_attach,	altera_avgen_nexus_attach),
438322724Smarius	DEVMETHOD(device_detach,	altera_avgen_nexus_detach),
439322724Smarius	{ 0, 0 }
440322724Smarius};
441322724Smarius
442322724Smariusstatic driver_t altera_avgen_nexus_driver = {
443322724Smarius	"altera_avgen",
444322724Smarius	altera_avgen_nexus_methods,
445322724Smarius	sizeof(struct altera_avgen_softc),
446322724Smarius};
447322724Smarius
448322724Smariusstatic devclass_t altera_avgen_devclass;
449322724Smarius
450322724SmariusDRIVER_MODULE(avgen, nexus, altera_avgen_nexus_driver, altera_avgen_devclass,
451322724Smarius    0, 0);
452322724Smarius