diff options
author | Uwe Stuehler <uwe@cvs.openbsd.org> | 2006-11-25 14:32:01 +0000 |
---|---|---|
committer | Uwe Stuehler <uwe@cvs.openbsd.org> | 2006-11-25 14:32:01 +0000 |
commit | 3d181ecda67c9ab9633a36011ba4b76b3d1f2a7c (patch) | |
tree | 466f5b6aa1c21712a8fb87c85506e12bf088e6bc /sys/dev | |
parent | 7f5337a570881bf9a7a29775b9ca02914568e566 (diff) |
Initial NAND flash support for Zaurus, not enabled yet; prodded by many.
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/flash.c | 1099 | ||||
-rw-r--r-- | sys/dev/flashvar.h | 143 |
2 files changed, 1242 insertions, 0 deletions
diff --git a/sys/dev/flash.c b/sys/dev/flash.c new file mode 100644 index 00000000000..0fa721463e2 --- /dev/null +++ b/sys/dev/flash.c @@ -0,0 +1,1099 @@ +/* $OpenBSD: flash.c,v 1.1 2006/11/25 14:32:00 uwe Exp $ */ + +/* + * Copyright (c) 2005 Uwe Stuehler <uwe@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/buf.h> +#include <sys/conf.h> +#include <sys/device.h> +#include <sys/disk.h> +#include <sys/disklabel.h> +#include <sys/dkio.h> +#include <sys/kernel.h> +#include <sys/stat.h> +#include <sys/systm.h> + +#include <dev/flashvar.h> + +#include <ufs/ffs/fs.h> /* XXX */ + +/* Samsung command set */ +#define SAMSUNG_CMD_PTRLO 0x00 +#define SAMSUNG_CMD_PTRHI 0x01 +#define SAMSUNG_CMD_PTROOB 0x50 +#define SAMSUNG_CMD_READ 0x30 +#define SAMSUNG_CMD_SEQIN 0x80 +#define SAMSUNG_CMD_WRITE 0x10 +#define SAMSUNG_CMD_ERASE0 0x60 +#define SAMSUNG_CMD_ERASE1 0xd0 +#define SAMSUNG_CMD_STATUS 0x70 +#define STATUS_FAIL (1<<0) +#define STATUS_READY (1<<6) +#define STATUS_NWP (1<<7) +#define SAMSUNG_CMD_READID 0x90 +#define SAMSUNG_CMD_RESET 0xff + +int flash_wait_ready(struct flash_softc *); +int flash_wait_complete(struct flash_softc *); + +/* XXX: these should go elsewhere */ +cdev_decl(flash); +bdev_decl(flash); + +#define flashlock(sc) disk_lock(&(sc)->sc_dk) +#define flashunlock(sc) disk_unlock(&(sc)->sc_dk) +#define flashlookup(unit) \ + (struct flash_softc *)device_lookup(&flash_cd, (unit)) + +void flashminphys(struct buf *); +void flashstart(struct flash_softc *); +void _flashstart(struct flash_softc *, struct buf *); +void flashdone(void *); + +int flashsafestrategy(struct flash_softc *, struct buf *); +void flashgetdefaultlabel(dev_t, struct flash_softc *, + struct disklabel *); +void flashgetdisklabel(dev_t, struct flash_softc *, struct disklabel *, + struct cpu_disklabel *, int); + +/* + * Driver attachment glue + */ + +struct flashvendor { + u_int8_t vendor; + const char *name; +}; + +static const struct flashvendor flashvendors[] = { + { FLASH_VENDOR_SAMSUNG, "Samsung" } +}; +#define FLASH_NVENDORS (sizeof(flashvendors) / sizeof(flashvendors[0])) + +static const struct flashdev flashdevs[] = { + { FLASH_DEVICE_SAMSUNG_K9F2808U0C, "K9F2808U0C 16Mx8 3.3V", + 512, 16, 32, 32768 }, + { FLASH_DEVICE_SAMSUNG_K9F1G08U0A, "K9F1G08U0A 128Mx8 3.3V", + 2048, 64, 64, 65536 }, +}; +#define FLASH_NDEVS (sizeof(flashdevs) / sizeof(flashdevs[0])) + +struct cfdriver flash_cd = { + NULL, "flash", DV_DISK +}; + +struct dkdriver flashdkdriver = { flashstrategy }; + +void +flashattach(struct flash_softc *sc, struct flash_ctl_tag *tag, + void *cookie) +{ + u_int8_t vendor, device; + u_int16_t id; + int i; + + sc->sc_tag = tag; + sc->sc_cookie = cookie; + + if (sc->sc_maxwaitready <= 0) + sc->sc_maxwaitready = 1000; /* 1ms */ + if (sc->sc_maxwaitcomplete <= 0) + sc->sc_maxwaitcomplete = 200000; /* 200ms */ + + flash_chip_enable(sc); + + /* Identify the flash device. */ + if (flash_chip_identify(sc, &vendor, &device) != 0) { + printf(": identification failed\n"); + flash_chip_disable(sc); + return; + } + id = (vendor << 8) | device; + + /* Look up device characteristics, abort if not recognized. */ + for (i = 0; i < FLASH_NVENDORS; i++) { + if (flashvendors[i].vendor == vendor) { + printf(": %s", flashvendors[i].name); + break; + } + } + if (i == FLASH_NVENDORS) + printf(": vendor 0x%02x", vendor); + for (i = 0; i < FLASH_NDEVS; i++) { + if (flashdevs[i].id == id) { + printf(" %s\n", flashdevs[i].longname); + break; + } + } + if (i == FLASH_NDEVS) { + /* Need to add this device to flashdevs first. */ + printf(" device 0x%02x\n", device); + flash_chip_disable(sc); + return; + } + sc->sc_flashdev = &flashdevs[i]; + + /* Check if the device really works or fail early. */ + if (flash_chip_reset(sc) != 0) { + printf("%s: reset failed\n", sc->sc_dev.dv_xname); + flash_chip_disable(sc); + return; + } + + flash_chip_disable(sc); + + /* + * Initialize and attach the disk structure. + */ + sc->sc_dk.dk_driver = &flashdkdriver; + sc->sc_dk.dk_name = sc->sc_dev.dv_xname; + disk_attach(&sc->sc_dk); + + /* XXX establish shutdown hook to finish any commands. */ +} + +int +flashdetach(struct device *self, int flags) +{ + struct flash_softc *sc = (struct flash_softc *)self; + + /* Detach disk. */ + disk_detach(&sc->sc_dk); + + /* XXX more resources need to be freed here. */ + return 0; +} + +int +flashactivate(struct device *self, enum devact act) +{ + /* XXX anything to be done here? */ + return 0; +} + +/* + * Flash controller and chip functions + */ + +u_int8_t +flash_reg8_read(struct flash_softc *sc, int reg) +{ + return sc->sc_tag->reg8_read(sc->sc_cookie, reg); +} + +void +flash_reg8_read_page(struct flash_softc *sc, caddr_t data, caddr_t oob) +{ + int i; + + for (i = 0; i < sc->sc_flashdev->pagesize; i++) + data[i] = flash_reg8_read(sc, FLASH_REG_DATA); + + if (oob != NULL) + for (i = 0; i < sc->sc_flashdev->oobsize; i++) + oob[i] = flash_reg8_read(sc, FLASH_REG_DATA); +} + +void +flash_reg8_write(struct flash_softc *sc, int reg, u_int8_t value) +{ + sc->sc_tag->reg8_write(sc->sc_cookie, reg, value); +} + +void +flash_reg8_write_page(struct flash_softc *sc, caddr_t data, caddr_t oob) +{ + int i; + + for (i = 0; i < sc->sc_flashdev->pagesize; i++) + flash_reg8_write(sc, FLASH_REG_DATA, data[i]); + + if (oob != NULL) + for (i = 0; i < sc->sc_flashdev->oobsize; i++) + flash_reg8_write(sc, FLASH_REG_DATA, oob[i]); +} + +/* + * Wait for the "Ready/Busy" signal to go high, indicating that the + * device is ready to accept another command. + */ +int +flash_wait_ready(struct flash_softc *sc) +{ + int timo = sc->sc_maxwaitready; + u_int8_t ready; + + ready = flash_reg8_read(sc, FLASH_REG_READY); + while (ready == 0 && timo-- > 0) { + delay(1); + ready = flash_reg8_read(sc, FLASH_REG_READY); + } + return (ready == 0 ? EIO : 0); +} + +/* + * Similar to flash_wait_ready() but looks at IO 6 and IO 0 signals + * besides R/B to decide whether the last operation was successful. + */ +int +flash_wait_complete(struct flash_softc *sc) +{ + int timo = sc->sc_maxwaitcomplete; + u_int8_t status; + + (void)flash_wait_ready(sc); + + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_STATUS); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + + status = flash_reg8_read(sc, FLASH_REG_DATA); + while ((status & STATUS_READY) == 0 && timo-- > 0) { + if (flash_reg8_read(sc, FLASH_REG_READY)) + break; + delay(1); + status = flash_reg8_read(sc, FLASH_REG_DATA); + } + + status = flash_reg8_read(sc, FLASH_REG_DATA); + return ((status & STATUS_FAIL) != 0 ? EIO : 0); +} + +void +flash_chip_enable(struct flash_softc *sc) +{ + /* XXX aquire the lock. */ + flash_reg8_write(sc, FLASH_REG_CE, 1); +} + +void +flash_chip_disable(struct flash_softc *sc) +{ + flash_reg8_write(sc, FLASH_REG_CE, 0); + /* XXX release the lock. */ +} + +int +flash_chip_reset(struct flash_softc *sc) +{ + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_RESET); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + + return flash_wait_ready(sc); +} + +int +flash_chip_identify(struct flash_softc *sc, u_int8_t *vendor, + u_int8_t *device) +{ + int error; + + (void)flash_wait_ready(sc); + + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_READID); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + + error = flash_wait_ready(sc); + if (error == 0) { + *vendor = flash_reg8_read(sc, FLASH_REG_DATA); + *device = flash_reg8_read(sc, FLASH_REG_DATA); + } + return error; +} + +int +flash_chip_erase_block(struct flash_softc *sc, long blkno) +{ + long pageno = blkno * sc->sc_flashdev->blkpages; + int error; + + (void)flash_wait_ready(sc); + + /* Disable write-protection. */ + flash_reg8_write(sc, FLASH_REG_WP, 0); + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_ERASE0); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_ROW, pageno); + flash_reg8_write(sc, FLASH_REG_ROW, pageno >> 8); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_ERASE1); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + error = flash_wait_complete(sc); + + /* Re-enable write-protection. */ + flash_reg8_write(sc, FLASH_REG_WP, 1); + + return error; +} + +int +flash_chip_read_block(struct flash_softc *sc, long blkno, caddr_t data) +{ + long pageno; + long blkend; + int error; + + pageno = blkno * sc->sc_flashdev->blkpages; + blkend = pageno + sc->sc_flashdev->blkpages; + + while (pageno < blkend) { + error = flash_chip_read_page(sc, pageno, data, NULL); + if (error != 0) + return error; + data += sc->sc_flashdev->pagesize; + pageno++; + } + return 0; +} + +int +flash_chip_read_page(struct flash_softc *sc, long pageno, caddr_t data, + caddr_t oob) +{ + int error; + + (void)flash_wait_ready(sc); + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_PTRLO); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_ROW, pageno); + flash_reg8_write(sc, FLASH_REG_ROW, pageno >> 8); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_READ); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + if ((error = flash_wait_ready(sc)) != 0) + return error; + + /* Support hardware ECC calculation. */ + if (sc->sc_tag->regx_read_page) { + error = sc->sc_tag->regx_read_page(sc->sc_cookie, data, + oob); + if (error != 0) + return error; + } else + flash_reg8_read_page(sc, data, oob); + + return 0; +} + +int +flash_chip_read_oob(struct flash_softc *sc, long pageno, caddr_t oob) +{ + u_int8_t *p = (u_int8_t *)oob; + int error; + int i; + + (void)flash_wait_ready(sc); + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_PTROOB); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_PTRLO); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_COL, 0x08); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_ROW, pageno); + flash_reg8_write(sc, FLASH_REG_ROW, pageno >> 8); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_READ); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + if ((error = flash_wait_ready(sc)) != 0) + return error; + + for (i = 0; i < sc->sc_flashdev->oobsize; i++) + p[i] = flash_reg8_read(sc, FLASH_REG_DATA); + + return 0; +} + +int +flash_chip_write_block(struct flash_softc *sc, long blkno, caddr_t data, + caddr_t oob) +{ + long pageno; + long blkend; + caddr_t p; + int error; + + pageno = blkno * sc->sc_flashdev->blkpages; + blkend = pageno + sc->sc_flashdev->blkpages; + + p = data; + while (pageno < blkend) { + error = flash_chip_write_page(sc, pageno, p, oob); + if (error != 0) + return error; + p += sc->sc_flashdev->pagesize; + pageno++; + } + + /* Verify the newly written block. */ + return flash_chip_verify_block(sc, blkno, data, oob); +} + +int +flash_chip_write_page(struct flash_softc *sc, long pageno, caddr_t data, + caddr_t oob) +{ + int error; + + (void)flash_wait_ready(sc); + + /* Disable write-protection. */ + flash_reg8_write(sc, FLASH_REG_WP, 0); + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_PTRLO); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_SEQIN); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_ROW, pageno); + flash_reg8_write(sc, FLASH_REG_ROW, pageno >> 8); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + /* Support hardware ECC calculation. */ + if (sc->sc_tag->regx_write_page) { + error = sc->sc_tag->regx_write_page(sc->sc_cookie, data, + oob); + if (error != 0) + return error; + } else + flash_reg8_write_page(sc, data, oob); + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_WRITE); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + /* + * Wait for the write operation to complete although this can + * take up to 700 us for the K9F1G08U0A flash type. + */ + error = flash_wait_complete(sc); + + /* Re-enable write-protection. */ + flash_reg8_write(sc, FLASH_REG_WP, 1); + + return error; +} + +int +flash_chip_verify_block(struct flash_softc *sc, long blkno, caddr_t data, + caddr_t oob) +{ + long pageno; + long blkend; + int error; + + pageno = blkno * sc->sc_flashdev->blkpages; + blkend = pageno + sc->sc_flashdev->blkpages; + + while (pageno < blkend) { + error = flash_chip_verify_page(sc, pageno, data, oob); + if (error != 0) { + printf("block %d page %d verify failed\n", + blkno, pageno); + return error; + } + data += sc->sc_flashdev->pagesize; + pageno++; + } + return 0; +} + +int +flash_chip_verify_page(struct flash_softc *sc, long pageno, caddr_t data, + caddr_t oob) +{ + static u_char rbuf[FLASH_MAXPAGESIZE]; + static u_char roob[FLASH_MAXOOBSIZE]; + int error; + + error = flash_chip_read_page(sc, pageno, rbuf, + oob == NULL ? NULL : roob); + if (error != 0) + return error; + + if (memcmp((const void *)&rbuf[0], (const void *)data, + sc->sc_flashdev->pagesize) != 0) + return EIO; + + if (oob != NULL && memcmp((const void *)&roob[0], + (const void *)oob, sc->sc_flashdev->oobsize) != 0) + return EIO; + + return 0; +} + +/* + * Block device functions + */ + +int +flashopen(dev_t dev, int oflags, int devtype, struct proc *p) +{ + struct flash_softc *sc; + int error; + int part; + + sc = flashlookup(flashunit(dev)); + if (sc == NULL) + return ENXIO; + + if ((error = flashlock(sc)) != 0) { + device_unref(&sc->sc_dev); + return error; + } + + /* + * If no partition is open load the partition info if it is + * not already valid. If partitions are already open, allow + * opens only for the same kind of device. + */ + if (sc->sc_dk.dk_openmask == 0) { + if ((sc->sc_flags & FDK_LOADED) == 0 || + ((sc->sc_flags & FDK_SAFE) == 0) != + (flashsafe(dev) == 0)) { + sc->sc_flags &= ~FDK_SAFE; + sc->sc_flags |= FDK_LOADED; + if (flashsafe(dev)) + sc->sc_flags |= FDK_SAFE; + flashgetdisklabel(dev, sc, sc->sc_dk.dk_label, + sc->sc_dk.dk_cpulabel, 0); + } + } else if (((sc->sc_flags & FDK_SAFE) == 0) != + (flashsafe(dev) == 0)) { + flashunlock(sc); + device_unref(&sc->sc_dev); + return EBUSY; + } + + /* Check that the partition exists. */ + part = flashpart(dev); + if (part != RAW_PART && + (part >= sc->sc_dk.dk_label->d_npartitions || + sc->sc_dk.dk_label->d_partitions[part].p_fstype == FS_UNUSED)) { + flashunlock(sc); + device_unref(&sc->sc_dev); + return ENXIO; + } + + /* Prevent our unit from being deconfigured while open. */ + switch (devtype) { + case S_IFCHR: + sc->sc_dk.dk_copenmask |= (1 << part); + break; + case S_IFBLK: + sc->sc_dk.dk_bopenmask |= (1 << part); + break; + } + sc->sc_dk.dk_openmask = + sc->sc_dk.dk_copenmask | sc->sc_dk.dk_bopenmask; + + flashunlock(sc); + device_unref(&sc->sc_dev); + return 0; +} + +int +flashclose(dev_t dev, int fflag, int devtype, struct proc *p) +{ + struct flash_softc *sc; + int error; + int part; + + sc = flashlookup(flashunit(dev)); + if (sc == NULL) + return ENXIO; + + if ((error = flashlock(sc)) != 0) { + device_unref(&sc->sc_dev); + return error; + } + + /* Close one open partition. */ + part = flashpart(dev); + switch (devtype) { + case S_IFCHR: + sc->sc_dk.dk_copenmask &= ~(1 << part); + break; + case S_IFBLK: + sc->sc_dk.dk_bopenmask &= ~(1 << part); + break; + } + sc->sc_dk.dk_openmask = + sc->sc_dk.dk_copenmask | sc->sc_dk.dk_bopenmask; + + if (sc->sc_dk.dk_openmask == 0) { + /* XXX wait for I/O to complete? */ + } + + flashunlock(sc); + device_unref(&sc->sc_dev); + return 0; +} + +/* + * Queue the transfer of one or more flash pages. + */ +void +flashstrategy(struct buf *bp) +{ + struct flash_softc *sc; + int s; + + sc = flashlookup(flashunit(bp->b_dev)); + if (sc == NULL) { + bp->b_error = ENXIO; + goto bad; + } + + /* Transfer only a multiple of the flash page size. */ + if ((bp->b_bcount % sc->sc_flashdev->pagesize) != 0) { + bp->b_error = EINVAL; + goto bad; + } + + /* If the device has been invalidated, error out. */ + if ((sc->sc_flags & FDK_LOADED) == 0) { + bp->b_error = EIO; + goto bad; + } + + /* Translate logical block numbers to physical. */ + if (flashsafe(bp->b_dev) && flashsafestrategy(sc, bp) <= 0) + goto done; + + /* Return immediately if it is a null transfer. */ + if (bp->b_bcount == 0) + goto done; + + /* Do bounds checking on partitions. */ + if (flashpart(bp->b_dev) != RAW_PART && + bounds_check_with_label(bp, sc->sc_dk.dk_label, + sc->sc_dk.dk_cpulabel, 0) <= 0) + goto done; + + /* Queue the transfer. */ + s = splbio(); + disksort(&sc->sc_q, bp); + flashstart(sc); + splx(s); + device_unref(&sc->sc_dev); + return; + +bad: + bp->b_flags |= B_ERROR; +done: + if ((bp->b_flags & B_ERROR) != 0) + bp->b_resid = bp->b_bcount; + s = splbio(); + biodone(bp); + splx(s); + if (sc != NULL) + device_unref(&sc->sc_dev); +} + +int +flashioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc *p) +{ + struct flash_softc *sc; + int error = 0; + + sc = flashlookup(flashunit(dev)); + if (sc == NULL) + return ENXIO; + + if ((sc->sc_flags & FDK_LOADED) == 0) { + device_unref(&sc->sc_dev); + return EIO; + } + + switch (cmd) { + case DIOCGDINFO: + *(struct disklabel *)data = *sc->sc_dk.dk_label; + break; + default: + error = ENOTTY; + break; + } + + device_unref(&sc->sc_dev); + return error; +} + +int +flashdump(dev_t dev, daddr_t blkno, caddr_t va, size_t size) +{ + printf("flashdump\n"); + return ENODEV; +} + +int +flashsize(dev_t dev) +{ + printf("flashsize\n"); + return ENODEV; +} + +void +flashstart(struct flash_softc *sc) +{ + struct buf *dp, *bp; + + while (1) { + /* Remove the next buffer from the queue or stop. */ + dp = &sc->sc_q; + bp = dp->b_actf; + if (bp == NULL) + return; + dp->b_actf = bp->b_actf; + + /* Transfer this buffer now. */ + _flashstart(sc, bp); + } +} + +void +_flashstart(struct flash_softc *sc, struct buf *bp) +{ + int part; + long offset; + long pgno; + + part = flashpart(bp->b_dev); + if (part != RAW_PART) + offset = sc->sc_dk.dk_label->d_partitions[part].p_offset; + else + offset = 0; + + offset = offset + bp->b_blkno; + pgno = offset / (sc->sc_flashdev->pagesize / DEV_BSIZE); + + /* + * If the requested page is exactly at the end of flash and it + * is an "unsafe" device, return EOF, else error out. + */ + if (!flashsafe(bp->b_dev) && pgno == sc->sc_flashdev->capacity) { + bp->b_resid = bp->b_bcount; + biodone(bp); + return; + } else if (pgno >= sc->sc_flashdev->capacity) { + bp->b_error = EINVAL; + bp->b_flags |= B_ERROR; + biodone(bp); + return; + } + + sc->sc_bp = bp; + + /* Instrumentation. */ + disk_busy(&sc->sc_dk); + + /* XXX this should be done asynchronously. */ + flash_chip_enable(sc); + if ((bp->b_flags & B_READ) != 0) + bp->b_error = flash_chip_read_page(sc, pgno, bp->b_data, + NULL); + else + bp->b_error = flash_chip_write_page(sc, pgno, bp->b_data, + NULL); + if (bp->b_error == 0) + bp->b_resid = bp->b_bcount - sc->sc_flashdev->pagesize; + flash_chip_disable(sc); + flashdone(sc); +} + +void +flashdone(void *v) +{ + struct flash_softc *sc = v; + struct buf *bp = sc->sc_bp; + + /* Instrumentation. */ + disk_unbusy(&sc->sc_dk, bp->b_bcount - bp->b_resid, + (bp->b_flags & B_READ) != 0); + + if (bp->b_error != 0) + bp->b_flags |= B_ERROR; + + biodone(bp); + flashstart(sc); +} + +void +flashgetdefaultlabel(dev_t dev, struct flash_softc *sc, + struct disklabel *lp) +{ + size_t len; + + bzero(lp, sizeof(struct disklabel)); + + lp->d_type = 0; + lp->d_subtype = 0; + strncpy(lp->d_typename, "NAND flash", sizeof(lp->d_typename)); + + /* Use the product name up to the first space. */ + strncpy(lp->d_packname, sc->sc_flashdev->longname, + sizeof(lp->d_packname)); + for (len = 0; len < sizeof(lp->d_packname); len++) + if (lp->d_packname[len] == ' ') { + lp->d_packname[len] = '\0'; + break; + } + + /* Fake the disk geometry. */ + lp->d_ncylinders = 1; + lp->d_ntracks = 16; + lp->d_secsize = sc->sc_flashdev->pagesize; + lp->d_nsectors = sc->sc_flashdev->capacity / lp->d_ntracks + / lp->d_ncylinders; + lp->d_secpercyl = lp->d_ntracks * lp->d_nsectors; + lp->d_secperunit = lp->d_ncylinders * lp->d_secpercyl; + + /* Fake hardware characteristics. */ + lp->d_rpm = 3600; + lp->d_interleave = 1; + + /* XXX these values assume ffs. */ + lp->d_bbsize = BBSIZE; + lp->d_sbsize = SBSIZE; + + /* Fake the partition information. */ + lp->d_npartitions = RAW_PART + 1; + lp->d_partitions[RAW_PART].p_size = lp->d_secperunit; + lp->d_partitions[RAW_PART].p_offset = 0; + lp->d_partitions[RAW_PART].p_fstype = FS_UNUSED; + + /* Wrap it up. */ + lp->d_magic = DISKMAGIC; + lp->d_magic2 = DISKMAGIC; + lp->d_checksum = dkcksum(lp); +} + +void +flashgetdisklabel(dev_t dev, struct flash_softc *sc, + struct disklabel *lp, struct cpu_disklabel *clp, int spoofonly) +{ + char *errstring; + dev_t labeldev; + + flashgetdefaultlabel(dev, sc, lp); + bzero(clp, sizeof(struct cpu_disklabel)); + + if (sc->sc_tag->default_disklabel != NULL) + sc->sc_tag->default_disklabel(sc->sc_cookie, dev, lp, clp); + + /* Call the generic disklabel extraction routine. */ + labeldev = flashlabeldev(dev); + errstring = readdisklabel(labeldev, flashstrategy, lp, clp, + spoofonly); + if (errstring != NULL) + printf("%s: %s\n", sc->sc_dev.dv_xname, errstring); +} + +/* + * Character device functions + */ + +void +flashminphys(struct buf *bp) +{ + struct flash_softc *sc; + + sc = flashlookup(flashunit(bp->b_dev)); + + if (bp->b_bcount > sc->sc_flashdev->pagesize) + bp->b_bcount = sc->sc_flashdev->pagesize; +} + +int +flashread(dev_t dev, struct uio *uio, int ioflag) +{ + return physio(flashstrategy, NULL, dev, B_READ, flashminphys, uio); +} + +int +flashwrite(dev_t dev, struct uio *uio, int ioflag) +{ + return physio(flashstrategy, NULL, dev, B_WRITE, flashminphys, uio); +} + +/* + * Physical access strategy "fixup" routines for transparent bad + * blocks management, wear-leveling, etc. + */ + +/* + * Call the machine-specific routine if there is any or use just a + * default strategy for bad blocks management. + */ +int +flashsafestrategy(struct flash_softc *sc, struct buf *bp) +{ + if (sc->sc_tag->safe_strategy) { + return sc->sc_tag->safe_strategy(sc->sc_cookie, bp); + } + + /* XXX no default bad blocks management strategy yet */ + return 1; +} + +void dumppage(u_char *); +void dumppage(u_char *buf) +{ + int i; + for (i = 0; i < 512; i++) { + if ((i % 16) == 0) + printf("%04x: ", i); + if ((i % 16) == 8) + printf(" "); + printf(" %02x", buf[i]); + if ((i % 16) == 15) + printf("\n"); + } + if ((i % 16) != 0) + printf("\n"); +} diff --git a/sys/dev/flashvar.h b/sys/dev/flashvar.h new file mode 100644 index 00000000000..dccf80d566d --- /dev/null +++ b/sys/dev/flashvar.h @@ -0,0 +1,143 @@ +/* $OpenBSD: flashvar.h,v 1.1 2006/11/25 14:32:00 uwe Exp $ */ + +/* + * Copyright (c) 2005 Uwe Stuehler <uwe@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _FLASHVAR_H_ +#define _FLASHVAR_H_ + +#ifdef _KERNEL + +/* Flash controller descriptor structure */ +struct flash_ctl_tag { + u_int8_t (*reg8_read)(void *, int); + int (*regx_read_page)(void *, caddr_t, caddr_t); + void (*reg8_write)(void *, int, u_int8_t); + int (*regx_write_page)(void *, caddr_t, caddr_t); + void (*default_disklabel)(void *, dev_t, struct disklabel *, + struct cpu_disklabel *); + int (*safe_strategy)(void *, struct buf *); +}; + +/* + * Pseudo-registers for a fictitious flash controller + * + * Note that logical levels are assumed for CE and WP bits. + * Signals corresponding to these bits are usually negated. + */ +#define FLASH_REG_DATA 0x00 +#define FLASH_REG_COL 0x01 +#define FLASH_REG_ROW 0x02 +#define FLASH_REG_CMD 0x03 +#define FLASH_REG_ALE 0x04 +#define FLASH_REG_CLE 0x05 +#define FLASH_REG_CE 0x06 +#define FLASH_REG_WP 0x07 +#define FLASH_REG_READY 0x0f + +/* Flash device descriptor structure */ +struct flashdev { + u_int16_t id; + const char *longname; + u_long pagesize; /* bytes per page */ + u_long oobsize; /* OOB bytes per page */ + u_long blkpages; /* pages per erasable block */ + u_long capacity; /* pages per device */ +}; + +#define FLASH_DEVICE(v,d) ((FLASH_VENDOR_##v << 8) | (d)) + +/* Flash device vendors */ +#define FLASH_VENDOR_SAMSUNG 0xec + +/* Flash devices */ +#define FLASH_DEVICE_SAMSUNG_K9F2808U0C FLASH_DEVICE(SAMSUNG, 0x73) +#define FLASH_DEVICE_SAMSUNG_K9F1G08U0A FLASH_DEVICE(SAMSUNG, 0xf1) + +/* Maximum sizes for all devices */ +#define FLASH_MAXPAGESIZE 2048 +#define FLASH_MAXOOBSIZE 64 + +/* + * Should-be private softc structure for the generic flash driver. + */ +struct flash_softc { + struct device sc_dev; + /* Disk device information */ + struct disk sc_dk; + struct buf sc_q; + struct buf *sc_bp; + int sc_flags; + /* Flash controller tag */ + struct flash_ctl_tag *sc_tag; + void *sc_cookie; + /* Flash device characteristics */ + const struct flashdev *sc_flashdev; + int sc_maxwaitready; + int sc_maxwaitcomplete; +}; + +/* Values for sc_flags */ +#define FDK_LOADED 0x00000001 +#define FDK_SAFE 0x00000002 + +/* + * Similar to vnd(4) devices there are two kinds of flash devices. + * Both device kinds share the same disklabel. + * + * ``Safe'' devices have bit 11 set in the minor number and use the + * out-of-band page data to implement wear-leveling and transparent + * management of bad block information. Block erasing and rewriting + * is also handled transparently; arbitrary pages can be modified. + * + * ``Unsafe'' devices provide raw access to the flash pages. Access + * to OOB page data is possible via ioctl()s only with these devices. + * Erasing the containing flash block may be necessary before a page + * can be writting successfully, but the block erase command is only + * provided as an ioctl(). + */ +#define flashsafe(x) (minor(x) & 0x800) +#define flashunit(x) DISKUNIT(makedev(major(x), minor(x) & 0x7ff)) +#define flashpart(x) DISKPART(makedev(major(x), minor(x) & 0x7ff)) +#define flashlabeldev(x) (MAKEDISKDEV(major(x), flashunit(x), RAW_PART)\ + |flashsafe(x)) + +void flashattach(struct flash_softc *, struct flash_ctl_tag *, void *); +int flashdetach(struct device *, int); +int flashactivate(struct device *, enum devact); + +u_int8_t flash_reg8_read(struct flash_softc *, int); +void flash_reg8_read_page(struct flash_softc *, caddr_t, caddr_t); +void flash_reg8_write(struct flash_softc *, int, u_int8_t); +void flash_reg8_write_page(struct flash_softc *, caddr_t, caddr_t); +void flash_chip_enable(struct flash_softc *); +void flash_chip_disable(struct flash_softc *); +int flash_chip_reset(struct flash_softc *); +int flash_chip_identify(struct flash_softc *, u_int8_t *, u_int8_t *); +int flash_chip_erase_block(struct flash_softc *, long); +int flash_chip_read_block(struct flash_softc *, long, caddr_t); +int flash_chip_read_page(struct flash_softc *, long, caddr_t, caddr_t); +int flash_chip_read_oob(struct flash_softc *, long, caddr_t); +int flash_chip_write_block(struct flash_softc *, long, caddr_t, caddr_t); +int flash_chip_write_page(struct flash_softc *, long, caddr_t, caddr_t); +int flash_chip_verify_block(struct flash_softc *, long, caddr_t, caddr_t); +int flash_chip_verify_page(struct flash_softc *, long, caddr_t, caddr_t); + +#endif /* _KERNEL */ + +/* XXX: define ioctl commands for OOB page data access and block erase. */ + +#endif /* _FLASHVAR_H_ */ |