cfi_disk.c revision 189606
1189606Ssam/*- 2189606Ssam * Copyright (c) 2009 Sam Leffler, Errno Consulting 3189606Ssam * All rights reserved. 4189606Ssam * 5189606Ssam * Redistribution and use in source and binary forms, with or without 6189606Ssam * modification, are permitted provided that the following conditions 7189606Ssam * are met: 8189606Ssam * 1. Redistributions of source code must retain the above copyright 9189606Ssam * notice, this list of conditions and the following disclaimer. 10189606Ssam * 2. Redistributions in binary form must reproduce the above copyright 11189606Ssam * notice, this list of conditions and the following disclaimer in the 12189606Ssam * documentation and/or other materials provided with the distribution. 13189606Ssam * 14189606Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15189606Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16189606Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17189606Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18189606Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19189606Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20189606Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21189606Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22189606Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23189606Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24189606Ssam */ 25189606Ssam 26189606Ssam#include <sys/cdefs.h> 27189606Ssam__FBSDID("$FreeBSD: head/sys/dev/cfi/cfi_disk.c 189606 2009-03-09 23:16:02Z sam $"); 28189606Ssam 29189606Ssam#include <sys/param.h> 30189606Ssam#include <sys/systm.h> 31189606Ssam#include <sys/bio.h> 32189606Ssam#include <sys/bus.h> 33189606Ssam#include <sys/conf.h> 34189606Ssam#include <sys/kernel.h> 35189606Ssam#include <sys/malloc.h> 36189606Ssam#include <sys/lock.h> 37189606Ssam#include <sys/mutex.h> 38189606Ssam#include <sys/module.h> 39189606Ssam#include <sys/rman.h> 40189606Ssam#include <sys/sysctl.h> 41189606Ssam#include <sys/taskqueue.h> 42189606Ssam 43189606Ssam#include <machine/bus.h> 44189606Ssam 45189606Ssam#include <dev/cfi/cfi_var.h> 46189606Ssam 47189606Ssam#include <geom/geom_disk.h> 48189606Ssam 49189606Ssamstruct cfi_disk_softc { 50189606Ssam struct cfi_softc *parent; 51189606Ssam struct disk *disk; 52189606Ssam int flags; 53189606Ssam#define CFI_DISK_OPEN 0x0001 54189606Ssam struct bio_queue_head bioq; /* bio queue */ 55189606Ssam struct mtx qlock; /* bioq lock */ 56189606Ssam struct taskqueue *tq; /* private task queue for i/o request */ 57189606Ssam struct task iotask; /* i/o processing */ 58189606Ssam}; 59189606Ssam 60189606Ssam#define CFI_DISK_SECSIZE 512 61189606Ssam#define CFI_DISK_MAXIOSIZE 65536 62189606Ssam 63189606Ssamstatic int cfi_disk_detach(device_t); 64189606Ssamstatic int cfi_disk_open(struct disk *); 65189606Ssamstatic int cfi_disk_close(struct disk *); 66189606Ssamstatic void cfi_io_proc(void *, int); 67189606Ssamstatic void cfi_disk_strategy(struct bio *); 68189606Ssamstatic int cfi_disk_ioctl(struct disk *, u_long, void *, int, struct thread *); 69189606Ssam 70189606Ssamstatic int 71189606Ssamcfi_disk_probe(device_t dev) 72189606Ssam{ 73189606Ssam return 0; 74189606Ssam} 75189606Ssam 76189606Ssamstatic int 77189606Ssamcfi_disk_attach(device_t dev) 78189606Ssam{ 79189606Ssam struct cfi_disk_softc *sc = device_get_softc(dev); 80189606Ssam 81189606Ssam sc->parent = device_get_softc(device_get_parent(dev)); 82189606Ssam /* validate interface width; assumed by other code */ 83189606Ssam if (sc->parent->sc_width != 1 && 84189606Ssam sc->parent->sc_width != 2 && 85189606Ssam sc->parent->sc_width != 4) 86189606Ssam return EINVAL; 87189606Ssam 88189606Ssam sc->disk = disk_alloc(); 89189606Ssam if (sc->disk == NULL) 90189606Ssam return ENOMEM; 91189606Ssam sc->disk->d_name = "cfid"; 92189606Ssam sc->disk->d_unit = device_get_unit(dev); 93189606Ssam sc->disk->d_open = cfi_disk_open; 94189606Ssam sc->disk->d_close = cfi_disk_close; 95189606Ssam sc->disk->d_strategy = cfi_disk_strategy; 96189606Ssam sc->disk->d_ioctl = cfi_disk_ioctl; 97189606Ssam sc->disk->d_dump = NULL; /* NB: no dumps */ 98189606Ssam sc->disk->d_sectorsize = CFI_DISK_SECSIZE; 99189606Ssam sc->disk->d_mediasize = sc->parent->sc_size; 100189606Ssam sc->disk->d_maxsize = CFI_DISK_MAXIOSIZE; 101189606Ssam /* NB: use stripesize to hold the erase/region size */ 102189606Ssam if (sc->parent->sc_regions) 103189606Ssam sc->disk->d_stripesize = sc->parent->sc_region->r_blksz; 104189606Ssam else 105189606Ssam sc->disk->d_stripesize = sc->disk->d_mediasize; 106189606Ssam sc->disk->d_drv1 = sc; 107189606Ssam disk_create(sc->disk, DISK_VERSION); 108189606Ssam 109189606Ssam mtx_init(&sc->qlock, "CFID I/O lock", NULL, MTX_DEF); 110189606Ssam bioq_init(&sc->bioq); 111189606Ssam 112189606Ssam sc->tq = taskqueue_create("cfid_taskq", M_NOWAIT, 113189606Ssam taskqueue_thread_enqueue, &sc->tq); 114189606Ssam taskqueue_start_threads(&sc->tq, 1, PI_DISK, "cfid taskq"); 115189606Ssam 116189606Ssam TASK_INIT(&sc->iotask, 0, cfi_io_proc, sc); 117189606Ssam 118189606Ssam return 0; 119189606Ssam} 120189606Ssam 121189606Ssamstatic int 122189606Ssamcfi_disk_detach(device_t dev) 123189606Ssam{ 124189606Ssam struct cfi_disk_softc *sc = device_get_softc(dev); 125189606Ssam 126189606Ssam if (sc->flags & CFI_DISK_OPEN) 127189606Ssam return EBUSY; 128189606Ssam taskqueue_free(sc->tq); 129189606Ssam /* XXX drain bioq */ 130189606Ssam disk_destroy(sc->disk); 131189606Ssam mtx_destroy(&sc->qlock); 132189606Ssam return 0; 133189606Ssam} 134189606Ssam 135189606Ssamstatic int 136189606Ssamcfi_disk_open(struct disk *dp) 137189606Ssam{ 138189606Ssam struct cfi_disk_softc *sc = dp->d_drv1; 139189606Ssam 140189606Ssam /* XXX no interlock with /dev/cfi */ 141189606Ssam sc->flags |= CFI_DISK_OPEN; 142189606Ssam return 0; 143189606Ssam} 144189606Ssam 145189606Ssamstatic int 146189606Ssamcfi_disk_close(struct disk *dp) 147189606Ssam{ 148189606Ssam struct cfi_disk_softc *sc = dp->d_drv1; 149189606Ssam 150189606Ssam sc->flags &= ~CFI_DISK_OPEN; 151189606Ssam return 0; 152189606Ssam} 153189606Ssam 154189606Ssamstatic void 155189606Ssamcfi_disk_read(struct cfi_softc *sc, struct bio *bp) 156189606Ssam{ 157189606Ssam long resid; 158189606Ssam 159189606Ssam KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4, 160189606Ssam ("sc_width %d", sc->sc_width)); 161189606Ssam 162189606Ssam if (sc->sc_writing) { 163189606Ssam bp->bio_error = cfi_block_finish(sc); 164189606Ssam if (bp->bio_error) { 165189606Ssam bp->bio_flags |= BIO_ERROR; 166189606Ssam goto done; 167189606Ssam } 168189606Ssam } 169189606Ssam if (bp->bio_offset > sc->sc_size) { 170189606Ssam bp->bio_flags |= BIO_ERROR; 171189606Ssam bp->bio_error = EIO; 172189606Ssam goto done; 173189606Ssam } 174189606Ssam resid = bp->bio_bcount; 175189606Ssam if (sc->sc_width == 1) { 176189606Ssam uint8_t *dp = (uint8_t *)bp->bio_data; 177189606Ssam while (resid > 0 && bp->bio_offset < sc->sc_size) { 178189606Ssam *dp++ = cfi_read(sc, bp->bio_offset); 179189606Ssam bp->bio_offset += 1, resid -= 1; 180189606Ssam } 181189606Ssam } else if (sc->sc_width == 2) { 182189606Ssam uint16_t *dp = (uint16_t *)bp->bio_data; 183189606Ssam while (resid > 0 && bp->bio_offset < sc->sc_size) { 184189606Ssam *dp++ = cfi_read(sc, bp->bio_offset); 185189606Ssam bp->bio_offset += 2, resid -= 2; 186189606Ssam } 187189606Ssam } else { 188189606Ssam uint32_t *dp = (uint32_t *)bp->bio_data; 189189606Ssam while (resid > 0 && bp->bio_offset < sc->sc_size) { 190189606Ssam *dp++ = cfi_read(sc, bp->bio_offset); 191189606Ssam bp->bio_offset += 4, resid -= 4; 192189606Ssam } 193189606Ssam } 194189606Ssam bp->bio_resid = resid; 195189606Ssamdone: 196189606Ssam biodone(bp); 197189606Ssam} 198189606Ssam 199189606Ssamstatic void 200189606Ssamcfi_disk_write(struct cfi_softc *sc, struct bio *bp) 201189606Ssam{ 202189606Ssam long resid; 203189606Ssam u_int top; 204189606Ssam 205189606Ssam KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4, 206189606Ssam ("sc_width %d", sc->sc_width)); 207189606Ssam 208189606Ssam if (bp->bio_offset > sc->sc_size) { 209189606Ssam bp->bio_flags |= BIO_ERROR; 210189606Ssam bp->bio_error = EIO; 211189606Ssam goto done; 212189606Ssam } 213189606Ssam resid = bp->bio_bcount; 214189606Ssam while (resid > 0) { 215189606Ssam /* 216189606Ssam * Finish the current block if we're about to write 217189606Ssam * to a different block. 218189606Ssam */ 219189606Ssam if (sc->sc_writing) { 220189606Ssam top = sc->sc_wrofs + sc->sc_wrbufsz; 221189606Ssam if (bp->bio_offset < sc->sc_wrofs || 222189606Ssam bp->bio_offset >= top) 223189606Ssam cfi_block_finish(sc); 224189606Ssam } 225189606Ssam 226189606Ssam /* Start writing to a (new) block if applicable. */ 227189606Ssam if (!sc->sc_writing) { 228189606Ssam bp->bio_error = cfi_block_start(sc, bp->bio_offset); 229189606Ssam if (bp->bio_error) { 230189606Ssam bp->bio_flags |= BIO_ERROR; 231189606Ssam goto done; 232189606Ssam } 233189606Ssam } 234189606Ssam 235189606Ssam top = sc->sc_wrofs + sc->sc_wrbufsz; 236189606Ssam bcopy(bp->bio_data, 237189606Ssam sc->sc_wrbuf + bp->bio_offset - sc->sc_wrofs, 238189606Ssam MIN(top - bp->bio_offset, resid)); 239189606Ssam resid -= MIN(top - bp->bio_offset, resid); 240189606Ssam } 241189606Ssam bp->bio_resid = resid; 242189606Ssamdone: 243189606Ssam biodone(bp); 244189606Ssam} 245189606Ssam 246189606Ssamstatic void 247189606Ssamcfi_io_proc(void *arg, int pending) 248189606Ssam{ 249189606Ssam struct cfi_disk_softc *sc = arg; 250189606Ssam struct cfi_softc *cfi = sc->parent; 251189606Ssam struct bio *bp; 252189606Ssam 253189606Ssam for (;;) { 254189606Ssam mtx_lock(&sc->qlock); 255189606Ssam bp = bioq_takefirst(&sc->bioq); 256189606Ssam mtx_unlock(&sc->qlock); 257189606Ssam if (bp == NULL) 258189606Ssam break; 259189606Ssam 260189606Ssam switch (bp->bio_cmd) { 261189606Ssam case BIO_READ: 262189606Ssam cfi_disk_read(cfi, bp); 263189606Ssam break; 264189606Ssam case BIO_WRITE: 265189606Ssam cfi_disk_write(cfi, bp); 266189606Ssam break; 267189606Ssam } 268189606Ssam } 269189606Ssam} 270189606Ssam 271189606Ssamstatic void 272189606Ssamcfi_disk_strategy(struct bio *bp) 273189606Ssam{ 274189606Ssam struct cfi_disk_softc *sc = bp->bio_disk->d_drv1; 275189606Ssam 276189606Ssam if (sc == NULL) 277189606Ssam goto invalid; 278189606Ssam if (bp->bio_bcount == 0) { 279189606Ssam bp->bio_resid = bp->bio_bcount; 280189606Ssam biodone(bp); 281189606Ssam return; 282189606Ssam } 283189606Ssam switch (bp->bio_cmd) { 284189606Ssam case BIO_READ: 285189606Ssam case BIO_WRITE: 286189606Ssam mtx_lock(&sc->qlock); 287189606Ssam /* no value in sorting requests? */ 288189606Ssam bioq_insert_tail(&sc->bioq, bp); 289189606Ssam mtx_unlock(&sc->qlock); 290189606Ssam taskqueue_enqueue(sc->tq, &sc->iotask); 291189606Ssam return; 292189606Ssam } 293189606Ssam /* fall thru... */ 294189606Ssaminvalid: 295189606Ssam bp->bio_flags |= BIO_ERROR; 296189606Ssam bp->bio_error = EINVAL; 297189606Ssam biodone(bp); 298189606Ssam} 299189606Ssam 300189606Ssamstatic int 301189606Ssamcfi_disk_ioctl(struct disk *dp, u_long cmd, void *data, int fflag, 302189606Ssam struct thread *td) 303189606Ssam{ 304189606Ssam return EINVAL; 305189606Ssam} 306189606Ssam 307189606Ssamstatic device_method_t cfi_disk_methods[] = { 308189606Ssam DEVMETHOD(device_probe, cfi_disk_probe), 309189606Ssam DEVMETHOD(device_attach, cfi_disk_attach), 310189606Ssam DEVMETHOD(device_detach, cfi_disk_detach), 311189606Ssam 312189606Ssam { 0, 0 } 313189606Ssam}; 314189606Ssamstatic driver_t cfi_disk_driver = { 315189606Ssam "cfid", 316189606Ssam cfi_disk_methods, 317189606Ssam sizeof(struct cfi_disk_softc), 318189606Ssam}; 319189606SsamDRIVER_MODULE(cfid, cfi, cfi_disk_driver, cfi_diskclass, 0, NULL); 320