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