1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2012-2013, 2016 Robert N. M. Watson
5 * All rights reserved.
6 *
7 * This software was developed by SRI International and the University of
8 * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
9 * ("CTSRD"), as part of the DARPA CRASH research programme.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD$");
35
36#include <sys/param.h>
37#include <sys/bio.h>
38#include <sys/bus.h>
39#include <sys/condvar.h>
40#include <sys/conf.h>
41#include <sys/kernel.h>
42#include <sys/lock.h>
43#include <sys/malloc.h>
44#include <sys/module.h>
45#include <sys/mutex.h>
46#include <sys/rman.h>
47#include <sys/stat.h>
48#include <sys/systm.h>
49#include <sys/uio.h>
50
51#include <geom/geom_disk.h>
52
53#include <machine/bus.h>
54#include <machine/resource.h>
55
56#include <vm/vm.h>
57
58#include <dev/altera/avgen/altera_avgen.h>
59
60/*
61 * Generic device driver for allowing read(), write(), and mmap() on
62 * memory-mapped, Avalon-attached devices.  There is no actual dependence on
63 * Avalon, so conceivably this should just be soc_dev or similar, since many
64 * system-on-chip bus environments would work fine with the same code.
65 */
66
67devclass_t altera_avgen_devclass;
68
69static d_mmap_t altera_avgen_mmap;
70static d_read_t altera_avgen_read;
71static d_write_t altera_avgen_write;
72
73#define	ALTERA_AVGEN_DEVNAME		"altera_avgen"
74#define	ALTERA_AVGEN_DEVNAME_FMT	(ALTERA_AVGEN_DEVNAME "%d")
75
76static struct cdevsw avg_cdevsw = {
77	.d_version =	D_VERSION,
78	.d_mmap =	altera_avgen_mmap,
79	.d_read =	altera_avgen_read,
80	.d_write =	altera_avgen_write,
81	.d_name =	ALTERA_AVGEN_DEVNAME,
82};
83
84#define	ALTERA_AVGEN_SECTORSIZE	512	/* Not configurable at this time. */
85
86static int
87altera_avgen_read(struct cdev *dev, struct uio *uio, int flag)
88{
89	struct altera_avgen_softc *sc;
90	u_long offset, size;
91#ifdef NOTYET
92	uint64_t v8;
93#endif
94	uint32_t v4;
95	uint16_t v2;
96	uint8_t v1;
97	u_int width;
98	int error;
99
100	sc = dev->si_drv1;
101	if ((sc->avg_flags & ALTERA_AVALON_FLAG_READ) == 0)
102		return (EACCES);
103	width = sc->avg_width;
104	if (uio->uio_offset < 0 || uio->uio_offset % width != 0 ||
105	    uio->uio_resid % width != 0)
106		return (ENODEV);
107	size = rman_get_size(sc->avg_res);
108	if ((uio->uio_offset + uio->uio_resid < 0) ||
109	    (uio->uio_offset + uio->uio_resid > size))
110		return (ENODEV);
111	while (uio->uio_resid > 0) {
112		offset = uio->uio_offset;
113		if (offset + width > size)
114			return (ENODEV);
115		switch (width) {
116		case 1:
117			v1 = bus_read_1(sc->avg_res, offset);
118			error = uiomove(&v1, sizeof(v1), uio);
119			break;
120
121		case 2:
122			v2 = bus_read_2(sc->avg_res, offset);
123			error = uiomove(&v2, sizeof(v2), uio);
124			break;
125
126		case 4:
127			v4 = bus_read_4(sc->avg_res, offset);
128			error = uiomove(&v4, sizeof(v4), uio);
129			break;
130
131#ifdef NOTYET
132		case 8:
133			v8 = bus_read_8(sc->avg_res, offset);
134			error = uiomove(&v8, sizeof(v8), uio);
135			break;
136
137#endif
138
139		default:
140			panic("%s: unexpected widthment %u", __func__, width);
141		}
142		if (error)
143			return (error);
144	}
145	return (0);
146}
147
148static int
149altera_avgen_write(struct cdev *dev, struct uio *uio, int flag)
150{
151	struct altera_avgen_softc *sc;
152	u_long offset, size;
153#ifdef NOTYET
154	uint64_t v8;
155#endif
156	uint32_t v4;
157	uint16_t v2;
158	uint8_t v1;
159	u_int width;
160	int error;
161
162	sc = dev->si_drv1;
163	if ((sc->avg_flags & ALTERA_AVALON_FLAG_WRITE) == 0)
164		return (EACCES);
165	width = sc->avg_width;
166	if (uio->uio_offset < 0 || uio->uio_offset % width != 0 ||
167	    uio->uio_resid % width != 0)
168		return (ENODEV);
169	size = rman_get_size(sc->avg_res);
170	while (uio->uio_resid > 0) {
171		offset = uio->uio_offset;
172		if (offset + width > size)
173			return (ENODEV);
174		switch (width) {
175		case 1:
176			error = uiomove(&v1, sizeof(v1), uio);
177			if (error)
178				return (error);
179			bus_write_1(sc->avg_res, offset, v1);
180			break;
181
182		case 2:
183			error = uiomove(&v2, sizeof(v2), uio);
184			if (error)
185				return (error);
186			bus_write_2(sc->avg_res, offset, v2);
187			break;
188
189		case 4:
190			error = uiomove(&v4, sizeof(v4), uio);
191			if (error)
192				return (error);
193			bus_write_4(sc->avg_res, offset, v4);
194			break;
195
196#ifdef NOTYET
197		case 8:
198			error = uiomove(&v8, sizeof(v8), uio);
199			if (error)
200				return (error);
201			bus_write_8(sc->avg_res, offset, v8);
202			break;
203#endif
204
205		default:
206			panic("%s: unexpected width %u", __func__, width);
207		}
208	}
209	return (0);
210}
211
212static int
213altera_avgen_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr,
214    int nprot, vm_memattr_t *memattr)
215{
216	struct altera_avgen_softc *sc;
217
218	sc = dev->si_drv1;
219	if (nprot & VM_PROT_READ) {
220		if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_READ) == 0)
221			return (EACCES);
222	}
223	if (nprot & VM_PROT_WRITE) {
224		if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_WRITE) == 0)
225			return (EACCES);
226	}
227	if (nprot & VM_PROT_EXECUTE) {
228		if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_EXEC) == 0)
229			return (EACCES);
230	}
231	if (trunc_page(offset) == offset &&
232	    offset + PAGE_SIZE > offset &&
233	    rman_get_size(sc->avg_res) >= offset + PAGE_SIZE) {
234		*paddr = rman_get_start(sc->avg_res) + offset;
235		*memattr = VM_MEMATTR_UNCACHEABLE;
236	} else
237		return (ENODEV);
238	return (0);
239}
240
241/*
242 * NB: We serialise block reads and writes in case the OS is generating
243 * concurrent I/O against the same block, in which case we want one I/O (or
244 * another) to win.  This is not sufficient to provide atomicity for the
245 * sector in the presence of a fail stop -- however, we're just writing this
246 * to non-persistent DRAM .. right?
247 */
248static void
249altera_avgen_disk_strategy(struct bio *bp)
250{
251	struct altera_avgen_softc *sc;
252	void *data;
253	long bcount;
254	daddr_t pblkno;
255	int error;
256
257	sc = bp->bio_disk->d_drv1;
258	data = bp->bio_data;
259	bcount = bp->bio_bcount;
260	pblkno = bp->bio_pblkno;
261	error = 0;
262
263	/*
264	 * Serialize block reads / writes.
265	 */
266	mtx_lock(&sc->avg_disk_mtx);
267	switch (bp->bio_cmd) {
268	case BIO_READ:
269		if (!(sc->avg_flags & ALTERA_AVALON_FLAG_GEOM_READ)) {
270			error = EROFS;
271			break;
272		}
273		switch (sc->avg_width) {
274		case 1:
275			bus_read_region_1(sc->avg_res,
276			    bp->bio_pblkno * ALTERA_AVGEN_SECTORSIZE,
277			    (uint8_t *)data, bcount);
278			break;
279
280		case 2:
281			bus_read_region_2(sc->avg_res,
282			    bp->bio_pblkno * ALTERA_AVGEN_SECTORSIZE,
283			    (uint16_t *)data, bcount / 2);
284			break;
285
286		case 4:
287			bus_read_region_4(sc->avg_res,
288			    bp->bio_pblkno * ALTERA_AVGEN_SECTORSIZE,
289			    (uint32_t *)data, bcount / 4);
290			break;
291
292		default:
293			panic("%s: unexpected width %u", __func__,
294			    sc->avg_width);
295		}
296		break;
297
298	case BIO_WRITE:
299		if (!(sc->avg_flags & ALTERA_AVALON_FLAG_GEOM_WRITE)) {
300			biofinish(bp, NULL, EROFS);
301			break;
302		}
303		switch (sc->avg_width) {
304		case 1:
305			bus_write_region_1(sc->avg_res,
306			    bp->bio_pblkno * ALTERA_AVGEN_SECTORSIZE,
307			    (uint8_t *)data, bcount);
308			break;
309
310		case 2:
311			bus_write_region_2(sc->avg_res,
312			    bp->bio_pblkno * ALTERA_AVGEN_SECTORSIZE,
313			    (uint16_t *)data, bcount / 2);
314			break;
315
316		case 4:
317			bus_write_region_4(sc->avg_res,
318			    bp->bio_pblkno * ALTERA_AVGEN_SECTORSIZE,
319			    (uint32_t *)data, bcount / 4);
320			break;
321
322		default:
323			panic("%s: unexpected width %u", __func__,
324			    sc->avg_width);
325		}
326		break;
327
328	default:
329		error = EOPNOTSUPP;
330		break;
331	}
332	mtx_unlock(&sc->avg_disk_mtx);
333	biofinish(bp, NULL, error);
334}
335
336static int
337altera_avgen_process_options(struct altera_avgen_softc *sc,
338    const char *str_fileio, const char *str_geomio, const char *str_mmapio,
339    const char *str_devname, int devunit)
340{
341	const char *cp;
342	device_t dev = sc->avg_dev;
343
344	/*
345	 * Check for valid combinations of options.
346	 */
347	if (str_fileio == NULL && str_geomio == NULL && str_mmapio == NULL) {
348		device_printf(dev,
349		    "at least one of %s, %s, or %s must be specified\n",
350		    ALTERA_AVALON_STR_FILEIO, ALTERA_AVALON_STR_GEOMIO,
351		    ALTERA_AVALON_STR_MMAPIO);
352		return (ENXIO);
353	}
354
355	/*
356	 * Validity check: a device can either be a GEOM device (in which case
357	 * we use GEOM to register the device node), or a special device --
358	 * but not both as that causes a collision in /dev.
359	 */
360	if (str_geomio != NULL && (str_fileio != NULL || str_mmapio != NULL)) {
361		device_printf(dev,
362		    "at most one of %s and (%s or %s) may be specified\n",
363		    ALTERA_AVALON_STR_GEOMIO, ALTERA_AVALON_STR_FILEIO,
364		    ALTERA_AVALON_STR_MMAPIO);
365		return (ENXIO);
366	}
367
368	/*
369	 * Ensure that a unit is specified if a name is also specified.
370	 */
371	if (str_devname == NULL && devunit != -1) {
372		device_printf(dev, "%s requires %s be specified\n",
373		    ALTERA_AVALON_STR_DEVUNIT, ALTERA_AVALON_STR_DEVNAME);
374		return (ENXIO);
375	}
376
377	/*
378	 * Extract, digest, and save values.
379	 */
380	switch (sc->avg_width) {
381	case 1:
382	case 2:
383	case 4:
384#ifdef NOTYET
385	case 8:
386#endif
387		break;
388
389	default:
390		device_printf(dev, "%s unsupported value %u\n",
391		    ALTERA_AVALON_STR_WIDTH, sc->avg_width);
392		return (ENXIO);
393	}
394	sc->avg_flags = 0;
395	if (str_fileio != NULL) {
396		for (cp = str_fileio; *cp != '\0'; cp++) {
397			switch (*cp) {
398			case ALTERA_AVALON_CHAR_READ:
399				sc->avg_flags |= ALTERA_AVALON_FLAG_READ;
400				break;
401
402			case ALTERA_AVALON_CHAR_WRITE:
403				sc->avg_flags |= ALTERA_AVALON_FLAG_WRITE;
404				break;
405
406			default:
407				device_printf(dev,
408				    "invalid %s character %c\n",
409				    ALTERA_AVALON_STR_FILEIO, *cp);
410				return (ENXIO);
411			}
412		}
413	}
414	if (str_geomio != NULL) {
415		for (cp = str_geomio; *cp != '\0'; cp++){
416			switch (*cp) {
417			case ALTERA_AVALON_CHAR_READ:
418				sc->avg_flags |= ALTERA_AVALON_FLAG_GEOM_READ;
419				break;
420
421			case ALTERA_AVALON_CHAR_WRITE:
422				sc->avg_flags |= ALTERA_AVALON_FLAG_GEOM_WRITE;
423				break;
424
425			default:
426				device_printf(dev,
427				    "invalid %s character %c\n",
428				    ALTERA_AVALON_STR_GEOMIO, *cp);
429				return (ENXIO);
430			}
431		}
432	}
433	if (str_mmapio != NULL) {
434		for (cp = str_mmapio; *cp != '\0'; cp++) {
435			switch (*cp) {
436			case ALTERA_AVALON_CHAR_READ:
437				sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_READ;
438				break;
439
440			case ALTERA_AVALON_CHAR_WRITE:
441				sc->avg_flags |=
442				    ALTERA_AVALON_FLAG_MMAP_WRITE;
443				break;
444
445			case ALTERA_AVALON_CHAR_EXEC:
446				sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_EXEC;
447				break;
448
449			default:
450				device_printf(dev,
451				    "invalid %s character %c\n",
452				    ALTERA_AVALON_STR_MMAPIO, *cp);
453				return (ENXIO);
454			}
455		}
456	}
457	return (0);
458}
459
460int
461altera_avgen_attach(struct altera_avgen_softc *sc, const char *str_fileio,
462    const char *str_geomio, const char *str_mmapio, const char *str_devname,
463    int devunit)
464{
465	device_t dev = sc->avg_dev;
466	int error;
467
468	error = altera_avgen_process_options(sc, str_fileio, str_geomio,
469	    str_mmapio, str_devname, devunit);
470	if (error)
471		return (error);
472
473	if (rman_get_size(sc->avg_res) >= PAGE_SIZE || str_mmapio != NULL) {
474		if (rman_get_size(sc->avg_res) % PAGE_SIZE != 0) {
475			device_printf(dev,
476			    "memory region not even multiple of page size\n");
477			return (ENXIO);
478		}
479		if (rman_get_start(sc->avg_res) % PAGE_SIZE != 0) {
480			device_printf(dev, "memory region not page-aligned\n");
481			return (ENXIO);
482		}
483	}
484
485	/*
486	 * If a GEOM permission is requested, then create the device via GEOM.
487	 * Otherwise, create a special device.  We checked during options
488	 * processing that both weren't requested a once.
489	 */
490	if (str_devname != NULL) {
491		sc->avg_name = strdup(str_devname, M_TEMP);
492		devunit = sc->avg_unit;
493	} else
494		sc->avg_name = strdup(ALTERA_AVGEN_DEVNAME, M_TEMP);
495	if (sc->avg_flags & (ALTERA_AVALON_FLAG_GEOM_READ |
496	    ALTERA_AVALON_FLAG_GEOM_WRITE)) {
497		mtx_init(&sc->avg_disk_mtx, "altera_avgen_disk", NULL,
498		    MTX_DEF);
499		sc->avg_disk = disk_alloc();
500		sc->avg_disk->d_drv1 = sc;
501		sc->avg_disk->d_strategy = altera_avgen_disk_strategy;
502		if (devunit == -1)
503			devunit = 0;
504		sc->avg_disk->d_name = sc->avg_name;
505		sc->avg_disk->d_unit = devunit;
506
507		/*
508		 * NB: As avg_res is a multiple of PAGE_SIZE, it is also a
509		 * multiple of ALTERA_AVGEN_SECTORSIZE.
510		 */
511		sc->avg_disk->d_sectorsize = ALTERA_AVGEN_SECTORSIZE;
512		sc->avg_disk->d_mediasize = rman_get_size(sc->avg_res);
513		sc->avg_disk->d_maxsize = ALTERA_AVGEN_SECTORSIZE;
514		disk_create(sc->avg_disk, DISK_VERSION);
515	} else {
516		/* Device node allocation. */
517		if (str_devname == NULL) {
518			str_devname = ALTERA_AVGEN_DEVNAME_FMT;
519			devunit = sc->avg_unit;
520		}
521		if (devunit != -1)
522			sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit,
523			    UID_ROOT, GID_WHEEL, S_IRUSR | S_IWUSR, "%s%d",
524			    str_devname, devunit);
525		else
526			sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit,
527			    UID_ROOT, GID_WHEEL, S_IRUSR | S_IWUSR,
528			    "%s", str_devname);
529		if (sc->avg_cdev == NULL) {
530			device_printf(sc->avg_dev, "%s: make_dev failed\n",
531			    __func__);
532			return (ENXIO);
533		}
534
535		/* XXXRW: Slight race between make_dev(9) and here. */
536		sc->avg_cdev->si_drv1 = sc;
537	}
538	return (0);
539}
540
541void
542altera_avgen_detach(struct altera_avgen_softc *sc)
543{
544
545	KASSERT((sc->avg_disk != NULL) || (sc->avg_cdev != NULL),
546	    ("%s: neither GEOM nor special device", __func__));
547
548	if (sc->avg_disk != NULL) {
549		disk_gone(sc->avg_disk);
550		disk_destroy(sc->avg_disk);
551		free(sc->avg_name, M_TEMP);
552		mtx_destroy(&sc->avg_disk_mtx);
553	} else {
554		destroy_dev(sc->avg_cdev);
555	}
556}
557