diff options
author | sam <sam@FreeBSD.org> | 2009-03-09 23:16:02 +0000 |
---|---|---|
committer | sam <sam@FreeBSD.org> | 2009-03-09 23:16:02 +0000 |
commit | 010280a0bccdd4e4ef8ea41fb3309a88fa97acde (patch) | |
tree | d70fc54072f8a3553bf462db467a75c2015dc6b7 /sys/dev/cfi | |
parent | 2f15c795a97265832068c77e9c224e94c5fcd8e6 (diff) | |
download | FreeBSD-src-010280a0bccdd4e4ef8ea41fb3309a88fa97acde.zip FreeBSD-src-010280a0bccdd4e4ef8ea41fb3309a88fa97acde.tar.gz |
Add cfid, a disk interface to CFI flash devices; this enables construction
of flash-based filesystems.
Note this is not interlocked against the raw CFI device.
Diffstat (limited to 'sys/dev/cfi')
-rw-r--r-- | sys/dev/cfi/cfi_core.c | 5 | ||||
-rw-r--r-- | sys/dev/cfi/cfi_dev.c | 4 | ||||
-rw-r--r-- | sys/dev/cfi/cfi_disk.c | 319 | ||||
-rw-r--r-- | sys/dev/cfi/cfi_var.h | 3 |
4 files changed, 329 insertions, 2 deletions
diff --git a/sys/dev/cfi/cfi_core.c b/sys/dev/cfi/cfi_core.c index c7257c2..112ece2 100644 --- a/sys/dev/cfi/cfi_core.c +++ b/sys/dev/cfi/cfi_core.c @@ -51,6 +51,7 @@ extern struct cdevsw cfi_cdevsw; char cfi_driver_name[] = "cfi"; devclass_t cfi_devclass; +devclass_t cfi_diskclass; uint32_t cfi_read(struct cfi_softc *sc, u_int ofs) @@ -284,6 +285,10 @@ cfi_attach(device_t dev) "%s%u", cfi_driver_name, u); sc->sc_nod->si_drv1 = sc; + device_add_child(dev, "cfid", + devclass_find_free_unit(cfi_diskclass, 0)); + bus_generic_attach(dev); + return (0); } diff --git a/sys/dev/cfi/cfi_dev.c b/sys/dev/cfi/cfi_dev.c index 89069cb..d635753 100644 --- a/sys/dev/cfi/cfi_dev.c +++ b/sys/dev/cfi/cfi_dev.c @@ -74,7 +74,7 @@ struct cdevsw cfi_cdevsw = { * or the process stops writing. At that time we write the whole * sector to flash (see cfi_block_finish). */ -static int +int cfi_block_start(struct cfi_softc *sc, u_int ofs) { union { @@ -124,7 +124,7 @@ cfi_block_start(struct cfi_softc *sc, u_int ofs) * Finish updating the current block/sector by writing the compound * set of changes to the flash. */ -static int +int cfi_block_finish(struct cfi_softc *sc) { int error; diff --git a/sys/dev/cfi/cfi_disk.c b/sys/dev/cfi/cfi_disk.c new file mode 100644 index 0000000..eaa03fd --- /dev/null +++ b/sys/dev/cfi/cfi_disk.c @@ -0,0 +1,319 @@ +/*- + * Copyright (c) 2009 Sam Leffler, Errno Consulting + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bio.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/module.h> +#include <sys/rman.h> +#include <sys/sysctl.h> +#include <sys/taskqueue.h> + +#include <machine/bus.h> + +#include <dev/cfi/cfi_var.h> + +#include <geom/geom_disk.h> + +struct cfi_disk_softc { + struct cfi_softc *parent; + struct disk *disk; + int flags; +#define CFI_DISK_OPEN 0x0001 + struct bio_queue_head bioq; /* bio queue */ + struct mtx qlock; /* bioq lock */ + struct taskqueue *tq; /* private task queue for i/o request */ + struct task iotask; /* i/o processing */ +}; + +#define CFI_DISK_SECSIZE 512 +#define CFI_DISK_MAXIOSIZE 65536 + +static int cfi_disk_detach(device_t); +static int cfi_disk_open(struct disk *); +static int cfi_disk_close(struct disk *); +static void cfi_io_proc(void *, int); +static void cfi_disk_strategy(struct bio *); +static int cfi_disk_ioctl(struct disk *, u_long, void *, int, struct thread *); + +static int +cfi_disk_probe(device_t dev) +{ + return 0; +} + +static int +cfi_disk_attach(device_t dev) +{ + struct cfi_disk_softc *sc = device_get_softc(dev); + + sc->parent = device_get_softc(device_get_parent(dev)); + /* validate interface width; assumed by other code */ + if (sc->parent->sc_width != 1 && + sc->parent->sc_width != 2 && + sc->parent->sc_width != 4) + return EINVAL; + + sc->disk = disk_alloc(); + if (sc->disk == NULL) + return ENOMEM; + sc->disk->d_name = "cfid"; + sc->disk->d_unit = device_get_unit(dev); + sc->disk->d_open = cfi_disk_open; + sc->disk->d_close = cfi_disk_close; + sc->disk->d_strategy = cfi_disk_strategy; + sc->disk->d_ioctl = cfi_disk_ioctl; + sc->disk->d_dump = NULL; /* NB: no dumps */ + sc->disk->d_sectorsize = CFI_DISK_SECSIZE; + sc->disk->d_mediasize = sc->parent->sc_size; + sc->disk->d_maxsize = CFI_DISK_MAXIOSIZE; + /* NB: use stripesize to hold the erase/region size */ + if (sc->parent->sc_regions) + sc->disk->d_stripesize = sc->parent->sc_region->r_blksz; + else + sc->disk->d_stripesize = sc->disk->d_mediasize; + sc->disk->d_drv1 = sc; + disk_create(sc->disk, DISK_VERSION); + + mtx_init(&sc->qlock, "CFID I/O lock", NULL, MTX_DEF); + bioq_init(&sc->bioq); + + sc->tq = taskqueue_create("cfid_taskq", M_NOWAIT, + taskqueue_thread_enqueue, &sc->tq); + taskqueue_start_threads(&sc->tq, 1, PI_DISK, "cfid taskq"); + + TASK_INIT(&sc->iotask, 0, cfi_io_proc, sc); + + return 0; +} + +static int +cfi_disk_detach(device_t dev) +{ + struct cfi_disk_softc *sc = device_get_softc(dev); + + if (sc->flags & CFI_DISK_OPEN) + return EBUSY; + taskqueue_free(sc->tq); + /* XXX drain bioq */ + disk_destroy(sc->disk); + mtx_destroy(&sc->qlock); + return 0; +} + +static int +cfi_disk_open(struct disk *dp) +{ + struct cfi_disk_softc *sc = dp->d_drv1; + + /* XXX no interlock with /dev/cfi */ + sc->flags |= CFI_DISK_OPEN; + return 0; +} + +static int +cfi_disk_close(struct disk *dp) +{ + struct cfi_disk_softc *sc = dp->d_drv1; + + sc->flags &= ~CFI_DISK_OPEN; + return 0; +} + +static void +cfi_disk_read(struct cfi_softc *sc, struct bio *bp) +{ + long resid; + + KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4, + ("sc_width %d", sc->sc_width)); + + if (sc->sc_writing) { + bp->bio_error = cfi_block_finish(sc); + if (bp->bio_error) { + bp->bio_flags |= BIO_ERROR; + goto done; + } + } + if (bp->bio_offset > sc->sc_size) { + bp->bio_flags |= BIO_ERROR; + bp->bio_error = EIO; + goto done; + } + resid = bp->bio_bcount; + if (sc->sc_width == 1) { + uint8_t *dp = (uint8_t *)bp->bio_data; + while (resid > 0 && bp->bio_offset < sc->sc_size) { + *dp++ = cfi_read(sc, bp->bio_offset); + bp->bio_offset += 1, resid -= 1; + } + } else if (sc->sc_width == 2) { + uint16_t *dp = (uint16_t *)bp->bio_data; + while (resid > 0 && bp->bio_offset < sc->sc_size) { + *dp++ = cfi_read(sc, bp->bio_offset); + bp->bio_offset += 2, resid -= 2; + } + } else { + uint32_t *dp = (uint32_t *)bp->bio_data; + while (resid > 0 && bp->bio_offset < sc->sc_size) { + *dp++ = cfi_read(sc, bp->bio_offset); + bp->bio_offset += 4, resid -= 4; + } + } + bp->bio_resid = resid; +done: + biodone(bp); +} + +static void +cfi_disk_write(struct cfi_softc *sc, struct bio *bp) +{ + long resid; + u_int top; + + KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4, + ("sc_width %d", sc->sc_width)); + + if (bp->bio_offset > sc->sc_size) { + bp->bio_flags |= BIO_ERROR; + bp->bio_error = EIO; + goto done; + } + resid = bp->bio_bcount; + while (resid > 0) { + /* + * Finish the current block if we're about to write + * to a different block. + */ + if (sc->sc_writing) { + top = sc->sc_wrofs + sc->sc_wrbufsz; + if (bp->bio_offset < sc->sc_wrofs || + bp->bio_offset >= top) + cfi_block_finish(sc); + } + + /* Start writing to a (new) block if applicable. */ + if (!sc->sc_writing) { + bp->bio_error = cfi_block_start(sc, bp->bio_offset); + if (bp->bio_error) { + bp->bio_flags |= BIO_ERROR; + goto done; + } + } + + top = sc->sc_wrofs + sc->sc_wrbufsz; + bcopy(bp->bio_data, + sc->sc_wrbuf + bp->bio_offset - sc->sc_wrofs, + MIN(top - bp->bio_offset, resid)); + resid -= MIN(top - bp->bio_offset, resid); + } + bp->bio_resid = resid; +done: + biodone(bp); +} + +static void +cfi_io_proc(void *arg, int pending) +{ + struct cfi_disk_softc *sc = arg; + struct cfi_softc *cfi = sc->parent; + struct bio *bp; + + for (;;) { + mtx_lock(&sc->qlock); + bp = bioq_takefirst(&sc->bioq); + mtx_unlock(&sc->qlock); + if (bp == NULL) + break; + + switch (bp->bio_cmd) { + case BIO_READ: + cfi_disk_read(cfi, bp); + break; + case BIO_WRITE: + cfi_disk_write(cfi, bp); + break; + } + } +} + +static void +cfi_disk_strategy(struct bio *bp) +{ + struct cfi_disk_softc *sc = bp->bio_disk->d_drv1; + + if (sc == NULL) + goto invalid; + if (bp->bio_bcount == 0) { + bp->bio_resid = bp->bio_bcount; + biodone(bp); + return; + } + switch (bp->bio_cmd) { + case BIO_READ: + case BIO_WRITE: + mtx_lock(&sc->qlock); + /* no value in sorting requests? */ + bioq_insert_tail(&sc->bioq, bp); + mtx_unlock(&sc->qlock); + taskqueue_enqueue(sc->tq, &sc->iotask); + return; + } + /* fall thru... */ +invalid: + bp->bio_flags |= BIO_ERROR; + bp->bio_error = EINVAL; + biodone(bp); +} + +static int +cfi_disk_ioctl(struct disk *dp, u_long cmd, void *data, int fflag, + struct thread *td) +{ + return EINVAL; +} + +static device_method_t cfi_disk_methods[] = { + DEVMETHOD(device_probe, cfi_disk_probe), + DEVMETHOD(device_attach, cfi_disk_attach), + DEVMETHOD(device_detach, cfi_disk_detach), + + { 0, 0 } +}; +static driver_t cfi_disk_driver = { + "cfid", + cfi_disk_methods, + sizeof(struct cfi_disk_softc), +}; +DRIVER_MODULE(cfid, cfi, cfi_disk_driver, cfi_diskclass, 0, NULL); diff --git a/sys/dev/cfi/cfi_var.h b/sys/dev/cfi/cfi_var.h index d2a778b..4dd9a60 100644 --- a/sys/dev/cfi/cfi_var.h +++ b/sys/dev/cfi/cfi_var.h @@ -65,6 +65,7 @@ struct cfi_softc { extern char cfi_driver_name[]; extern devclass_t cfi_devclass; +extern devclass_t cfi_diskclass; int cfi_probe(device_t); int cfi_attach(device_t); @@ -73,6 +74,8 @@ int cfi_detach(device_t); uint32_t cfi_read(struct cfi_softc *, u_int); uint8_t cfi_read_qry(struct cfi_softc *, u_int); int cfi_write_block(struct cfi_softc *); +int cfi_block_start(struct cfi_softc *, u_int); +int cfi_block_finish(struct cfi_softc *); #ifdef CFI_SUPPORT_STRATAFLASH int cfi_intel_get_factory_pr(struct cfi_softc *sc, uint64_t *); |