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