1189606Ssam/*- 2189606Ssam * Copyright (c) 2009 Sam Leffler, Errno Consulting 3255207Sbrooks * Copyright (c) 2012-2013, SRI International 4189606Ssam * All rights reserved. 5189606Ssam * 6255207Sbrooks * Portions of this software were developed by SRI International and the 7255207Sbrooks * University of Cambridge Computer Laboratory under DARPA/AFRL contract 8255207Sbrooks * (FA8750-10-C-0237) ("CTSRD"), as part of the DARPA CRASH research 9255207Sbrooks * programme. 10255207Sbrooks * 11189606Ssam * Redistribution and use in source and binary forms, with or without 12189606Ssam * modification, are permitted provided that the following conditions 13189606Ssam * are met: 14189606Ssam * 1. Redistributions of source code must retain the above copyright 15189606Ssam * notice, this list of conditions and the following disclaimer. 16189606Ssam * 2. Redistributions in binary form must reproduce the above copyright 17189606Ssam * notice, this list of conditions and the following disclaimer in the 18189606Ssam * documentation and/or other materials provided with the distribution. 19189606Ssam * 20189606Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 21189606Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22189606Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 23189606Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 24189606Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 25189606Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26189606Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27189606Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28189606Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 29189606Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30189606Ssam */ 31189606Ssam 32189606Ssam#include <sys/cdefs.h> 33189606Ssam__FBSDID("$FreeBSD$"); 34189606Ssam 35189606Ssam#include <sys/param.h> 36189606Ssam#include <sys/systm.h> 37189606Ssam#include <sys/bio.h> 38189606Ssam#include <sys/bus.h> 39189606Ssam#include <sys/conf.h> 40189606Ssam#include <sys/kernel.h> 41189606Ssam#include <sys/malloc.h> 42189606Ssam#include <sys/lock.h> 43189606Ssam#include <sys/mutex.h> 44189606Ssam#include <sys/module.h> 45189606Ssam#include <sys/rman.h> 46189606Ssam#include <sys/sysctl.h> 47189606Ssam#include <sys/taskqueue.h> 48189606Ssam 49189606Ssam#include <machine/bus.h> 50189606Ssam 51189606Ssam#include <dev/cfi/cfi_var.h> 52189606Ssam 53251117Sbrooks#include <geom/geom.h> 54189606Ssam#include <geom/geom_disk.h> 55189606Ssam 56189606Ssamstruct cfi_disk_softc { 57189606Ssam struct cfi_softc *parent; 58189606Ssam struct disk *disk; 59189606Ssam int flags; 60189606Ssam#define CFI_DISK_OPEN 0x0001 61189606Ssam struct bio_queue_head bioq; /* bio queue */ 62189606Ssam struct mtx qlock; /* bioq lock */ 63189606Ssam struct taskqueue *tq; /* private task queue for i/o request */ 64189606Ssam struct task iotask; /* i/o processing */ 65189606Ssam}; 66189606Ssam 67189606Ssam#define CFI_DISK_SECSIZE 512 68189606Ssam#define CFI_DISK_MAXIOSIZE 65536 69189606Ssam 70189606Ssamstatic int cfi_disk_detach(device_t); 71189606Ssamstatic int cfi_disk_open(struct disk *); 72189606Ssamstatic int cfi_disk_close(struct disk *); 73189606Ssamstatic void cfi_io_proc(void *, int); 74251117Sbrooksstatic int cfi_disk_getattr(struct bio *); 75189606Ssamstatic void cfi_disk_strategy(struct bio *); 76189606Ssamstatic int cfi_disk_ioctl(struct disk *, u_long, void *, int, struct thread *); 77189606Ssam 78189606Ssamstatic int 79189606Ssamcfi_disk_probe(device_t dev) 80189606Ssam{ 81189606Ssam return 0; 82189606Ssam} 83189606Ssam 84189606Ssamstatic int 85189606Ssamcfi_disk_attach(device_t dev) 86189606Ssam{ 87189606Ssam struct cfi_disk_softc *sc = device_get_softc(dev); 88189606Ssam 89189606Ssam sc->parent = device_get_softc(device_get_parent(dev)); 90189606Ssam /* validate interface width; assumed by other code */ 91189606Ssam if (sc->parent->sc_width != 1 && 92189606Ssam sc->parent->sc_width != 2 && 93189606Ssam sc->parent->sc_width != 4) 94189606Ssam return EINVAL; 95189606Ssam 96189606Ssam sc->disk = disk_alloc(); 97189606Ssam if (sc->disk == NULL) 98189606Ssam return ENOMEM; 99189606Ssam sc->disk->d_name = "cfid"; 100189606Ssam sc->disk->d_unit = device_get_unit(dev); 101189606Ssam sc->disk->d_open = cfi_disk_open; 102189606Ssam sc->disk->d_close = cfi_disk_close; 103189606Ssam sc->disk->d_strategy = cfi_disk_strategy; 104189606Ssam sc->disk->d_ioctl = cfi_disk_ioctl; 105189606Ssam sc->disk->d_dump = NULL; /* NB: no dumps */ 106251117Sbrooks sc->disk->d_getattr = cfi_disk_getattr; 107189606Ssam sc->disk->d_sectorsize = CFI_DISK_SECSIZE; 108189606Ssam sc->disk->d_mediasize = sc->parent->sc_size; 109189606Ssam sc->disk->d_maxsize = CFI_DISK_MAXIOSIZE; 110189606Ssam /* NB: use stripesize to hold the erase/region size */ 111189654Ssam if (sc->parent->sc_regions) { 112189654Ssam /* 113189654Ssam * Multiple regions, use the last one. This is a 114189654Ssam * total hack as it's (presently) used only by 115189654Ssam * geom_redboot to locate the FIS directory which 116189654Ssam * lies at the start of the last erase region. 117189654Ssam */ 118189654Ssam sc->disk->d_stripesize = 119189654Ssam sc->parent->sc_region[sc->parent->sc_regions-1].r_blksz; 120189654Ssam } else 121189606Ssam sc->disk->d_stripesize = sc->disk->d_mediasize; 122189606Ssam sc->disk->d_drv1 = sc; 123189606Ssam disk_create(sc->disk, DISK_VERSION); 124189606Ssam 125189606Ssam mtx_init(&sc->qlock, "CFID I/O lock", NULL, MTX_DEF); 126189606Ssam bioq_init(&sc->bioq); 127189606Ssam 128189606Ssam sc->tq = taskqueue_create("cfid_taskq", M_NOWAIT, 129189606Ssam taskqueue_thread_enqueue, &sc->tq); 130189606Ssam taskqueue_start_threads(&sc->tq, 1, PI_DISK, "cfid taskq"); 131189606Ssam 132189606Ssam TASK_INIT(&sc->iotask, 0, cfi_io_proc, sc); 133189606Ssam 134189606Ssam return 0; 135189606Ssam} 136189606Ssam 137189606Ssamstatic int 138189606Ssamcfi_disk_detach(device_t dev) 139189606Ssam{ 140189606Ssam struct cfi_disk_softc *sc = device_get_softc(dev); 141189606Ssam 142189606Ssam if (sc->flags & CFI_DISK_OPEN) 143189606Ssam return EBUSY; 144189606Ssam taskqueue_free(sc->tq); 145189606Ssam /* XXX drain bioq */ 146189606Ssam disk_destroy(sc->disk); 147189606Ssam mtx_destroy(&sc->qlock); 148189606Ssam return 0; 149189606Ssam} 150189606Ssam 151189606Ssamstatic int 152189606Ssamcfi_disk_open(struct disk *dp) 153189606Ssam{ 154189606Ssam struct cfi_disk_softc *sc = dp->d_drv1; 155189606Ssam 156189606Ssam /* XXX no interlock with /dev/cfi */ 157189606Ssam sc->flags |= CFI_DISK_OPEN; 158189606Ssam return 0; 159189606Ssam} 160189606Ssam 161189606Ssamstatic int 162189606Ssamcfi_disk_close(struct disk *dp) 163189606Ssam{ 164189606Ssam struct cfi_disk_softc *sc = dp->d_drv1; 165189606Ssam 166189606Ssam sc->flags &= ~CFI_DISK_OPEN; 167189606Ssam return 0; 168189606Ssam} 169189606Ssam 170189606Ssamstatic void 171189606Ssamcfi_disk_read(struct cfi_softc *sc, struct bio *bp) 172189606Ssam{ 173189606Ssam long resid; 174189606Ssam 175189606Ssam KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4, 176189606Ssam ("sc_width %d", sc->sc_width)); 177189606Ssam 178189606Ssam if (sc->sc_writing) { 179189606Ssam bp->bio_error = cfi_block_finish(sc); 180189606Ssam if (bp->bio_error) { 181189606Ssam bp->bio_flags |= BIO_ERROR; 182189606Ssam goto done; 183189606Ssam } 184189606Ssam } 185189606Ssam if (bp->bio_offset > sc->sc_size) { 186189606Ssam bp->bio_flags |= BIO_ERROR; 187189606Ssam bp->bio_error = EIO; 188189606Ssam goto done; 189189606Ssam } 190189606Ssam resid = bp->bio_bcount; 191189606Ssam if (sc->sc_width == 1) { 192189606Ssam uint8_t *dp = (uint8_t *)bp->bio_data; 193189606Ssam while (resid > 0 && bp->bio_offset < sc->sc_size) { 194233553Sjchandra *dp++ = cfi_read_raw(sc, bp->bio_offset); 195189606Ssam bp->bio_offset += 1, resid -= 1; 196189606Ssam } 197189606Ssam } else if (sc->sc_width == 2) { 198189606Ssam uint16_t *dp = (uint16_t *)bp->bio_data; 199189606Ssam while (resid > 0 && bp->bio_offset < sc->sc_size) { 200233553Sjchandra *dp++ = cfi_read_raw(sc, bp->bio_offset); 201189606Ssam bp->bio_offset += 2, resid -= 2; 202189606Ssam } 203189606Ssam } else { 204189606Ssam uint32_t *dp = (uint32_t *)bp->bio_data; 205189606Ssam while (resid > 0 && bp->bio_offset < sc->sc_size) { 206233553Sjchandra *dp++ = cfi_read_raw(sc, bp->bio_offset); 207189606Ssam bp->bio_offset += 4, resid -= 4; 208189606Ssam } 209189606Ssam } 210189606Ssam bp->bio_resid = resid; 211189606Ssamdone: 212189606Ssam biodone(bp); 213189606Ssam} 214189606Ssam 215189606Ssamstatic void 216189606Ssamcfi_disk_write(struct cfi_softc *sc, struct bio *bp) 217189606Ssam{ 218189606Ssam long resid; 219189606Ssam u_int top; 220189606Ssam 221189606Ssam KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4, 222189606Ssam ("sc_width %d", sc->sc_width)); 223189606Ssam 224189606Ssam if (bp->bio_offset > sc->sc_size) { 225189606Ssam bp->bio_flags |= BIO_ERROR; 226189606Ssam bp->bio_error = EIO; 227189606Ssam goto done; 228189606Ssam } 229189606Ssam resid = bp->bio_bcount; 230189606Ssam while (resid > 0) { 231189606Ssam /* 232189606Ssam * Finish the current block if we're about to write 233189606Ssam * to a different block. 234189606Ssam */ 235189606Ssam if (sc->sc_writing) { 236189606Ssam top = sc->sc_wrofs + sc->sc_wrbufsz; 237189606Ssam if (bp->bio_offset < sc->sc_wrofs || 238189606Ssam bp->bio_offset >= top) 239189606Ssam cfi_block_finish(sc); 240189606Ssam } 241189606Ssam 242189606Ssam /* Start writing to a (new) block if applicable. */ 243189606Ssam if (!sc->sc_writing) { 244189606Ssam bp->bio_error = cfi_block_start(sc, bp->bio_offset); 245189606Ssam if (bp->bio_error) { 246189606Ssam bp->bio_flags |= BIO_ERROR; 247189606Ssam goto done; 248189606Ssam } 249189606Ssam } 250189606Ssam 251189606Ssam top = sc->sc_wrofs + sc->sc_wrbufsz; 252189606Ssam bcopy(bp->bio_data, 253189606Ssam sc->sc_wrbuf + bp->bio_offset - sc->sc_wrofs, 254189606Ssam MIN(top - bp->bio_offset, resid)); 255189606Ssam resid -= MIN(top - bp->bio_offset, resid); 256189606Ssam } 257189606Ssam bp->bio_resid = resid; 258189606Ssamdone: 259189606Ssam biodone(bp); 260189606Ssam} 261189606Ssam 262189606Ssamstatic void 263189606Ssamcfi_io_proc(void *arg, int pending) 264189606Ssam{ 265189606Ssam struct cfi_disk_softc *sc = arg; 266189606Ssam struct cfi_softc *cfi = sc->parent; 267189606Ssam struct bio *bp; 268189606Ssam 269189606Ssam for (;;) { 270189606Ssam mtx_lock(&sc->qlock); 271189606Ssam bp = bioq_takefirst(&sc->bioq); 272189606Ssam mtx_unlock(&sc->qlock); 273189606Ssam if (bp == NULL) 274189606Ssam break; 275189606Ssam 276189606Ssam switch (bp->bio_cmd) { 277189606Ssam case BIO_READ: 278189606Ssam cfi_disk_read(cfi, bp); 279189606Ssam break; 280189606Ssam case BIO_WRITE: 281189606Ssam cfi_disk_write(cfi, bp); 282189606Ssam break; 283189606Ssam } 284189606Ssam } 285189606Ssam} 286189606Ssam 287251117Sbrooksstatic int 288251117Sbrookscfi_disk_getattr(struct bio *bp) 289251117Sbrooks{ 290251117Sbrooks struct cfi_disk_softc *dsc; 291251117Sbrooks struct cfi_softc *sc; 292251117Sbrooks device_t dev; 293251117Sbrooks 294251117Sbrooks if (bp->bio_disk == NULL || bp->bio_disk->d_drv1 == NULL) 295251117Sbrooks return (ENXIO); 296251117Sbrooks 297251117Sbrooks dsc = bp->bio_disk->d_drv1; 298251117Sbrooks sc = dsc->parent; 299251117Sbrooks dev = sc->sc_dev; 300251117Sbrooks 301251651Smav if (strcmp(bp->bio_attribute, "CFI::device") == 0) { 302251651Smav if (bp->bio_length != sizeof(dev)) 303251651Smav return (EFAULT); 304251651Smav bcopy(&dev, bp->bio_data, sizeof(dev)); 305251651Smav } else 306251651Smav return (-1); 307251651Smav return (0); 308251117Sbrooks} 309251117Sbrooks 310251117Sbrooks 311189606Ssamstatic void 312189606Ssamcfi_disk_strategy(struct bio *bp) 313189606Ssam{ 314189606Ssam struct cfi_disk_softc *sc = bp->bio_disk->d_drv1; 315189606Ssam 316189606Ssam if (sc == NULL) 317189606Ssam goto invalid; 318189606Ssam if (bp->bio_bcount == 0) { 319189606Ssam bp->bio_resid = bp->bio_bcount; 320189606Ssam biodone(bp); 321189606Ssam return; 322189606Ssam } 323189606Ssam switch (bp->bio_cmd) { 324189606Ssam case BIO_READ: 325189606Ssam case BIO_WRITE: 326189606Ssam mtx_lock(&sc->qlock); 327189606Ssam /* no value in sorting requests? */ 328189606Ssam bioq_insert_tail(&sc->bioq, bp); 329189606Ssam mtx_unlock(&sc->qlock); 330189606Ssam taskqueue_enqueue(sc->tq, &sc->iotask); 331189606Ssam return; 332189606Ssam } 333189606Ssam /* fall thru... */ 334189606Ssaminvalid: 335189606Ssam bp->bio_flags |= BIO_ERROR; 336189606Ssam bp->bio_error = EINVAL; 337189606Ssam biodone(bp); 338189606Ssam} 339189606Ssam 340189606Ssamstatic int 341189606Ssamcfi_disk_ioctl(struct disk *dp, u_long cmd, void *data, int fflag, 342189606Ssam struct thread *td) 343189606Ssam{ 344189606Ssam return EINVAL; 345189606Ssam} 346189606Ssam 347189606Ssamstatic device_method_t cfi_disk_methods[] = { 348189606Ssam DEVMETHOD(device_probe, cfi_disk_probe), 349189606Ssam DEVMETHOD(device_attach, cfi_disk_attach), 350189606Ssam DEVMETHOD(device_detach, cfi_disk_detach), 351189606Ssam 352189606Ssam { 0, 0 } 353189606Ssam}; 354189606Ssamstatic driver_t cfi_disk_driver = { 355189606Ssam "cfid", 356189606Ssam cfi_disk_methods, 357189606Ssam sizeof(struct cfi_disk_softc), 358189606Ssam}; 359189606SsamDRIVER_MODULE(cfid, cfi, cfi_disk_driver, cfi_diskclass, 0, NULL); 360