]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - sys/dev/gxemul/disk/gxemul_disk.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / sys / dev / gxemul / disk / gxemul_disk.c
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$
27  */
28
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
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
49 struct 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
56 static struct mtx gxemul_disk_controller_mutex;
57
58 static g_start_t        gxemul_disk_start;
59 static g_access_t       gxemul_disk_access;
60
61 struct 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
68 DECLARE_GEOM_CLASS(g_gxemul_disk_class, g_gxemul_disk);
69
70 static void     gxemul_disk_identify(driver_t *, device_t);
71 static int      gxemul_disk_probe(device_t);
72 static int      gxemul_disk_attach(device_t);
73 static void     gxemul_disk_attach_geom(void *, int);
74
75 static int      gxemul_disk_read(unsigned, void *, off_t);
76 static int      gxemul_disk_size(unsigned, uint64_t *);
77 static int      gxemul_disk_write(unsigned, const void *, off_t);
78
79 static void
80 gxemul_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
129 static int
130 gxemul_disk_access(struct g_provider *pp, int r, int w, int e)
131 {
132         return (0);
133 }
134
135 static void
136 gxemul_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
156 static int
157 gxemul_disk_probe(device_t dev)
158 {
159         device_set_desc(dev, "GXemul test disk");
160
161         return (0);
162 }
163
164 static void
165 gxemul_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
180 static int
181 gxemul_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
207 static int
208 gxemul_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
243 static int
244 gxemul_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
280 static int
281 gxemul_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
315 static 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
323 static driver_t gxemul_disk_driver = {
324         "gxemul_disk", 
325         gxemul_disk_methods, 
326         sizeof (struct gxemul_disk_softc)
327 };
328
329 static devclass_t gxemul_disk_devclass;
330 DRIVER_MODULE(gxemul_disk, nexus, gxemul_disk_driver, gxemul_disk_devclass, 0, 0);