1 /*- 2 * Copyright (c) 2009 Sam Leffler, Errno Consulting 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 ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include <sys/cdefs.h> 27 __FBSDID("$FreeBSD$"); 28 29 #include <sys/param.h> 30 #include <sys/systm.h> 31 #include <sys/bio.h> 32 #include <sys/bus.h> 33 #include <sys/conf.h> 34 #include <sys/kernel.h> 35 #include <sys/malloc.h> 36 #include <sys/lock.h> 37 #include <sys/mutex.h> 38 #include <sys/module.h> 39 #include <sys/rman.h> 40 #include <sys/sysctl.h> 41 #include <sys/taskqueue.h> 42 43 #include <machine/bus.h> 44 45 #include <dev/cfi/cfi_var.h> 46 47 #include <geom/geom.h> 48 #include <geom/geom_disk.h> 49 50 struct cfi_disk_softc { 51 struct cfi_softc *parent; 52 struct disk *disk; 53 int flags; 54 #define CFI_DISK_OPEN 0x0001 55 struct bio_queue_head bioq; /* bio queue */ 56 struct mtx qlock; /* bioq lock */ 57 struct taskqueue *tq; /* private task queue for i/o request */ 58 struct task iotask; /* i/o processing */ 59 }; 60 61 #define CFI_DISK_SECSIZE 512 62 #define CFI_DISK_MAXIOSIZE 65536 63 64 static int cfi_disk_detach(device_t); 65 static int cfi_disk_open(struct disk *); 66 static int cfi_disk_close(struct disk *); 67 static void cfi_io_proc(void *, int); 68 static int cfi_disk_getattr(struct bio *); 69 static void cfi_disk_strategy(struct bio *); 70 static int cfi_disk_ioctl(struct disk *, u_long, void *, int, struct thread *); 71 72 static int 73 cfi_disk_probe(device_t dev) 74 { 75 return 0; 76 } 77 78 static int 79 cfi_disk_attach(device_t dev) 80 { 81 struct cfi_disk_softc *sc = device_get_softc(dev); 82 83 sc->parent = device_get_softc(device_get_parent(dev)); 84 /* validate interface width; assumed by other code */ 85 if (sc->parent->sc_width != 1 && 86 sc->parent->sc_width != 2 && 87 sc->parent->sc_width != 4) 88 return EINVAL; 89 90 sc->disk = disk_alloc(); 91 if (sc->disk == NULL) 92 return ENOMEM; 93 sc->disk->d_name = "cfid"; 94 sc->disk->d_unit = device_get_unit(dev); 95 sc->disk->d_open = cfi_disk_open; 96 sc->disk->d_close = cfi_disk_close; 97 sc->disk->d_strategy = cfi_disk_strategy; 98 sc->disk->d_ioctl = cfi_disk_ioctl; 99 sc->disk->d_dump = NULL; /* NB: no dumps */ 100 sc->disk->d_getattr = cfi_disk_getattr; 101 sc->disk->d_sectorsize = CFI_DISK_SECSIZE; 102 sc->disk->d_mediasize = sc->parent->sc_size; 103 sc->disk->d_maxsize = CFI_DISK_MAXIOSIZE; 104 /* NB: use stripesize to hold the erase/region size */ 105 if (sc->parent->sc_regions) { 106 /* 107 * Multiple regions, use the last one. This is a 108 * total hack as it's (presently) used only by 109 * geom_redboot to locate the FIS directory which 110 * lies at the start of the last erase region. 111 */ 112 sc->disk->d_stripesize = 113 sc->parent->sc_region[sc->parent->sc_regions-1].r_blksz; 114 } else 115 sc->disk->d_stripesize = sc->disk->d_mediasize; 116 sc->disk->d_drv1 = sc; 117 disk_create(sc->disk, DISK_VERSION); 118 119 mtx_init(&sc->qlock, "CFID I/O lock", NULL, MTX_DEF); 120 bioq_init(&sc->bioq); 121 122 sc->tq = taskqueue_create("cfid_taskq", M_NOWAIT, 123 taskqueue_thread_enqueue, &sc->tq); 124 taskqueue_start_threads(&sc->tq, 1, PI_DISK, "cfid taskq"); 125 126 TASK_INIT(&sc->iotask, 0, cfi_io_proc, sc); 127 128 return 0; 129 } 130 131 static int 132 cfi_disk_detach(device_t dev) 133 { 134 struct cfi_disk_softc *sc = device_get_softc(dev); 135 136 if (sc->flags & CFI_DISK_OPEN) 137 return EBUSY; 138 taskqueue_free(sc->tq); 139 /* XXX drain bioq */ 140 disk_destroy(sc->disk); 141 mtx_destroy(&sc->qlock); 142 return 0; 143 } 144 145 static int 146 cfi_disk_open(struct disk *dp) 147 { 148 struct cfi_disk_softc *sc = dp->d_drv1; 149 150 /* XXX no interlock with /dev/cfi */ 151 sc->flags |= CFI_DISK_OPEN; 152 return 0; 153 } 154 155 static int 156 cfi_disk_close(struct disk *dp) 157 { 158 struct cfi_disk_softc *sc = dp->d_drv1; 159 160 sc->flags &= ~CFI_DISK_OPEN; 161 return 0; 162 } 163 164 static void 165 cfi_disk_read(struct cfi_softc *sc, struct bio *bp) 166 { 167 long resid; 168 169 KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4, 170 ("sc_width %d", sc->sc_width)); 171 172 if (sc->sc_writing) { 173 bp->bio_error = cfi_block_finish(sc); 174 if (bp->bio_error) { 175 bp->bio_flags |= BIO_ERROR; 176 goto done; 177 } 178 } 179 if (bp->bio_offset > sc->sc_size) { 180 bp->bio_flags |= BIO_ERROR; 181 bp->bio_error = EIO; 182 goto done; 183 } 184 resid = bp->bio_bcount; 185 if (sc->sc_width == 1) { 186 uint8_t *dp = (uint8_t *)bp->bio_data; 187 while (resid > 0 && bp->bio_offset < sc->sc_size) { 188 *dp++ = cfi_read_raw(sc, bp->bio_offset); 189 bp->bio_offset += 1, resid -= 1; 190 } 191 } else if (sc->sc_width == 2) { 192 uint16_t *dp = (uint16_t *)bp->bio_data; 193 while (resid > 0 && bp->bio_offset < sc->sc_size) { 194 *dp++ = cfi_read_raw(sc, bp->bio_offset); 195 bp->bio_offset += 2, resid -= 2; 196 } 197 } else { 198 uint32_t *dp = (uint32_t *)bp->bio_data; 199 while (resid > 0 && bp->bio_offset < sc->sc_size) { 200 *dp++ = cfi_read_raw(sc, bp->bio_offset); 201 bp->bio_offset += 4, resid -= 4; 202 } 203 } 204 bp->bio_resid = resid; 205 done: 206 biodone(bp); 207 } 208 209 static void 210 cfi_disk_write(struct cfi_softc *sc, struct bio *bp) 211 { 212 long resid; 213 u_int top; 214 215 KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4, 216 ("sc_width %d", sc->sc_width)); 217 218 if (bp->bio_offset > sc->sc_size) { 219 bp->bio_flags |= BIO_ERROR; 220 bp->bio_error = EIO; 221 goto done; 222 } 223 resid = bp->bio_bcount; 224 while (resid > 0) { 225 /* 226 * Finish the current block if we're about to write 227 * to a different block. 228 */ 229 if (sc->sc_writing) { 230 top = sc->sc_wrofs + sc->sc_wrbufsz; 231 if (bp->bio_offset < sc->sc_wrofs || 232 bp->bio_offset >= top) 233 cfi_block_finish(sc); 234 } 235 236 /* Start writing to a (new) block if applicable. */ 237 if (!sc->sc_writing) { 238 bp->bio_error = cfi_block_start(sc, bp->bio_offset); 239 if (bp->bio_error) { 240 bp->bio_flags |= BIO_ERROR; 241 goto done; 242 } 243 } 244 245 top = sc->sc_wrofs + sc->sc_wrbufsz; 246 bcopy(bp->bio_data, 247 sc->sc_wrbuf + bp->bio_offset - sc->sc_wrofs, 248 MIN(top - bp->bio_offset, resid)); 249 resid -= MIN(top - bp->bio_offset, resid); 250 } 251 bp->bio_resid = resid; 252 done: 253 biodone(bp); 254 } 255 256 static void 257 cfi_io_proc(void *arg, int pending) 258 { 259 struct cfi_disk_softc *sc = arg; 260 struct cfi_softc *cfi = sc->parent; 261 struct bio *bp; 262 263 for (;;) { 264 mtx_lock(&sc->qlock); 265 bp = bioq_takefirst(&sc->bioq); 266 mtx_unlock(&sc->qlock); 267 if (bp == NULL) 268 break; 269 270 switch (bp->bio_cmd) { 271 case BIO_READ: 272 cfi_disk_read(cfi, bp); 273 break; 274 case BIO_WRITE: 275 cfi_disk_write(cfi, bp); 276 break; 277 } 278 } 279 } 280 281 static int 282 cfi_disk_getattr(struct bio *bp) 283 { 284 struct cfi_disk_softc *dsc; 285 struct cfi_softc *sc; 286 device_t dev; 287 288 if (bp->bio_disk == NULL || bp->bio_disk->d_drv1 == NULL) 289 return (ENXIO); 290 291 dsc = bp->bio_disk->d_drv1; 292 sc = dsc->parent; 293 dev = sc->sc_dev; 294 295 if (strcmp(bp->bio_attribute, "CFI::device") == 0) { 296 if (bp->bio_length != sizeof(dev)) 297 return (EFAULT); 298 bcopy(&dev, bp->bio_data, sizeof(dev)); 299 } else 300 return (-1); 301 return (0); 302 } 303 304 305 static void 306 cfi_disk_strategy(struct bio *bp) 307 { 308 struct cfi_disk_softc *sc = bp->bio_disk->d_drv1; 309 310 if (sc == NULL) 311 goto invalid; 312 if (bp->bio_bcount == 0) { 313 bp->bio_resid = bp->bio_bcount; 314 biodone(bp); 315 return; 316 } 317 switch (bp->bio_cmd) { 318 case BIO_READ: 319 case BIO_WRITE: 320 mtx_lock(&sc->qlock); 321 /* no value in sorting requests? */ 322 bioq_insert_tail(&sc->bioq, bp); 323 mtx_unlock(&sc->qlock); 324 taskqueue_enqueue(sc->tq, &sc->iotask); 325 return; 326 } 327 /* fall thru... */ 328 invalid: 329 bp->bio_flags |= BIO_ERROR; 330 bp->bio_error = EINVAL; 331 biodone(bp); 332 } 333 334 static int 335 cfi_disk_ioctl(struct disk *dp, u_long cmd, void *data, int fflag, 336 struct thread *td) 337 { 338 return EINVAL; 339 } 340 341 static device_method_t cfi_disk_methods[] = { 342 DEVMETHOD(device_probe, cfi_disk_probe), 343 DEVMETHOD(device_attach, cfi_disk_attach), 344 DEVMETHOD(device_detach, cfi_disk_detach), 345 346 { 0, 0 } 347 }; 348 static driver_t cfi_disk_driver = { 349 "cfid", 350 cfi_disk_methods, 351 sizeof(struct cfi_disk_softc), 352 }; 353 DRIVER_MODULE(cfid, cfi, cfi_disk_driver, cfi_diskclass, 0, NULL); 354