1/*-
2 * Copyright (c) 2008-2012 Juli Mallett <jmallett@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD: releng/10.3/sys/dev/gxemul/disk/gxemul_disk.c 265999 2014-05-14 01:35:43Z ian $
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD: releng/10.3/sys/dev/gxemul/disk/gxemul_disk.c 265999 2014-05-14 01:35:43Z ian $");
31
32#include <sys/param.h>
33#include <sys/bio.h>
34#include <sys/systm.h>
35#include <sys/bus.h>
36#include <sys/kernel.h>
37#include <sys/lock.h>
38#include <sys/malloc.h>
39#include <sys/module.h>
40#include <sys/mutex.h>
41#include <sys/rman.h>
42
43#include <geom/geom.h>
44
45#include <machine/cpuregs.h>
46
47#include <dev/gxemul/disk/gxemul_diskreg.h>
48
49struct gxemul_disk_softc {
50	device_t sc_dev;
51	uint64_t sc_size;
52	struct g_geom *sc_geom;
53	struct g_provider *sc_provider;
54};
55
56static struct mtx gxemul_disk_controller_mutex;
57
58static g_start_t	gxemul_disk_start;
59static g_access_t	gxemul_disk_access;
60
61struct g_class g_gxemul_disk_class = {
62	.name = "GXemul",
63	.version = G_VERSION,
64	.start = gxemul_disk_start,
65	.access = gxemul_disk_access,
66};
67
68DECLARE_GEOM_CLASS(g_gxemul_disk_class, g_gxemul_disk);
69
70static void	gxemul_disk_identify(driver_t *, device_t);
71static int	gxemul_disk_probe(device_t);
72static int	gxemul_disk_attach(device_t);
73static void	gxemul_disk_attach_geom(void *, int);
74
75static int	gxemul_disk_read(unsigned, void *, off_t);
76static int	gxemul_disk_size(unsigned, uint64_t *);
77static int	gxemul_disk_write(unsigned, const void *, off_t);
78
79static void
80gxemul_disk_start(struct bio *bp)
81{
82	struct gxemul_disk_softc *sc;
83	unsigned diskid;
84	off_t offset;
85	uint8_t *buf;
86	int error;
87
88	sc = bp->bio_to->geom->softc;
89	diskid = device_get_unit(sc->sc_dev);
90
91	if ((bp->bio_length % GXEMUL_DISK_DEV_BLOCKSIZE) != 0) {
92		g_io_deliver(bp, EINVAL);
93		return;
94	}
95
96	buf = bp->bio_data;
97	offset = bp->bio_offset;
98	bp->bio_resid = bp->bio_length;
99	while (bp->bio_resid != 0) {
100		switch (bp->bio_cmd) {
101		case BIO_READ:
102			mtx_lock(&gxemul_disk_controller_mutex);
103			error = gxemul_disk_read(diskid, buf, offset);
104			mtx_unlock(&gxemul_disk_controller_mutex);
105			break;
106		case BIO_WRITE:
107			mtx_lock(&gxemul_disk_controller_mutex);
108			error = gxemul_disk_write(diskid, buf, offset);
109			mtx_unlock(&gxemul_disk_controller_mutex);
110			break;
111		default:
112			g_io_deliver(bp, EOPNOTSUPP);
113			return;
114		}
115		if (error != 0) {
116			g_io_deliver(bp, error);
117			return;
118		}
119
120		buf += GXEMUL_DISK_DEV_BLOCKSIZE;
121		offset += GXEMUL_DISK_DEV_BLOCKSIZE;
122		bp->bio_completed += GXEMUL_DISK_DEV_BLOCKSIZE;
123		bp->bio_resid -= GXEMUL_DISK_DEV_BLOCKSIZE;
124	}
125
126	g_io_deliver(bp, 0);
127}
128
129static int
130gxemul_disk_access(struct g_provider *pp, int r, int w, int e)
131{
132	return (0);
133}
134
135static void
136gxemul_disk_identify(driver_t *drv, device_t parent)
137{
138	unsigned diskid;
139
140	mtx_init(&gxemul_disk_controller_mutex, "GXemul disk controller", NULL, MTX_DEF);
141
142	mtx_lock(&gxemul_disk_controller_mutex);
143	for (diskid = 0; diskid < 0x100; diskid++) {
144		/*
145		 * If we can read at offset 0, this disk id must be
146		 * present enough.  If we get an error, stop looking.
147		 * Disks in GXemul are allocated linearly from 0.
148		 */
149		if (gxemul_disk_read(diskid, NULL, 0) != 0)
150			break;
151		BUS_ADD_CHILD(parent, 0, "gxemul_disk", diskid);
152	}
153	mtx_unlock(&gxemul_disk_controller_mutex);
154}
155
156static int
157gxemul_disk_probe(device_t dev)
158{
159	device_set_desc(dev, "GXemul test disk");
160
161	return (BUS_PROBE_NOWILDCARD);
162}
163
164static void
165gxemul_disk_attach_geom(void *arg, int flag)
166{
167	struct gxemul_disk_softc *sc;
168
169	sc = arg;
170
171	sc->sc_geom = g_new_geomf(&g_gxemul_disk_class, "%s", device_get_nameunit(sc->sc_dev));
172	sc->sc_geom->softc = sc;
173
174	sc->sc_provider = g_new_providerf(sc->sc_geom, sc->sc_geom->name);
175	sc->sc_provider->sectorsize = GXEMUL_DISK_DEV_BLOCKSIZE;
176	sc->sc_provider->mediasize = sc->sc_size;
177	g_error_provider(sc->sc_provider, 0);
178}
179
180static int
181gxemul_disk_attach(device_t dev)
182{
183	struct gxemul_disk_softc *sc;
184	unsigned diskid;
185	int error;
186
187	diskid = device_get_unit(dev);
188
189	sc = device_get_softc(dev);
190	sc->sc_dev = dev;
191	sc->sc_geom = NULL;
192	sc->sc_provider = NULL;
193
194	mtx_lock(&gxemul_disk_controller_mutex);
195	error = gxemul_disk_size(diskid, &sc->sc_size);
196	if (error != 0) {
197		mtx_unlock(&gxemul_disk_controller_mutex);
198		return (error);
199	}
200	mtx_unlock(&gxemul_disk_controller_mutex);
201
202	g_post_event(gxemul_disk_attach_geom, sc, M_WAITOK, NULL);
203
204	return (0);
205}
206
207static int
208gxemul_disk_read(unsigned diskid, void *buf, off_t off)
209{
210	const volatile void *src;
211
212	mtx_assert(&gxemul_disk_controller_mutex, MA_OWNED);
213
214	if (off < 0 || off % GXEMUL_DISK_DEV_BLOCKSIZE != 0)
215		return (EINVAL);
216
217#ifdef _LP64
218	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_OFFSET, (uint64_t)off);
219#else
220	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_OFFSET_LO,
221	    (uint32_t)(off & 0xffffffff));
222	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_OFFSET_HI,
223	    (uint32_t)((off >> 32) & 0xffffffff));
224#endif
225	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_DISKID, diskid);
226	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_START, GXEMUL_DISK_DEV_START_READ);
227	switch (GXEMUL_DISK_DEV_READ(GXEMUL_DISK_DEV_STATUS)) {
228	case GXEMUL_DISK_DEV_STATUS_FAILURE:
229		return (EIO);
230	default:
231		break;
232	}
233
234	if (buf != NULL) {
235		src = GXEMUL_DISK_DEV_FUNCTION(GXEMUL_DISK_DEV_BLOCK);
236		memcpy(buf, (const void *)(uintptr_t)src,
237		       GXEMUL_DISK_DEV_BLOCKSIZE);
238	}
239
240	return (0);
241}
242
243static int
244gxemul_disk_size(unsigned diskid, uint64_t *sizep)
245{
246	uint64_t offset, ogood;
247	uint64_t m, s;
248	int error;
249
250	m = 1;
251	s = 3;
252	ogood = 0;
253
254	for (;;) {
255		offset = (ogood * s) + (m * GXEMUL_DISK_DEV_BLOCKSIZE);
256
257		error = gxemul_disk_read(diskid, NULL, offset);
258		if (error != 0) {
259			if (m == 1 && s == 1) {
260				*sizep = ogood + GXEMUL_DISK_DEV_BLOCKSIZE;
261				return (0);
262			}
263			if (m > 1)
264				m /= 2;
265			if (s > 1)
266				s--;
267			continue;
268		}
269		if (ogood == offset) {
270			m = 1;
271			continue;
272		}
273		ogood = offset;
274		m++;
275	}
276
277	return (EDOOFUS);
278}
279
280static int
281gxemul_disk_write(unsigned diskid, const void *buf, off_t off)
282{
283	volatile void *dst;
284
285	mtx_assert(&gxemul_disk_controller_mutex, MA_OWNED);
286
287	if (off < 0 || off % GXEMUL_DISK_DEV_BLOCKSIZE != 0)
288		return (EINVAL);
289
290#ifdef _LP64
291	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_OFFSET, (uint64_t)off);
292#else
293	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_OFFSET_LO,
294	    (uint32_t)(off & 0xffffffff));
295	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_OFFSET_HI,
296	    (uint32_t)((off >> 32) & 0xffffffff));
297#endif
298
299	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_DISKID, diskid);
300
301	dst = GXEMUL_DISK_DEV_FUNCTION(GXEMUL_DISK_DEV_BLOCK);
302	memcpy((void *)(uintptr_t)dst, buf, GXEMUL_DISK_DEV_BLOCKSIZE);
303
304	GXEMUL_DISK_DEV_WRITE(GXEMUL_DISK_DEV_START, GXEMUL_DISK_DEV_START_WRITE);
305	switch (GXEMUL_DISK_DEV_READ(GXEMUL_DISK_DEV_STATUS)) {
306	case GXEMUL_DISK_DEV_STATUS_FAILURE:
307		return (EIO);
308	default:
309		break;
310	}
311
312	return (0);
313}
314
315static device_method_t gxemul_disk_methods[] = {
316	DEVMETHOD(device_probe,		gxemul_disk_probe),
317	DEVMETHOD(device_identify,      gxemul_disk_identify),
318	DEVMETHOD(device_attach,	gxemul_disk_attach),
319
320	{ 0, 0 }
321};
322
323static driver_t gxemul_disk_driver = {
324	"gxemul_disk",
325	gxemul_disk_methods,
326	sizeof (struct gxemul_disk_softc)
327};
328
329static devclass_t gxemul_disk_devclass;
330DRIVER_MODULE(gxemul_disk, nexus, gxemul_disk_driver, gxemul_disk_devclass, 0, 0);
331