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$"); 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 */ 102189654Ssam if (sc->parent->sc_regions) { 103189654Ssam /* 104189654Ssam * Multiple regions, use the last one. This is a 105189654Ssam * total hack as it's (presently) used only by 106189654Ssam * geom_redboot to locate the FIS directory which 107189654Ssam * lies at the start of the last erase region. 108189654Ssam */ 109189654Ssam sc->disk->d_stripesize = 110189654Ssam sc->parent->sc_region[sc->parent->sc_regions-1].r_blksz; 111189654Ssam } else 112189606Ssam sc->disk->d_stripesize = sc->disk->d_mediasize; 113189606Ssam sc->disk->d_drv1 = sc; 114189606Ssam disk_create(sc->disk, DISK_VERSION); 115189606Ssam 116189606Ssam mtx_init(&sc->qlock, "CFID I/O lock", NULL, MTX_DEF); 117189606Ssam bioq_init(&sc->bioq); 118189606Ssam 119189606Ssam sc->tq = taskqueue_create("cfid_taskq", M_NOWAIT, 120189606Ssam taskqueue_thread_enqueue, &sc->tq); 121189606Ssam taskqueue_start_threads(&sc->tq, 1, PI_DISK, "cfid taskq"); 122189606Ssam 123189606Ssam TASK_INIT(&sc->iotask, 0, cfi_io_proc, sc); 124189606Ssam 125189606Ssam return 0; 126189606Ssam} 127189606Ssam 128189606Ssamstatic int 129189606Ssamcfi_disk_detach(device_t dev) 130189606Ssam{ 131189606Ssam struct cfi_disk_softc *sc = device_get_softc(dev); 132189606Ssam 133189606Ssam if (sc->flags & CFI_DISK_OPEN) 134189606Ssam return EBUSY; 135189606Ssam taskqueue_free(sc->tq); 136189606Ssam /* XXX drain bioq */ 137189606Ssam disk_destroy(sc->disk); 138189606Ssam mtx_destroy(&sc->qlock); 139189606Ssam return 0; 140189606Ssam} 141189606Ssam 142189606Ssamstatic int 143189606Ssamcfi_disk_open(struct disk *dp) 144189606Ssam{ 145189606Ssam struct cfi_disk_softc *sc = dp->d_drv1; 146189606Ssam 147189606Ssam /* XXX no interlock with /dev/cfi */ 148189606Ssam sc->flags |= CFI_DISK_OPEN; 149189606Ssam return 0; 150189606Ssam} 151189606Ssam 152189606Ssamstatic int 153189606Ssamcfi_disk_close(struct disk *dp) 154189606Ssam{ 155189606Ssam struct cfi_disk_softc *sc = dp->d_drv1; 156189606Ssam 157189606Ssam sc->flags &= ~CFI_DISK_OPEN; 158189606Ssam return 0; 159189606Ssam} 160189606Ssam 161189606Ssamstatic void 162189606Ssamcfi_disk_read(struct cfi_softc *sc, struct bio *bp) 163189606Ssam{ 164189606Ssam long resid; 165189606Ssam 166189606Ssam KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4, 167189606Ssam ("sc_width %d", sc->sc_width)); 168189606Ssam 169189606Ssam if (sc->sc_writing) { 170189606Ssam bp->bio_error = cfi_block_finish(sc); 171189606Ssam if (bp->bio_error) { 172189606Ssam bp->bio_flags |= BIO_ERROR; 173189606Ssam goto done; 174189606Ssam } 175189606Ssam } 176189606Ssam if (bp->bio_offset > sc->sc_size) { 177189606Ssam bp->bio_flags |= BIO_ERROR; 178189606Ssam bp->bio_error = EIO; 179189606Ssam goto done; 180189606Ssam } 181189606Ssam resid = bp->bio_bcount; 182189606Ssam if (sc->sc_width == 1) { 183189606Ssam uint8_t *dp = (uint8_t *)bp->bio_data; 184189606Ssam while (resid > 0 && bp->bio_offset < sc->sc_size) { 185189606Ssam *dp++ = cfi_read(sc, bp->bio_offset); 186189606Ssam bp->bio_offset += 1, resid -= 1; 187189606Ssam } 188189606Ssam } else if (sc->sc_width == 2) { 189189606Ssam uint16_t *dp = (uint16_t *)bp->bio_data; 190189606Ssam while (resid > 0 && bp->bio_offset < sc->sc_size) { 191189606Ssam *dp++ = cfi_read(sc, bp->bio_offset); 192189606Ssam bp->bio_offset += 2, resid -= 2; 193189606Ssam } 194189606Ssam } else { 195189606Ssam uint32_t *dp = (uint32_t *)bp->bio_data; 196189606Ssam while (resid > 0 && bp->bio_offset < sc->sc_size) { 197189606Ssam *dp++ = cfi_read(sc, bp->bio_offset); 198189606Ssam bp->bio_offset += 4, resid -= 4; 199189606Ssam } 200189606Ssam } 201189606Ssam bp->bio_resid = resid; 202189606Ssamdone: 203189606Ssam biodone(bp); 204189606Ssam} 205189606Ssam 206189606Ssamstatic void 207189606Ssamcfi_disk_write(struct cfi_softc *sc, struct bio *bp) 208189606Ssam{ 209189606Ssam long resid; 210189606Ssam u_int top; 211189606Ssam 212189606Ssam KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4, 213189606Ssam ("sc_width %d", sc->sc_width)); 214189606Ssam 215189606Ssam if (bp->bio_offset > sc->sc_size) { 216189606Ssam bp->bio_flags |= BIO_ERROR; 217189606Ssam bp->bio_error = EIO; 218189606Ssam goto done; 219189606Ssam } 220189606Ssam resid = bp->bio_bcount; 221189606Ssam while (resid > 0) { 222189606Ssam /* 223189606Ssam * Finish the current block if we're about to write 224189606Ssam * to a different block. 225189606Ssam */ 226189606Ssam if (sc->sc_writing) { 227189606Ssam top = sc->sc_wrofs + sc->sc_wrbufsz; 228189606Ssam if (bp->bio_offset < sc->sc_wrofs || 229189606Ssam bp->bio_offset >= top) 230189606Ssam cfi_block_finish(sc); 231189606Ssam } 232189606Ssam 233189606Ssam /* Start writing to a (new) block if applicable. */ 234189606Ssam if (!sc->sc_writing) { 235189606Ssam bp->bio_error = cfi_block_start(sc, bp->bio_offset); 236189606Ssam if (bp->bio_error) { 237189606Ssam bp->bio_flags |= BIO_ERROR; 238189606Ssam goto done; 239189606Ssam } 240189606Ssam } 241189606Ssam 242189606Ssam top = sc->sc_wrofs + sc->sc_wrbufsz; 243189606Ssam bcopy(bp->bio_data, 244189606Ssam sc->sc_wrbuf + bp->bio_offset - sc->sc_wrofs, 245189606Ssam MIN(top - bp->bio_offset, resid)); 246189606Ssam resid -= MIN(top - bp->bio_offset, resid); 247189606Ssam } 248189606Ssam bp->bio_resid = resid; 249189606Ssamdone: 250189606Ssam biodone(bp); 251189606Ssam} 252189606Ssam 253189606Ssamstatic void 254189606Ssamcfi_io_proc(void *arg, int pending) 255189606Ssam{ 256189606Ssam struct cfi_disk_softc *sc = arg; 257189606Ssam struct cfi_softc *cfi = sc->parent; 258189606Ssam struct bio *bp; 259189606Ssam 260189606Ssam for (;;) { 261189606Ssam mtx_lock(&sc->qlock); 262189606Ssam bp = bioq_takefirst(&sc->bioq); 263189606Ssam mtx_unlock(&sc->qlock); 264189606Ssam if (bp == NULL) 265189606Ssam break; 266189606Ssam 267189606Ssam switch (bp->bio_cmd) { 268189606Ssam case BIO_READ: 269189606Ssam cfi_disk_read(cfi, bp); 270189606Ssam break; 271189606Ssam case BIO_WRITE: 272189606Ssam cfi_disk_write(cfi, bp); 273189606Ssam break; 274189606Ssam } 275189606Ssam } 276189606Ssam} 277189606Ssam 278189606Ssamstatic void 279189606Ssamcfi_disk_strategy(struct bio *bp) 280189606Ssam{ 281189606Ssam struct cfi_disk_softc *sc = bp->bio_disk->d_drv1; 282189606Ssam 283189606Ssam if (sc == NULL) 284189606Ssam goto invalid; 285189606Ssam if (bp->bio_bcount == 0) { 286189606Ssam bp->bio_resid = bp->bio_bcount; 287189606Ssam biodone(bp); 288189606Ssam return; 289189606Ssam } 290189606Ssam switch (bp->bio_cmd) { 291189606Ssam case BIO_READ: 292189606Ssam case BIO_WRITE: 293189606Ssam mtx_lock(&sc->qlock); 294189606Ssam /* no value in sorting requests? */ 295189606Ssam bioq_insert_tail(&sc->bioq, bp); 296189606Ssam mtx_unlock(&sc->qlock); 297189606Ssam taskqueue_enqueue(sc->tq, &sc->iotask); 298189606Ssam return; 299189606Ssam } 300189606Ssam /* fall thru... */ 301189606Ssaminvalid: 302189606Ssam bp->bio_flags |= BIO_ERROR; 303189606Ssam bp->bio_error = EINVAL; 304189606Ssam biodone(bp); 305189606Ssam} 306189606Ssam 307189606Ssamstatic int 308189606Ssamcfi_disk_ioctl(struct disk *dp, u_long cmd, void *data, int fflag, 309189606Ssam struct thread *td) 310189606Ssam{ 311189606Ssam return EINVAL; 312189606Ssam} 313189606Ssam 314189606Ssamstatic device_method_t cfi_disk_methods[] = { 315189606Ssam DEVMETHOD(device_probe, cfi_disk_probe), 316189606Ssam DEVMETHOD(device_attach, cfi_disk_attach), 317189606Ssam DEVMETHOD(device_detach, cfi_disk_detach), 318189606Ssam 319189606Ssam { 0, 0 } 320189606Ssam}; 321189606Ssamstatic driver_t cfi_disk_driver = { 322189606Ssam "cfid", 323189606Ssam cfi_disk_methods, 324189606Ssam sizeof(struct cfi_disk_softc), 325189606Ssam}; 326189606SsamDRIVER_MODULE(cfid, cfi, cfi_disk_driver, cfi_diskclass, 0, NULL); 327