diff options
| author | ahoka <ahoka@NetBSD.org> | 2011-02-26 18:07:13 +0000 |
|---|---|---|
| committer | ahoka <ahoka@NetBSD.org> | 2011-02-26 18:07:13 +0000 |
| commit | b84aee73028fe564bfa78c0c2c568359ca7288a8 (patch) | |
| tree | fe512736abd91f2864d5b0021de9e4af277634cd /sys/dev | |
| parent | 11ce3c28dd5240489a3d69366d65cc854b754c35 (diff) | |
Import the Flash and NAND subsytem code contributed by the University
of Szeged, Hungary.
The commit includes:
- Flash layer, which gives a common API to access flash devices
- NAND controller subsystem for the flash layer
- An example OMAP driver which is used on BeagleBoard or alike ARM boards
Diffstat (limited to 'sys/dev')
| -rw-r--r-- | sys/dev/flash/files.flash | 9 | ||||
| -rw-r--r-- | sys/dev/flash/flash.c | 695 | ||||
| -rw-r--r-- | sys/dev/flash/flash.h | 168 | ||||
| -rw-r--r-- | sys/dev/nand/files.nand | 16 | ||||
| -rw-r--r-- | sys/dev/nand/hamming.c | 232 | ||||
| -rw-r--r-- | sys/dev/nand/hamming.h | 56 | ||||
| -rw-r--r-- | sys/dev/nand/nand.c | 1478 | ||||
| -rw-r--r-- | sys/dev/nand/nand.h | 465 | ||||
| -rw-r--r-- | sys/dev/nand/nand_bbt.c | 250 | ||||
| -rw-r--r-- | sys/dev/nand/nand_bbt.h | 58 | ||||
| -rw-r--r-- | sys/dev/nand/nand_crc.c | 63 | ||||
| -rw-r--r-- | sys/dev/nand/nand_crc.h | 42 | ||||
| -rw-r--r-- | sys/dev/nand/nand_io.c | 373 | ||||
| -rw-r--r-- | sys/dev/nand/nandemulator.c | 779 | ||||
| -rw-r--r-- | sys/dev/nand/nandemulator.h | 5 | ||||
| -rw-r--r-- | sys/dev/nand/onfi.h | 180 |
16 files changed, 4869 insertions, 0 deletions
diff --git a/sys/dev/flash/files.flash b/sys/dev/flash/files.flash new file mode 100644 index 00000000000..7871e2d9031 --- /dev/null +++ b/sys/dev/flash/files.flash @@ -0,0 +1,9 @@ +# $NetBSD: files.flash,v 1.1 2011/02/26 18:07:30 ahoka Exp $ + +define flashbus { [offset = 0], [size = 0], [readonly = 0] } + +device flash +attach flash at flashbus +file dev/flash/flash.c flash + +defflag opt_flash.h FLASH_STATIC_PARTITIONS diff --git a/sys/dev/flash/flash.c b/sys/dev/flash/flash.c new file mode 100644 index 00000000000..33007cfe339 --- /dev/null +++ b/sys/dev/flash/flash.c @@ -0,0 +1,695 @@ +/* $NetBSD: flash.c,v 1.1 2011/02/26 18:07:30 ahoka Exp $ */ + +/*- + * Copyright (c) 2011 Department of Software Engineering, + * University of Szeged, Hungary + * Copyright (c) 2011 Adam Hoka <ahoka@NetBSD.org> + * Copyright (c) 2010 David Tengeri <dtengeri@inf.u-szeged.hu> + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by the Department of Software Engineering, University of Szeged, Hungary + * + * 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. + */ + +/*- + * Framework for storage devices based on Flash technology + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: flash.c,v 1.1 2011/02/26 18:07:30 ahoka Exp $"); + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/proc.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/device.h> +#include <sys/conf.h> +#include <sys/kmem.h> +#include <sys/uio.h> +#include <sys/kernel.h> + +#include <sys/atomic.h> +#include <sys/buf.h> +#include <sys/bufq.h> +#include <sys/disk.h> +#include <sys/disklabel.h> +#include <sys/malloc.h> +#include <sys/reboot.h> + +#include <sys/flashio.h> +#include "flash.h" + +#define FLASH_DEBUG 1 +#ifdef FLASH_DEBUG +#define DPRINTF(x) if (flashdebug) printf x +#define DPRINTFN(n,x) if (flashdebug>(n)) printf x +int flashdebug = FLASH_DEBUG; +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +extern struct cfdriver flash_cd; + +dev_type_open(flashopen); +dev_type_close(flashclose); +dev_type_read(flashread); +dev_type_write(flashwrite); +dev_type_ioctl(flashioctl); +dev_type_strategy(flashstrategy); +dev_type_dump(flashdump); +dev_type_size(flashsize); + +int flash_print(void *aux, const char *pnp); + +bool flash_shutdown(device_t dev, int how); +int flash_nsectors(struct buf *bp); +int flash_sector(struct buf *bp); + +static inline off_t flash_get_part_offset(struct flash_softc *fl, + size_t poffset); + +int flash_match(device_t parent, cfdata_t match, void *aux); +void flash_attach(device_t parent, device_t self, void *aux); +int flash_detach(device_t device, int flags); + +CFATTACH_DECL_NEW(flash, sizeof(struct flash_softc), + flash_match, flash_attach, flash_detach, NULL); + +/** + * Block device's operation + */ +const struct bdevsw flash_bdevsw = { + .d_open = flashopen, + .d_close = flashclose, + .d_strategy = flashstrategy, + .d_ioctl = flashioctl, + .d_dump = flashdump, + .d_psize = flashsize, + .d_flag = D_DISK | D_MPSAFE +}; + +/** + * Character device's operations + */ +const struct cdevsw flash_cdevsw = { + .d_open = flashopen, + .d_close = flashclose, + .d_read = flashread, + .d_write = flashwrite, + .d_ioctl = flashioctl, + .d_stop = nostop, + .d_tty = notty, + .d_poll = nopoll, + .d_mmap = nommap, + .d_kqfilter = nokqfilter, + .d_flag = D_DISK | D_MPSAFE +}; + +/* ARGSUSED */ +int +flash_match(device_t parent, cfdata_t match, void *aux) +{ + /* pseudo device, always attaches */ + return 1; +} + +/* ARGSUSED */ +void +flash_attach(device_t parent, device_t self, void *aux) +{ + struct flash_softc *sc = device_private(self); + struct flash_attach_args *faa = aux; + char pbuf[2][sizeof("9999 KB")]; + + sc->sc_dev = self; + sc->sc_parent_dev = parent; + sc->flash_if = faa->flash_if; + sc->hw_softc = device_private(parent); + + format_bytes(pbuf[0], sizeof(pbuf[0]), sc->flash_if->size); + format_bytes(pbuf[1], sizeof(pbuf[1]), sc->flash_if->erasesize); + + aprint_naive("\n"); + + switch (sc->flash_if->type) { + case FLASH_TYPE_NOR: + aprint_normal(": %s NOR flash\n", pbuf[0]); + break; + + case FLASH_TYPE_NAND: + aprint_normal(": %s NAND flash\n", pbuf[0]); + break; + + default: + aprint_normal(": %s unknown flash\n", pbuf[0]); + } + + aprint_normal_dev(sc->sc_dev, + "size: 0x%jx, offset: 0x%jx", + (uintmax_t )sc->flash_if->partition.part_size, + (uintmax_t )sc->flash_if->partition.part_offset); + + if (sc->flash_if->partition.part_flags & FLASH_PART_READONLY) { + sc->sc_readonly = true; + aprint_normal(", read only"); + } else { + sc->sc_readonly = false; + } + + aprint_normal("\n"); + + if (sc->flash_if->partition.part_size == 0) { + aprint_error_dev(self, + "partition size must be larger than 0\n"); + return; + } + + switch (sc->flash_if->type) { + case FLASH_TYPE_NOR: + aprint_normal_dev(sc->sc_dev, + "erase size %s bytes, write size %d bytes\n", + pbuf[1], sc->flash_if->writesize); + break; + + case FLASH_TYPE_NAND: + default: + aprint_normal_dev(sc->sc_dev, + "erase size %s, page size %d bytes, write size %d bytes\n", + pbuf[1], sc->flash_if->page_size, + sc->flash_if->writesize); + break; + } + + if (!pmf_device_register1(sc->sc_dev, NULL, NULL, flash_shutdown)) + aprint_error_dev(sc->sc_dev, + "couldn't establish power handler\n"); +} + +int +flash_detach(device_t device, int flags) +{ + struct flash_softc *sc = device_private(device); + + pmf_device_deregister(sc->sc_dev); + + /* freeing flash_if is our responsibility */ + printf("freeing flash_if..."); + kmem_free(sc->flash_if, sizeof(*sc->flash_if)); + printf("done!\n"); + + return 0; +} + +int +flash_print(void *aux, const char *pnp) +{ + struct flash_attach_args *arg; + const char *type; + + if (pnp != NULL) { + arg = aux; + switch (arg->flash_if->type) { + case FLASH_TYPE_NOR: + type = "NOR"; + break; + case FLASH_TYPE_NAND: + type = "NAND"; + break; + default: + panic("flash_print: unknown type %d", + arg->flash_if->type); + } + aprint_normal("%s flash at %s", type, pnp); + } + return UNCONF; +} + +device_t +flash_attach_mi(struct flash_interface *flash_if, device_t device) +{ + struct flash_attach_args arg; + +#ifdef DIAGNOSTIC + if (flash_if == NULL) { + aprint_error("flash_attach_mi: NULL\n"); + return 0; + } +#endif + arg.flash_if = flash_if; + + return config_found_ia(device, "flashbus", &arg, flash_print); +} + +/** + * flash_open - open the character device + * Checks if there is a driver registered to the minor number of the open + * request. + */ +int +flashopen(dev_t dev, int flags, int fmt, struct lwp *l) +{ + int unit = minor(dev); + struct flash_softc *sc; + + DPRINTFN(1, ("flash: opening device unit %d\n", unit)); + + if ((sc = device_lookup_private(&flash_cd, unit)) == NULL) + return ENXIO; + + /* TODO return eperm if want to open for writing a read only dev */ + + /* reset buffer length */ +// sc->sc_cache->fc_len = 0; + + return 0; +} + +/** + * flash_close - close device + * We don't have to release any resources, so just return 0. + */ +int +flashclose(dev_t dev, int flags, int fmt, struct lwp *l) +{ + int unit = minor(dev); + struct flash_softc *sc; + int err; + + DPRINTFN(1, ("flash: closing flash device unit %d\n", unit)); + + if ((sc = device_lookup_private(&flash_cd, unit)) == NULL) + return ENXIO; + + if (!sc->sc_readonly) { + err = flash_sync(sc->sc_dev); + if (err) + return err; + } + + return 0; +} + +/** + * flash_read - read from character device + * This function uses the registered driver's read function to read the requested length to + * a buffer and then moves this buffer to userspace. + */ +int +flashread(dev_t dev, struct uio *uio, int flag) +{ + return physio(flashstrategy, NULL, dev, B_READ, minphys, uio); +} + +/** + * flash_write - write to character device + * This function moves the data into a buffer from userspace to kernel space, + * then uses the registered driver's write function to write out the data to + * the media. + */ +int +flashwrite(dev_t dev, struct uio *uio, int flag) +{ + return physio(flashstrategy, NULL, dev, B_WRITE, minphys, uio); +} + +void +flashstrategy(struct buf *bp) +{ + struct flash_softc *sc; + const struct flash_interface *flash_if; + const struct flash_partition *part; + int unit, device_blks; + + unit = minor(bp->b_dev); + sc = device_lookup_private(&flash_cd, unit); + if (sc == NULL) { + bp->b_error = ENXIO; + goto done; + } + + flash_if = sc->flash_if; + part = &flash_if->partition; + + /* divider */ + KASSERT(flash_if->writesize != 0); + + aprint_debug_dev(sc->sc_dev, "flash_strategy()\n"); + + if (!(bp->b_flags & B_READ) && sc->sc_readonly) { + bp->b_error = EACCES; + goto done; + } + + /* check if length is not negative */ + if (bp->b_blkno < 0) { + bp->b_error = EINVAL; + goto done; + } + + /* zero lenght i/o */ + if (bp->b_bcount == 0) { + goto done; + } + + device_blks = sc->flash_if->size / DEV_BSIZE; + KASSERT(part->part_offset % DEV_BSIZE == 0); + bp->b_rawblkno = bp->b_blkno + (part->part_offset / DEV_BSIZE); + + if (bounds_check_with_mediasize(bp, DEV_BSIZE, device_blks) <= 0) { + goto done; + } + + bp->b_resid = bp->b_bcount; + flash_if->submit(sc->sc_parent_dev, bp); + + return; +done: + bp->b_resid = bp->b_bcount; + biodone(bp); +} + +/* + * Handle the ioctl for the device + */ +int +flashioctl(dev_t dev, u_long command, void *data, int flags, struct lwp *l) +{ + struct flash_erase_params *ep; + struct flash_info_params *ip; + struct flash_dump_params *dp; + struct flash_badblock_params *bbp; + struct flash_erase_instruction ei; + struct flash_softc *sc; + int unit, err; + size_t retlen; + flash_addr_t offset; + + unit = minor(dev); + if ((sc = device_lookup_private(&flash_cd, unit)) == NULL) + return ENXIO; + + err = 0; + switch (command) { + case FLASH_ERASE_BLOCK: + /** + * Set up an erase instruction then call the registered + * driver's erase operation. + */ + ep = data; + + if (sc->sc_readonly) { + return EACCES; + } + + ei.ei_addr = ep->ep_addr; + ei.ei_len = ep->ep_len; + ei.ei_callback = NULL; + + err = flash_erase(sc->sc_dev, &ei); + if (err) { + return err; + } + + break; + case FLASH_BLOCK_ISBAD: + /** + * Set up an erase instruction then call the registered + * driver's erase operation. + */ + bbp = data; + + err = flash_block_isbad(sc->sc_dev, bbp->bbp_addr); + if (err == EIO) { + bbp->bbp_isbad = true; + err = 0; + } else if (err) { + return err; + } else { + bbp->bbp_isbad = false; + } + + break; + case FLASH_BLOCK_MARKBAD: + bbp = data; + + err = flash_block_markbad(sc->sc_dev, bbp->bbp_addr); + + break; + case FLASH_DUMP: + dp = data; + offset = dp->dp_block * sc->flash_if->erasesize; + DPRINTF(("Reading from block: %jd len: %jd\n", + (intmax_t )dp->dp_block, (intmax_t )dp->dp_len)); + err = flash_read(sc->sc_parent_dev, offset, dp->dp_len, + &retlen, dp->dp_buf); + if (err) + return err; + if (retlen != dp->dp_len) { + dp->dp_len = -1; + dp->dp_buf = NULL; + } + + break; + case FLASH_GET_INFO: + ip = data; + + ip->ip_page_size = sc->flash_if->page_size; + ip->ip_erase_size = sc->flash_if->erasesize; + ip->ip_flash_type = sc->flash_if->type; + ip->ip_flash_size = sc->flash_if->size; + break; + default: + err = ENODEV; + } + + return err; +} +int +flashsize(dev_t dev) +{ + return -1; +} + +int +flashdump(dev_t dev, daddr_t blkno, void *va, size_t size) +{ + return EACCES; +} + +bool +flash_shutdown(device_t self, int how) +{ + struct flash_softc *sc = device_private(self); + + if ((how & RB_NOSYNC) == 0 && !sc->sc_readonly) + flash_sync(self); + + return true; +} + +const struct flash_interface * +flash_get_interface(dev_t dev) +{ + struct flash_softc *sc; + int unit; + + unit = minor(dev); + if ((sc = device_lookup_private(&flash_cd, unit)) == NULL) + return NULL; + + return sc->flash_if; +} + +const struct flash_softc * +flash_get_softc(dev_t dev) +{ + struct flash_softc *sc; + int unit; + + unit = minor(dev); + sc = device_lookup_private(&flash_cd, unit); + + return sc; +} + +device_t +flash_get_device(dev_t dev) +{ + struct flash_softc *sc; + int unit; + + unit = minor(dev); + sc = device_lookup_private(&flash_cd, unit); + + return sc->sc_dev; +} + +static inline off_t +flash_get_part_offset(struct flash_softc *fl, size_t poffset) +{ + return fl->flash_if->partition.part_offset + poffset; +} + +int +flash_erase(device_t self, struct flash_erase_instruction *ei) +{ + struct flash_softc *sc = device_private(self); + KASSERT(ei != NULL); + struct flash_erase_instruction e = *ei; + + if (sc->sc_readonly) + return EACCES; + + /* adjust for flash partition */ + e.ei_addr += sc->flash_if->partition.part_offset; + + /* bounds check for flash partition */ + if (e.ei_addr + e.ei_len > sc->flash_if->partition.part_size + + sc->flash_if->partition.part_offset) + return EINVAL; + + return sc->flash_if->erase(device_parent(self), &e); +} + +int +flash_read(device_t self, + off_t offset, size_t len, size_t *retlen, uint8_t *buf) +{ + struct flash_softc *sc = device_private(self); + + offset += sc->flash_if->partition.part_offset; + + if (offset + len > sc->flash_if->partition.part_size + + sc->flash_if->partition.part_offset) + return EINVAL; + + return sc->flash_if->read(device_parent(self), + offset, len, retlen, buf); +} + +int +flash_write(device_t self, + off_t offset, size_t len, size_t *retlen, const uint8_t *buf) +{ + struct flash_softc *sc = device_private(self); + + if (sc->sc_readonly) + return EACCES; + + offset += sc->flash_if->partition.part_offset; + + if (offset + len > sc->flash_if->partition.part_size + + sc->flash_if->partition.part_offset) + return EINVAL; + + return sc->flash_if->write(device_parent(self), + offset, len, retlen, buf); +} + +int +flash_block_markbad(device_t self, uint64_t offset) +{ + struct flash_softc *sc = device_private(self); + + if (sc->sc_readonly) + return EACCES; + + offset += sc->flash_if->partition.part_offset; + + if (offset + sc->flash_if->erasesize >= + sc->flash_if->partition.part_size + + sc->flash_if->partition.part_offset) + return EINVAL; + + return sc->flash_if->block_markbad(device_parent(self), offset); +} + +int +flash_block_isbad(device_t self, uint64_t offset) +{ + struct flash_softc *sc = device_private(self); + + offset += sc->flash_if->partition.part_offset; + + if (offset + sc->flash_if->erasesize > + sc->flash_if->partition.part_size + + sc->flash_if->partition.part_offset) + return EINVAL; + + return sc->flash_if->block_isbad(device_parent(self), offset); +} + +int +flash_sync(device_t self) +{ + struct flash_softc *sc = device_private(self); + + if (sc->sc_readonly) + return EACCES; + + /* noop now TODO: implement */ + return 0; +} + +MODULE(MODULE_CLASS_DRIVER, flash, NULL); + +#ifdef _MODULE +#include "ioconf.c" +#endif + +static int +flash_modcmd(modcmd_t cmd, void *opaque) +{ + int error = 0; +#ifdef _MODULE + int bmaj = -1, cmaj = -1; +#endif + + switch (cmd) { + case MODULE_CMD_INIT: +#ifdef _MODULE + error = config_init_component(cfdriver_ioconf_flash, + cfattach_ioconf_flash, cfdata_ioconf_flash); + if (error) + return error; + error = devsw_attach("flash", &flash_bdevsw, &bmaj, + &flash_cdevsw, &cmaj); + if (error) + config_fini_component(cfdriver_ioconf_flash, + cfattach_ioconf_flash, cfdata_ioconf_flash); +#endif + return error; + case MODULE_CMD_FINI: +#ifdef _MODULE + devsw_detach(&flash_bdevsw, &flash_cdevsw); + error = config_fini_component(cfdriver_ioconf_flash, + cfattach_ioconf_flash, cfdata_ioconf_flash); +#endif + return error; + default: + return ENOTTY; + } +} diff --git a/sys/dev/flash/flash.h b/sys/dev/flash/flash.h new file mode 100644 index 00000000000..ead0b3cda27 --- /dev/null +++ b/sys/dev/flash/flash.h @@ -0,0 +1,168 @@ +/* $NetBSD: flash.h,v 1.1 2011/02/26 18:07:30 ahoka Exp $ */ + +/*- + * Copyright (c) 2011 Department of Software Engineering, + * University of Szeged, Hungary + * Copyright (c) 2011 Adam Hoka <ahoka@NetBSD.org> + * Copyright (c) 2010 David Tengeri <dtengeri@inf.u-szeged.hu> + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by the Department of Software Engineering, University of Szeged, Hungary + * + * 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. + */ + +#ifndef _FLASH_H_ +#define _FLASH_H_ + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/module.h> +#include <sys/buf.h> +#include <sys/flashio.h> + +/** + * flash_softc - private structure for flash layer driver + */ + +struct flash_softc { + device_t sc_dev; + device_t sc_parent_dev; /* Hardware (parent) device */ + void *hw_softc; /* Hardware device private softc */ + struct flash_interface *flash_if; /* Hardware interface */ + + bool sc_readonly; /* read only flash device */ +}; + +struct flash_attach_args { + struct flash_interface *flash_if; /* Hardware interface */ +}; + +device_t flash_attach_mi(struct flash_interface *, device_t); +const struct flash_interface *flash_get_interface(dev_t); +const struct flash_softc *flash_get_softc(dev_t); +device_t flash_get_device(dev_t); + +/** + * struct erase_instruction - instructions to erase a flash eraseblock + * @fd: flash descriptor + * @addr: start address of the erase operation + * @len: the erase length + * @callback: callback operation, called when erase finished + * @priv: private data + * @state: the erase operation's result + */ +struct flash_erase_instruction { + flash_addr_t ei_addr; + flash_addr_t ei_len; + void (*ei_callback) (struct flash_erase_instruction *); + u_long ei_priv; + u_char ei_state; +}; + +enum { + FLASH_PART_READONLY = (1<<0), + FLASH_PART_FILESYSTEM = (1<<2) +}; + +struct flash_partition { + flash_addr_t part_offset; + flash_addr_t part_size; + int part_flags; +}; + +/** + * struct flash_interface - interface for flash operations + * @type: type of flash device + * @size: size of flash + * @page_size: page size of flash + * @erasesize: erase size of flash + * @writesize: minimum write size of flash + * @minor: minor number of the character device attached to this driver + * @erase: erase operation of the flash + * @read: read operation of the flash + * @write: write operation of the flash + * @block_markbad: marks a block as bad on the flash + * @block_isbad: checks if a block is bad on flash + */ +struct flash_interface { + int (*erase) (device_t, struct flash_erase_instruction *); + int (*read) (device_t, off_t, size_t, size_t *, uint8_t *); + int (*write) (device_t, off_t, size_t, size_t *, const uint8_t *); + int (*block_markbad)(device_t, uint64_t); + int (*block_isbad)(device_t, uint64_t); + int (*sync) (device_t); + + int (*submit)(device_t, struct buf *); + + /* storage for partition info */ + struct flash_partition partition; + + /* total size of mtd */ + flash_addr_t size; + uint32_t page_size; + uint32_t erasesize; + uint32_t writesize; + uint32_t minor; + uint8_t type; +}; + +/** + * struct cache - for caching writes on block device + */ +struct flash_cache { + size_t fc_len; + flash_addr_t fc_block; + uint8_t *fc_data; +}; + +/* flash operations should be used through these */ +int flash_erase(device_t, struct flash_erase_instruction *); +int flash_read(device_t, off_t, size_t, size_t *, uint8_t *); +int flash_write(device_t, off_t, size_t, size_t *, const uint8_t *); +int flash_block_markbad(device_t, uint64_t); +int flash_block_isbad(device_t, uint64_t); +int flash_sync(device_t); + +/* + * check_pattern - checks the buffer only contains the byte pattern + * @buf: the buffer to check + * @patt: the pattern to match + * @offset: the starting byte number, the matching starts from here + * @size: the buffer size + * + * This functions checks if the buffer only contains a specified byte pattern. + * Returns %0 if found something else, %1 otherwise. + */ +static inline int +check_pattern(const void *buf, uint8_t patt, size_t offset, size_t size) +{ + size_t i; + for (i = offset; i < size; i++) { + if (((const uint8_t *) buf)[i] != patt) + return 0; + } + return 1; +} + +#endif /* _FLASH_H_ */ diff --git a/sys/dev/nand/files.nand b/sys/dev/nand/files.nand new file mode 100644 index 00000000000..4ab562e4103 --- /dev/null +++ b/sys/dev/nand/files.nand @@ -0,0 +1,16 @@ +# $NetBSD: files.nand,v 1.1 2011/02/26 18:07:31 ahoka Exp $ + +define nandbus { } + +device nand: flashbus +attach nand at nandbus +file dev/nand/nand.c nand +file dev/nand/nand_io.c nand +file dev/nand/hamming.c nand +file dev/nand/nand_bbt.c nand +file dev/nand/nand_crc.c nand + +defpseudodev nandemulator: nandbus +file dev/nand/nandemulator.c nandemulator + +defflag opt_nand.h NAND_BBT diff --git a/sys/dev/nand/hamming.c b/sys/dev/nand/hamming.c new file mode 100644 index 00000000000..eb2cc7ab253 --- /dev/null +++ b/sys/dev/nand/hamming.c @@ -0,0 +1,232 @@ +/* $NetBSD: hamming.c,v 1.1 2011/02/26 18:07:31 ahoka Exp $ */ + +/* + * Copyright (c) 2008, Atmel Corporation + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the disclaimer below. + * + * Atmel's name may not be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE + * DISCLAIMED. IN NO EVENT SHALL ATMEL 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> +__KERNEL_RCSID(0, "$NetBSD: hamming.c,v 1.1 2011/02/26 18:07:31 ahoka Exp $"); + +#include <sys/param.h> +#include <lib/libkern/libkern.h> +#include "hamming.h" + +/** + * Calculates the 22-bit hamming code for a 256-bytes block of data. + * \param data Data buffer to calculate code for. + * \param code Pointer to a buffer where the code should be stored. + */ +void +hamming_compute_256(const uint8_t *data, uint8_t *code) +{ + unsigned int i; + uint8_t column_sum = 0; + uint8_t even_line_code = 0; + uint8_t odd_line_code = 0; + uint8_t even_column_code = 0; + uint8_t odd_column_code = 0; + + /*- + * Xor all bytes together to get the column sum; + * At the same time, calculate the even and odd line codes + */ + for (i = 0; i < 256; i++) { + column_sum ^= data[i]; + + /*- + * If the xor sum of the byte is 0, then this byte has no + * incidence on the computed code; so check if the sum is 1. + */ + if ((popcount(data[i]) & 1) == 1) { + /*- + * Parity groups are formed by forcing a particular + * index bit to 0 (even) or 1 (odd). + * Example on one byte: + * + * bits (dec) 7 6 5 4 3 2 1 0 + * (bin) 111 110 101 100 011 010 001 000 + * '---'---'---'----------. + * | + * groups P4' ooooooooooooooo eeeeeeeeeeeeeee P4 | + * P2' ooooooo eeeeeee ooooooo eeeeeee P2 | + * P1' ooo eee ooo eee ooo eee ooo eee P1 | + * | + * We can see that: | + * - P4 -> bit 2 of index is 0 --------------------' + * - P4' -> bit 2 of index is 1. + * - P2 -> bit 1 of index if 0. + * - etc... + * We deduce that a bit position has an impact on all + * even Px if the log2(x)nth bit of its index is 0 + * ex: log2(4) = 2, + * bit2 of the index must be 0 (-> 0 1 2 3) + * and on all odd Px' if the log2(x)nth bit + * of its index is 1 + * ex: log2(2) = 1, + * bit1 of the index must be 1 (-> 0 1 4 5) + * + * As such, we calculate all the possible Px and Px' + * values at the same time in two variables, + * even_line_code and odd_line_code, such as + * even_line_code bits: P128 P64 P32 + * P16 P8 P4 P2 P1 + * odd_line_code bits: P128' P64' P32' P16' + * P8' P4' P2' P1' + */ + even_line_code ^= (255 - i); + odd_line_code ^= i; + } + } + + /*- + * At this point, we have the line parities, and the column sum. + * First, We must caculate the parity group values on the column sum. + */ + for (i = 0; i < 8; i++) { + if (column_sum & 1) { + even_column_code ^= (7 - i); + odd_column_code ^= i; + } + column_sum >>= 1; + } + + /*- + * Now, we must interleave the parity values, + * to obtain the following layout: + * Code[0] = Line1 + * Code[1] = Line2 + * Code[2] = Column + * Line = Px' Px P(x-1)- P(x-1) ... + * Column = P4' P4 P2' P2 P1' P1 PadBit PadBit + */ + code[0] = 0; + code[1] = 0; + code[2] = 0; + + for (i = 0; i < 4; i++) { + code[0] <<= 2; + code[1] <<= 2; + code[2] <<= 2; + + /* Line 1 */ + if ((odd_line_code & 0x80) != 0) { + + code[0] |= 2; + } + if ((even_line_code & 0x80) != 0) { + + code[0] |= 1; + } + + /* Line 2 */ + if ((odd_line_code & 0x08) != 0) { + + code[1] |= 2; + } + if ((even_line_code & 0x08) != 0) { + + code[1] |= 1; + } + + /* Column */ + if ((odd_column_code & 0x04) != 0) { + + code[2] |= 2; + } + if ((even_column_code & 0x04) != 0) { + + code[2] |= 1; + } + + odd_line_code <<= 1; + even_line_code <<= 1; + odd_column_code <<= 1; + even_column_code <<= 1; + } + + /* Invert codes (linux compatibility) */ + code[0] = ~code[0]; + code[1] = ~code[1]; + code[2] = ~code[2]; +} + +/** + * Verifies and corrects a 256-bytes block of data using the given 22-bits + * hamming code. + * Returns 0 if there is no error, otherwise returns a HAMMING_ERROR code. + * param data Data buffer to check. + * \param original_code Hamming code to use for verifying the data. + */ +uint8_t +hamming_correct_256(uint8_t *data, const uint8_t *original_code, + const uint8_t *computed_code) +{ + /* Calculate new code */ + /* we allocate 4 bytes so we can use popcount32 in one step */ + uint8_t correction_code[4]; + + /* this byte should remain zero all the time */ + correction_code[3] = 0; + + /* Xor both codes together */ + correction_code[0] = computed_code[0] ^ original_code[0]; + correction_code[1] = computed_code[1] ^ original_code[1]; + correction_code[2] = computed_code[2] ^ original_code[2]; + + /* If all bytes are 0, there is no error */ + if (*(uint32_t *)correction_code == 0) { + return 0; + } + /* If there is a single bit error, there are 11 bits set to 1 */ + if (popcount32(*(uint32_t *)correction_code) == 11) { + /* Get byte and bit indexes */ + uint8_t byte = correction_code[0] & 0x80; + byte |= (correction_code[0] << 1) & 0x40; + byte |= (correction_code[0] << 2) & 0x20; + byte |= (correction_code[0] << 3) & 0x10; + + byte |= (correction_code[1] >> 4) & 0x08; + byte |= (correction_code[1] >> 3) & 0x04; + byte |= (correction_code[1] >> 2) & 0x02; + byte |= (correction_code[1] >> 1) & 0x01; + + uint8_t bit = (correction_code[2] >> 5) & 0x04; + bit |= (correction_code[2] >> 4) & 0x02; + bit |= (correction_code[2] >> 3) & 0x01; + + /* Correct bit */ + data[byte] ^= (1 << bit); + + return HAMMING_ERROR_SINGLEBIT; + } + /* Check if ECC has been corrupted */ + if (popcount32(*(uint32_t *)correction_code) == 1) { + return HAMMING_ERROR_ECC; + } else { + /* Otherwise, this is a multi-bit error */ + return HAMMING_ERROR_MULTIPLEBITS; + } +} + diff --git a/sys/dev/nand/hamming.h b/sys/dev/nand/hamming.h new file mode 100644 index 00000000000..4c3d569c9cf --- /dev/null +++ b/sys/dev/nand/hamming.h @@ -0,0 +1,56 @@ +/* $NetBSD: hamming.h,v 1.1 2011/02/26 18:07:31 ahoka Exp $ */ + +/* + * Copyright (c) 2008, Atmel Corporation + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the disclaimer below. + * + * Atmel's name may not be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE + * DISCLAIMED. IN NO EVENT SHALL ATMEL 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. + */ + +#ifndef HAMMING_H +#define HAMMING_H + +#include "nand.h" + +/*- + * These are the possible errors when trying to verify a block of data encoded + * using a Hamming code: + * + * - HAMMING_ERROR_SINGLEBIT + * - HAMMING_ERROR_ECC + * - HAMMING_ERROR_MULTIPLEBITS + */ + +/* A single bit was incorrect but has been recovered. */ +#define HAMMING_ERROR_SINGLEBIT NAND_ECC_CORRECTED + +/* The original code has been corrupted. */ +#define HAMMING_ERROR_ECC NAND_ECC_INVALID + +/* Multiple bits are incorrect in the data and they cannot be corrected. */ +#define HAMMING_ERROR_MULTIPLEBITS NAND_ECC_TWOBIT + +uint8_t hamming_correct_256(uint8_t *, const uint8_t *, const uint8_t *); +void hamming_compute_256(const uint8_t *data, uint8_t *code); + +#endif /* HAMMING_H */ + diff --git a/sys/dev/nand/nand.c b/sys/dev/nand/nand.c new file mode 100644 index 00000000000..e999b28fd73 --- /dev/null +++ b/sys/dev/nand/nand.c @@ -0,0 +1,1478 @@ +/* $NetBSD: nand.c,v 1.1 2011/02/26 18:07:31 ahoka Exp $ */ + +/*- + * Copyright (c) 2010 Department of Software Engineering, + * University of Szeged, Hungary + * Copyright (c) 2010 Adam Hoka <ahoka@NetBSD.org> + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by the Department of Software Engineering, University of Szeged, Hungary + * + * 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. + */ + +/* Common driver for NAND chips implementing the ONFI 2.2 specification */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: nand.c,v 1.1 2011/02/26 18:07:31 ahoka Exp $"); + +#include "locators.h" + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/device.h> +#include <sys/kmem.h> +#include <sys/sysctl.h> + +#include <dev/flash/flash.h> +#include <dev/nand/nand.h> +#include <dev/nand/onfi.h> +#include <dev/nand/hamming.h> +#include <dev/nand/nand_bbt.h> +#include <dev/nand/nand_crc.h> + +#include "opt_nand.h" + +int nand_match(device_t parent, cfdata_t match, void *aux); +void nand_attach(device_t parent, device_t self, void *aux); +int nand_detach(device_t device, int flags); +bool nand_shutdown(device_t self, int howto); + +int nand_print(void *aux, const char *pnp); + +static int nand_search(device_t parent, cfdata_t cf, const int *ldesc, + void *aux); +static void nand_address_row(device_t self, size_t row); +static void nand_address_column(device_t self, size_t row, size_t column); +static void nand_readid(device_t self, struct nand_chip *chip); +static void nand_read_parameter_page(device_t self, struct nand_chip *chip); +static const char *nand_midtoname(int id); +static int nand_scan_media(device_t self, struct nand_chip *chip); +static bool nand_check_wp(device_t self); + +CFATTACH_DECL_NEW(nand, sizeof(struct nand_softc), + nand_match, nand_attach, nand_detach, NULL); + +#ifdef NAND_DEBUG +int nanddebug = NAND_DEBUG; +#endif + +int nand_cachesync_timeout = 1; +int nand_cachesync_nodenum; + +const struct nand_manufacturer nand_mfrs[] = { + { NAND_MFR_AMD, "AMD" }, + { NAND_MFR_FUJITSU, "Fujitsu" }, + { NAND_MFR_RENESAS, "Renesas" }, + { NAND_MFR_STMICRO, "ST Micro" }, + { NAND_MFR_MICRON, "Micron" }, + { NAND_MFR_NATIONAL, "National" }, + { NAND_MFR_TOSHIBA, "Toshiba" }, + { NAND_MFR_HYNIX, "Hynix" }, + { NAND_MFR_SAMSUNG, "Samsung" }, + { NAND_MFR_UNKNOWN, "Unknown" } +}; + +/* ARGSUSED */ +int +nand_match(device_t parent, cfdata_t match, void *aux) +{ + /* pseudo device, always attaches */ + return 1; +} + +void +nand_attach(device_t parent, device_t self, void *aux) +{ + struct nand_softc *sc = device_private(self); + struct nand_attach_args *naa = aux; + struct nand_chip *chip = &sc->sc_chip; +// struct flash_interface *flash_if; +// int i; + + sc->sc_dev = self; + sc->nand_dev = parent; + sc->nand_if = naa->naa_nand_if; + +// sc->nand_softc = device_private(parent); + + aprint_naive("\n"); +// aprint_normal(": NAND flash memory\n"); + + if (nand_check_wp(self)) { + aprint_error("NAND chip is write protected!\n"); + return; + } + if (nand_scan_media(self, chip)) + return; + + /* allocate cache */ + chip->nc_oob_cache = kmem_alloc(chip->nc_spare_size, KM_SLEEP); + chip->nc_page_cache = kmem_alloc(chip->nc_page_size, KM_SLEEP); + + mutex_init(&sc->sc_device_lock, MUTEX_DEFAULT, IPL_NONE); + + if (nand_sync_thread_start(self)) { + goto error; + } + + if (!pmf_device_register1(sc->sc_dev, NULL, NULL, nand_shutdown)) + aprint_error_dev(sc->sc_dev, + "couldn't establish power handler\n"); + +#ifdef NAND_BBT + nand_bbt_init(self); + nand_bbt_scan(self); +#endif + + /* + * Attach all our devices + */ + config_search_ia(nand_search, self, NULL, NULL); + + return; +error: + kmem_free(chip->nc_oob_cache, chip->nc_spare_size); + kmem_free(chip->nc_page_cache, chip->nc_page_size); + mutex_destroy(&sc->sc_device_lock); +} + +static int +nand_search(device_t parent, cfdata_t cf, const int *ldesc, void *aux) +{ + struct nand_softc *sc = device_private(parent); + struct nand_chip *chip = &sc->sc_chip; + struct flash_interface *flash_if; + struct flash_attach_args faa; + + flash_if = kmem_alloc(sizeof(*flash_if), KM_SLEEP); + + flash_if->type = FLASH_TYPE_NAND; + + flash_if->read = nand_flash_read; + flash_if->write = nand_flash_write; + flash_if->erase = nand_flash_erase; + flash_if->block_isbad = nand_flash_isbad; + flash_if->block_markbad = nand_flash_markbad; + + flash_if->submit = nand_io_submit; + + flash_if->erasesize = chip->nc_block_size; + flash_if->page_size = chip->nc_page_size; + flash_if->writesize = chip->nc_page_size; + + flash_if->partition.part_offset = cf->cf_loc[FLASHBUSCF_OFFSET]; + + if (cf->cf_loc[FLASHBUSCF_SIZE] == 0) { + flash_if->size = chip->nc_size - + flash_if->partition.part_offset; + flash_if->partition.part_size = flash_if->size; + } else { + flash_if->size = cf->cf_loc[FLASHBUSCF_SIZE]; + flash_if->partition.part_size = cf->cf_loc[FLASHBUSCF_SIZE]; + } + + if (cf->cf_loc[FLASHBUSCF_READONLY]) + flash_if->partition.part_flags = FLASH_PART_READONLY; + else + flash_if->partition.part_flags = 0; + + faa.flash_if = flash_if; + + if (config_match(parent, cf, &faa)) { + config_attach(parent, cf, &faa, nand_print); + return 0; + } else { + kmem_free(flash_if, sizeof(*flash_if)); + } + + return 1; +} + +int +nand_detach(device_t self, int flags) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + int ret = 0; + +#ifdef NAND_BBT + nand_bbt_detach(self); +#endif + nand_sync_thread_stop(self); + + /* free oob cache */ + kmem_free(chip->nc_oob_cache, chip->nc_spare_size); + kmem_free(chip->nc_page_cache, chip->nc_page_size); + kmem_free(chip->nc_ecc_cache, chip->nc_ecc->necc_size); + + mutex_destroy(&sc->sc_device_lock); + + pmf_device_deregister(sc->sc_dev); + + return ret; +} + +int +nand_print(void *aux, const char *pnp) +{ + if (pnp != NULL) + aprint_normal("nand at %s\n", pnp); + + return UNCONF; +} + +device_t +nand_attach_mi(struct nand_interface *nand_if, device_t parent) +{ + struct nand_attach_args arg; + + KASSERT(nand_if != NULL); + + arg.naa_nand_if = nand_if; + return config_found_ia(parent, "nandbus", &arg, nand_print); +} + +static const char * +nand_midtoname(int id) +{ + int i; + + for (i = 0; nand_mfrs[i].id != 0; i++) { + if (nand_mfrs[i].id == id) + return nand_mfrs[i].name; + } + + KASSERT(nand_mfrs[i].id == 0); + + return nand_mfrs[i].name; +} + +#if 0 +/* handle quirks here */ +static void +nand_quirks(device_t self, struct nand_chip *chip) +{ + /* this is an example only! */ + switch (chip->nc_manf_id) { + case NAND_MFR_SAMSUNG: + if (chip->nc_dev_id == 0x00) { + /* do something only samsung chips need */ + /* or */ + /* chip->nc_quirks |= NC_QUIRK_NO_READ_START */ + } + } + + return; +} +#endif + +/** + * scan media to determine the chip's properties + * this function resets the device + */ +static int +nand_scan_media(device_t self, struct nand_chip *chip) +{ + struct nand_softc *sc = device_private(self); + struct nand_ecc *ecc; + uint8_t onfi_signature[4]; + + nand_select(self, true); + nand_command(self, ONFI_RESET); + nand_select(self, false); + + nand_select(self, true); + nand_command(self, ONFI_READ_ID); + nand_address(self, 0x20); + nand_read_byte(self, &onfi_signature[0]); + nand_read_byte(self, &onfi_signature[1]); + nand_read_byte(self, &onfi_signature[2]); + nand_read_byte(self, &onfi_signature[3]); + nand_select(self, false); + + if (onfi_signature[0] != 'O' || onfi_signature[1] != 'N' || + onfi_signature[2] != 'F' || onfi_signature[3] != 'I') { + aprint_error_dev(self, + "device does not support the ONFI specification\n"); + + return 1; + } + + nand_readid(self, chip); + + aprint_normal(": NAND Flash\n"); + + aprint_debug_dev(self, + "manufacturer id: 0x%.2x (%s), device id: 0x%.2x\n", + chip->nc_manf_id, + nand_midtoname(chip->nc_manf_id), + chip->nc_dev_id); + + nand_read_parameter_page(self, chip); + + ecc = chip->nc_ecc = &sc->nand_if->ecc; + + /* + * calculate the place of ecc data in oob + * we try to be compatible with Linux here + */ + switch (chip->nc_spare_size) { + case 8: + ecc->necc_offset = 0; + break; + case 16: + ecc->necc_offset = 0; + break; + case 64: + ecc->necc_offset = 40; + break; + case 128: + ecc->necc_offset = 80; + break; + default: + panic("OOB size is unexpected"); + } + + ecc->necc_steps = chip->nc_page_size / ecc->necc_block_size; + ecc->necc_size = ecc->necc_steps * ecc->necc_code_size; + + /* check if we fit in oob */ + if (ecc->necc_offset + ecc->necc_size > chip->nc_spare_size) { + panic("NAND ECC bits dont fit in OOB"); + } + + /* TODO: mark free oob area available for file systems */ + + chip->nc_ecc_cache = kmem_zalloc(ecc->necc_size, KM_SLEEP); + + /* + * calculate badblock marker offset in oob + * we try to be compatible with linux here + */ + if (chip->nc_page_size > 512) + chip->nc_badmarker_offs = 0; + else + chip->nc_badmarker_offs = 5; + + /* Calculate page shift and mask */ + chip->nc_page_shift = ffs(chip->nc_page_size) - 1; + chip->nc_page_mask = ~(chip->nc_page_size - 1); + /* same for block */ + chip->nc_block_shift = ffs(chip->nc_block_size) - 1; + chip->nc_block_mask = ~(chip->nc_block_size - 1); + + /* look for quirks here if needed in future */ + /* nand_quirks(self, chip); */ + + return 0; +} + +static void +nand_readid(device_t self, struct nand_chip *chip) +{ + nand_select(self, true); + nand_command(self, ONFI_READ_ID); + nand_address(self, 0x00); + nand_read_byte(self, &chip->nc_manf_id); + nand_read_byte(self, &chip->nc_dev_id); + nand_select(self, false); +} + +/* read the parameter page. TODO: check CRC! */ +static void +nand_read_parameter_page(device_t self, struct nand_chip *chip) +{ + struct onfi_parameter_page params; + uint8_t *bufp; + uint8_t vendor[13], model[21]; + uint16_t crc; + int i; + + KASSERT(sizeof(params) == 256); + + nand_select(self, true); + nand_command(self, ONFI_READ_PARAMETER_PAGE); + nand_address(self, 0x00); + + nand_busy(self); + + bufp = (uint8_t *)¶ms; + for (i = 0; i < 256; i++) { + nand_read_byte(self, &bufp[i]); + } + nand_select(self, false); + + /* validate the parameter page with the crc */ + crc = nand_crc16(bufp, 254); + + if (crc != params.param_integrity_crc) { + aprint_error_dev(self, "parameter page crc check failed\n"); + /* TODO: we should read the next parameter page copy */ + } + + /* strip manufacturer and model string */ + strlcpy(vendor, params.param_manufacturer, sizeof(vendor)); + for (i = 11; i > 0 && vendor[i] == ' '; i--) + vendor[i] = 0; + strlcpy(model, params.param_model, sizeof(model)); + for (i = 19; i > 0 && model[i] == ' '; i--) + model[i] = 0; + + aprint_normal_dev(self, "vendor: %s, model: %s\n", vendor, model); + + aprint_normal_dev(self, + "page size: %u bytes, spare size: %u bytes, block size: %u bytes\n", + params.param_pagesize, params.param_sparesize, + params.param_blocksize * params.param_pagesize); + + aprint_normal_dev(self, + "LUN size: %u blocks, LUNs: %u, total storage size: %u MB\n", + params.param_lunsize, params.param_numluns, + params.param_blocksize * params.param_pagesize * + params.param_lunsize * params.param_numluns / 1024 / 1024); + + /* XXX TODO multiple LUNs */ + if (__predict_false(params.param_numluns != 1)) + panic("more than one LUNs are not supported yet!\n"); + + chip->nc_size = params.param_pagesize * params.param_blocksize * + params.param_lunsize * params.param_numluns; + + chip->nc_page_size = params.param_pagesize; + chip->nc_block_pages = params.param_blocksize; + chip->nc_block_size = params.param_blocksize * params.param_pagesize; + chip->nc_spare_size = params.param_sparesize; + + /* the lower 4 bits contain the row address cycles */ + chip->nc_addr_cycles_row = params.param_addr_cycles & 0x07; + /* the upper 4 bits contain the column address cycles */ + chip->nc_addr_cycles_column = (params.param_addr_cycles & ~0x07) >> 4; + +#ifdef NAND_VERBOSE + aprint_normal_dev(self, "column cycles: %d, row cycles: %d\n", + chip->nc_addr_cycles_column, chip->nc_addr_cycles_row); +#endif + + if (params.param_features & ONFI_FEATURE_16BIT) + chip->nc_flags |= NC_BUSWIDTH_16; + + if (params.param_features & ONFI_FEATURE_EXTENDED_PARAM) + chip->nc_flags |= NC_EXTENDED_PARAM; +} + +/* ARGSUSED */ +bool +nand_shutdown(device_t self, int howto) +{ + return true; +} + +static void +nand_address_column(device_t self, size_t row, size_t column) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + uint8_t i; + + DPRINTF(("addressing row: 0x%jx column: %zu\n", + (uintmax_t )row, column)); + + /* XXX TODO */ + row >>= chip->nc_page_shift; +// DPRINTF(("row address is: 0x%jx\n", (uintmax_t )row)); + + /* Write the column (subpage) address */ + if (chip->nc_flags & NC_BUSWIDTH_16) + column >>= 1; + for (i = 0; i < chip->nc_addr_cycles_column; i++, column >>= 8) + nand_address(self, column & 0xff); + + /* Write the row (page) address */ + for (i = 0; i < chip->nc_addr_cycles_row; i++, row >>= 8) + nand_address(self, row & 0xff); +} + +static void +nand_address_row(device_t self, size_t row) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + off_t i; + +// DPRINTF(("addressing row: %zu\n", row)); + + /* XXX TODO */ + row >>= chip->nc_page_shift; + + /* Write the row (page) address */ + for (i = 0; i < chip->nc_addr_cycles_row; i++, row >>= 8) + nand_address(self, row & 0xff); +} + +static inline uint8_t +nand_get_status(device_t self) +{ + uint8_t status; + + nand_command(self, ONFI_READ_STATUS); + nand_busy(self); + nand_read_byte(self, &status); + + return status; +} + +static bool +nand_check_wp(device_t self) +{ + if (nand_get_status(self) & 0x80) + return false; + else + return true; +} + +static void +nand_prepare_read(device_t self, flash_addr_t row, flash_addr_t column) +{ + nand_command(self, ONFI_READ); + nand_address_column(self, row, column); + nand_command(self, ONFI_READ_START); + + nand_busy(self); +} + +/* read a page with ecc correction */ +int +nand_read_page(device_t self, size_t offset, uint8_t *data) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + size_t b, bs, e, cs; + uint8_t *ecc; + int result; + + DPRINTF(("nand read page\n")); + + nand_prepare_read(self, offset, 0); + + bs = chip->nc_ecc->necc_block_size; + cs = chip->nc_ecc->necc_code_size; + + /* decide if we access by 8 or 16 bits */ + if (chip->nc_flags & NC_BUSWIDTH_16) { + for (b = 0, e = 0; b < chip->nc_page_size; b += bs, e += cs) { + nand_ecc_prepare(self, NAND_ECC_READ); + nand_read_buf_word(self, data + b, bs); + nand_ecc_compute(self, data + b, + chip->nc_ecc_cache + e); + } + } else { + for (b = 0, e = 0; b < chip->nc_page_size; b += bs, e += cs) { + nand_ecc_prepare(self, NAND_ECC_READ); + nand_read_buf_byte(self, data + b, bs); + nand_ecc_compute(self, data + b, + chip->nc_ecc_cache + e); + } + } + +// nand_dump_data("page", data, chip->nc_page_size); + + nand_read_oob(self, offset, chip->nc_oob_cache); + ecc = chip->nc_oob_cache + chip->nc_ecc->necc_offset; + + /* useful for debugging new ecc drivers */ +#if 0 + printf("dumping ecc %d\n--------------\n", chip->nc_ecc->necc_steps); + for (e = 0; e < chip->nc_ecc->necc_steps; e++) { + printf("0x"); + for (b = 0; b < cs; b++) { + printf("%.2hhx", ecc[e+b]); + } + printf(" 0x"); + for (b = 0; b < cs; b++) { + printf("%.2hhx", chip->nc_ecc_cache[e+b]); + } + printf("\n"); + } + printf("--------------\n"); +#endif + + for (b = 0, e = 0; b < chip->nc_page_size; b += bs, e += cs) { + result = nand_ecc_correct(self, data + b, ecc + e, + chip->nc_ecc_cache + e); + + switch (result) { + case NAND_ECC_OK: + break; + case NAND_ECC_CORRECTED: + aprint_error_dev(self, + "data corrected with ECC at page offset 0x%jx " + "block %zu\n", (uintmax_t)offset, b); + break; + case NAND_ECC_TWOBIT: + aprint_error_dev(self, + "uncorrectable ECC error at page offset 0x%jx " + "block %zu\n", (uintmax_t)offset, b); + return EIO; + break; + case NAND_ECC_INVALID: + aprint_error_dev(self, + "invalid ECC in oob at page offset 0x%jx " + "block %zu\n", (uintmax_t)offset, b); + return EIO; + break; + default: + panic("invalid ECC correction errno"); + } + } + + return 0; +} + +static int +nand_program_page(device_t self, size_t page, const uint8_t *data) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + size_t bs, cs, e, b; + uint8_t status; + uint8_t *ecc; + + nand_command(self, ONFI_PAGE_PROGRAM); + nand_address_column(self, page, 0); + + nand_busy(self); + + bs = chip->nc_ecc->necc_block_size; + cs = chip->nc_ecc->necc_code_size; + ecc = chip->nc_oob_cache + chip->nc_ecc->necc_offset; + + /* XXX code duplication */ + /* decide if we access by 8 or 16 bits */ + if (chip->nc_flags & NC_BUSWIDTH_16) { + for (b = 0, e = 0; b < chip->nc_page_size; b += bs, e += cs) { + nand_ecc_prepare(self, NAND_ECC_WRITE); + nand_write_buf_word(self, data + b, bs); + nand_ecc_compute(self, data + b, ecc + e); + } + /* write oob with ecc correction code */ + nand_write_buf_word(self, chip->nc_oob_cache, + chip->nc_spare_size); + } else { + for (b = 0, e = 0; b < chip->nc_page_size; b += bs, e += cs) { + nand_ecc_prepare(self, NAND_ECC_WRITE); + nand_write_buf_byte(self, data + b, bs); + nand_ecc_compute(self, data + b, ecc + e); + } + /* write oob with ecc correction code */ + nand_write_buf_byte(self, chip->nc_oob_cache, + chip->nc_spare_size); + } + + nand_command(self, ONFI_PAGE_PROGRAM_START); + + nand_busy(self); + +#if 0 + printf("dumping ecc %d\n--------------\n", chip->nc_ecc->necc_steps); + for (e = 0; e < chip->nc_ecc->necc_steps; e++) { + printf("0x"); + for (b = 0; b < cs; b++) { + printf("%.2hhx", ecc[e+b]); + } + printf("\n"); + } + printf("--------------\n"); +#endif + + status = nand_get_status(self); + KASSERT(status & ONFI_STATUS_RDY); + if (status & ONFI_STATUS_FAIL) { + aprint_error_dev(self, "page program failed!\n"); + return EIO; + } + + return 0; +} + +int +nand_read_oob(device_t self, size_t page, void *oob) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + + nand_prepare_read(self, page, chip->nc_page_size); + + if (chip->nc_flags & NC_BUSWIDTH_16) + nand_read_buf_word(self, oob, chip->nc_spare_size); + else + nand_read_buf_byte(self, oob, chip->nc_spare_size); + +// nand_dump_data("oob", oob, chip->nc_spare_size); + + return 0; +} + +static int +nand_write_oob(device_t self, size_t offset, const void *oob) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + uint8_t status; + + nand_command(self, ONFI_PAGE_PROGRAM); + nand_address_column(self, offset, chip->nc_page_size); + nand_command(self, ONFI_PAGE_PROGRAM_START); + + nand_busy(self); + + if (chip->nc_flags & NC_BUSWIDTH_16) + nand_write_buf_word(self, oob, chip->nc_spare_size); + else + nand_write_buf_byte(self, oob, chip->nc_spare_size); + + status = nand_get_status(self); + KASSERT(status & ONFI_STATUS_RDY); + if (status & ONFI_STATUS_FAIL) + return EIO; + else + return 0; +} + +void +nand_markbad(device_t self, size_t offset) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + flash_addr_t blockoffset, marker; +#ifdef NAND_BBT + flash_addr_t block; + + block = offset / chip->nc_block_size; + + nand_bbt_block_markbad(self, block); +#endif + blockoffset = offset & chip->nc_block_mask; + marker = chip->nc_badmarker_offs & ~0x01; + + /* check if it is already marked bad */ + if (nand_isbad(self, blockoffset)) + return; + + nand_read_oob(self, blockoffset, chip->nc_oob_cache); + + chip->nc_oob_cache[chip->nc_badmarker_offs] = 0x00; + chip->nc_oob_cache[chip->nc_badmarker_offs + 1] = 0x00; + + nand_write_oob(self, blockoffset, chip->nc_oob_cache); +} + +bool +nand_isfactorybad(device_t self, flash_addr_t offset) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + flash_addr_t block, first_page, last_page, page; + int i; + + /* Check for factory bad blocks first + * Factory bad blocks are marked in the first or last + * page of the blocks, see: ONFI 2.2, 3.2.2. + */ + block = offset / chip->nc_block_size; + first_page = block * chip->nc_block_size; + last_page = (block + 1) * chip->nc_block_size + - chip->nc_page_size; + + for (i = 0, page = first_page; i < 2; i++, page = last_page) { + /* address OOB */ + nand_prepare_read(self, page, chip->nc_page_size); + + if (chip->nc_flags & NC_BUSWIDTH_16) { + uint16_t word; + nand_read_word(self, &word); + if (word == 0x0000) + return true; + } else { + uint8_t byte; + nand_read_byte(self, &byte); + if (byte == 0x00) + return true; + } + } + + return false; +} + +bool +nand_iswornoutbad(device_t self, flash_addr_t offset) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + flash_addr_t block; + + /* we inspect the first page of the block */ + block = offset & chip->nc_block_mask; + + /* Linux/u-boot compatible badblock handling */ + if (chip->nc_flags & NC_BUSWIDTH_16) { + uint16_t word, mark; + + nand_prepare_read(self, block, + chip->nc_page_size + (chip->nc_badmarker_offs & 0xfe)); + + nand_read_word(self, &word); + mark = htole16(word); + if (chip->nc_badmarker_offs & 0x01) + mark >>= 8; + if ((mark & 0xff) != 0xff) + return true; + } else { + uint8_t byte; + + nand_prepare_read(self, block, + chip->nc_page_size + chip->nc_badmarker_offs); + + nand_read_byte(self, &byte); + if (byte != 0xff) + return true; + } + + return false; +} + +bool +nand_isbad(device_t self, flash_addr_t offset) +{ +#ifdef NAND_BBT + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + flash_addr_t block; + + block = offset / chip->nc_block_size; + + return nand_bbt_block_isbad(self, block); +#else + /* ONFI host requirement */ + if (nand_isfactorybad(self, offset)) + return true; + + /* Look for Linux/U-Boot compatible bad marker */ + if (nand_iswornoutbad(self, offset)) + return true; + + return false; +#endif +} + +int +nand_erase_block(device_t self, size_t offset) +{ +// struct nand_softc *sc = device_private(self); +// struct nand_chip *chip = &sc->sc_chip; + uint8_t status; + + /* xxx calculate first page of block for address? */ + + nand_command(self, ONFI_BLOCK_ERASE); + nand_address_row(self, offset); + nand_command(self, ONFI_BLOCK_ERASE_START); + + nand_busy(self); + + status = nand_get_status(self); + KASSERT(status & ONFI_STATUS_RDY); + if (status & ONFI_STATUS_FAIL) { + aprint_error_dev(self, "block erase failed!\n"); + nand_markbad(self, offset); + return EIO; + } else { + return 0; + } +} + +/* default functions for driver development */ + +/* default ECC using hamming code of 256 byte chunks */ +int +nand_default_ecc_compute(device_t self, const uint8_t *data, uint8_t *code) +{ + hamming_compute_256(data, code); + + return 0; +} + +int +nand_default_ecc_correct(device_t self, uint8_t *data, const uint8_t *origcode, + const uint8_t *compcode) +{ + return hamming_correct_256(data, origcode, compcode); +} + +void +nand_default_select(device_t self, bool enable) +{ + /* do nothing */ + return; +} + +/* implementation of the block device API */ + +/* + * handle (page) unaligned write to nand + */ +static int +nand_flash_write_unaligned(device_t self, off_t offset, size_t len, + size_t *retlen, const uint8_t *buf) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + flash_addr_t first, last, firstoff; + const uint8_t *bufp; + flash_addr_t addr; + size_t left, count; + int error, i; + + /* to debug chfs */ +// printf("unaligned write to nand\n"); + + first = offset & chip->nc_page_mask; + firstoff = offset & ~chip->nc_page_mask; + /* XXX check if this should be len - 1 */ + last = (offset + len) & chip->nc_page_mask; + count = last - first + 1; + + addr = first; + *retlen = 0; + + if (count == 1) { + if (nand_isbad(self, addr)) { + aprint_error_dev(self, + "nand_flash_write_unaligned: " + "bad block encountered\n"); + return EIO; + } + + error = nand_read_page(self, addr, chip->nc_page_cache); + if (error) + return error; + + memcpy(chip->nc_page_cache + firstoff, buf, len); + + error = nand_program_page(self, addr, chip->nc_page_cache); + if (error) + return error; + + *retlen = len; + return 0; + } + + bufp = buf; + left = len; + + for (i = 0; i < count && left != 0; i++) { + if (nand_isbad(self, addr)) { + aprint_error_dev(self, + "nand_flash_write_unaligned: " + "bad block encountered\n"); + return EIO; + } + + if (i == 0) { + error = nand_read_page(self, + addr, chip->nc_page_cache); + if (error) + return error; + + memcpy(chip->nc_page_cache + firstoff, + bufp, chip->nc_page_size - firstoff); + + printf("program page: %s: %d\n", __FILE__, __LINE__); + error = nand_program_page(self, + addr, chip->nc_page_cache); + if (error) + return error; + + bufp += chip->nc_page_size - firstoff; + left -= chip->nc_page_size - firstoff; + *retlen += chip->nc_page_size - firstoff; + + } else if (i == count - 1) { + error = nand_read_page(self, + addr, chip->nc_page_cache); + if (error) + return error; + + memcpy(chip->nc_page_cache, bufp, left); + + error = nand_program_page(self, + addr, chip->nc_page_cache); + if (error) + return error; + + *retlen += left; + KASSERT(left < chip->nc_page_size); + + } else { + /* XXX debug */ + if (left > chip->nc_page_size) { + printf("left: %zu, i: %d, count: %zu\n", + (size_t )left, i, count); + } + KASSERT(left > chip->nc_page_size); + + error = nand_program_page(self, addr, bufp); + if (error) + return error; + + bufp += chip->nc_page_size; + left -= chip->nc_page_size; + *retlen += chip->nc_page_size; + } + + addr += chip->nc_page_size; + } + + KASSERT(*retlen == len); + + return 0; +} + +int +nand_flash_write(device_t self, off_t offset, size_t len, size_t *retlen, + const uint8_t *buf) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + const uint8_t *bufp; + size_t pages, page; + daddr_t addr; + int error = 0; + + if ((offset + len) > chip->nc_size) { + DPRINTF(("nand_flash_write: write (off: 0x%jx, len: %ju)," + " is over device size (0x%jx)\n", + (uintmax_t)offset, (uintmax_t)len, + (uintmax_t)chip->nc_size)); + return EINVAL; + } + + if (len % chip->nc_page_size != 0 || + offset % chip->nc_page_size != 0) { + return nand_flash_write_unaligned(self, + offset, len, retlen, buf); + } + + pages = len / chip->nc_page_size; + KASSERT(pages != 0); + *retlen = 0; + + addr = offset; + bufp = buf; + + mutex_enter(&sc->sc_device_lock); + for (page = 0; page < pages; page++) { + /* do we need this check here? */ + if (nand_isbad(self, addr)) { + aprint_error_dev(self, + "nand_flash_write: bad block encountered\n"); + + error = EIO; + goto out; + } + + error = nand_program_page(self, addr, bufp); + if (error) + goto out; + + addr += chip->nc_page_size; + bufp += chip->nc_page_size; + *retlen += chip->nc_page_size; + } +out: + mutex_exit(&sc->sc_device_lock); + DPRINTF(("page programming: retlen: %zu, len: %zu\n", *retlen, len)); + + return error; +} + +/* + * handle (page) unaligned read from nand + */ +static int +nand_flash_read_unaligned(device_t self, size_t offset, + size_t len, size_t *retlen, uint8_t *buf) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + daddr_t first, last, count, firstoff; + uint8_t *bufp; + daddr_t addr; + size_t left; + int error = 0, i; + + /* to debug chfs */ +// printf("unaligned read from nand\n"); + + first = offset & chip->nc_page_mask; + firstoff = offset & ~chip->nc_page_mask; + last = (offset + len) & chip->nc_page_mask; +// lastoff = chip->nc_page_size - (offset + len) & ~chip->nc_page_mask; + count = (last - first) / chip->nc_page_size + 1; + + addr = first; + bufp = buf; + left = len; + *retlen = 0; + + mutex_enter(&sc->sc_device_lock); + if (count == 1) { + error = nand_read_page(self, addr, chip->nc_page_cache); + if (error) + goto out; + + memcpy(bufp, chip->nc_page_cache + firstoff, len); + + *retlen = len; + goto out; + } + + for (i = 0; i < count && left != 0; i++) { + error = nand_read_page(self, addr, chip->nc_page_cache); + if (error) + goto out; + + if (i == 0) { + memcpy(bufp, chip->nc_page_cache + firstoff, + chip->nc_page_size - firstoff); + + bufp += chip->nc_page_size - firstoff; + left -= chip->nc_page_size - firstoff; + *retlen += chip->nc_page_size - firstoff; + + } else if (i == count - 1) { + memcpy(bufp, chip->nc_page_cache, left); + *retlen += left; + KASSERT(left < chip->nc_page_size); + + } else { + memcpy(bufp, chip->nc_page_cache, chip->nc_page_size); + + bufp += chip->nc_page_size; + left -= chip->nc_page_size; + *retlen += chip->nc_page_size; + } + + addr += chip->nc_page_size; + } + + KASSERT(*retlen == len); + +out: + mutex_exit(&sc->sc_device_lock); + + return error; +} + +int +nand_flash_read(device_t self, off_t offset, size_t len, size_t *retlen, + uint8_t *buf) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + uint8_t *bufp; + size_t addr; + size_t i, pages; + int error = 0; + + *retlen = 0; + + DPRINTF(("nand_flash_read: off: 0x%jx, len: %zu\n", + (uintmax_t)offset, len)); + + if (__predict_false((offset + len) > chip->nc_size)) { + DPRINTF(("nand_flash_read: read (off: 0x%jx, len: %zu)," + " is over device size (%ju)\n", (uintmax_t)offset, + len, (uintmax_t)chip->nc_size)); + return EINVAL; + } + + /* Handle unaligned access, shouldnt be needed when using the + * block device, as strategy handles it, so only low level + * accesses will use this path + */ +// if (len < chip->nc_page_size) +// panic("TODO page size is larger than read size"); + + if (len % chip->nc_page_size != 0 || + offset % chip->nc_page_size != 0) { + return nand_flash_read_unaligned(self, + offset, len, retlen, buf); + } + + bufp = buf; + addr = offset; + pages = len / chip->nc_page_size; + + mutex_enter(&sc->sc_device_lock); + for (i = 0; i < pages; i++) { + /* do we need this check here? */ + if (nand_isbad(self, addr)) { + aprint_error_dev(self, "bad block encountered\n"); + error = EIO; + goto out; + } + error = nand_read_page(self, addr, bufp); + if (error) + goto out; + + bufp += chip->nc_page_size; + addr += chip->nc_page_size; + *retlen += chip->nc_page_size; + } + +out: + mutex_exit(&sc->sc_device_lock); + + return error; +} + +int +nand_flash_isbad(device_t self, uint64_t ofs) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + bool result; +// uint64_t block_num; + + if (ofs > chip->nc_size) { + DPRINTF(("nand_flash_isbad: offset 0x%jx is larger than" + " device size (0x%jx)\n", (uintmax_t)ofs, + (uintmax_t)chip->nc_size)); + return EINVAL; + } + + if (ofs % chip->nc_block_size != 0) { + panic("offset (0x%jx) is not the multiple of block size (%ju)", + (uintmax_t)ofs, (uintmax_t)chip->nc_block_size); + } +// block_num = ofs / fl->flash_if.erasesize; + + mutex_enter(&sc->sc_device_lock); + result = nand_isbad(self, ofs); + mutex_exit(&sc->sc_device_lock); + + if (result) + return 1; + else + return 0; +} + +int +nand_flash_markbad(device_t self, uint64_t ofs) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + +// uint64_t block_num; + + if (ofs > chip->nc_size) { + DPRINTF(("nand_flash_markbad: offset 0x%jx is larger than" + " device size (0x%jx)\n", ofs, + (uintmax_t)chip->nc_size)); + return EINVAL; + } + + if (ofs % chip->nc_block_size != 0) { + panic("offset (%ju) is not the multiple of block size (%ju)", + (uintmax_t)ofs, (uintmax_t)chip->nc_block_size); + } + +// block_num = ofs / fl->flash_if->erasesize; + + mutex_enter(&sc->sc_device_lock); + nand_markbad(self, ofs); + mutex_exit(&sc->sc_device_lock); + + return 0; +} + +int +nand_flash_erase(device_t self, + struct flash_erase_instruction *ei) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + flash_addr_t addr; + int error; +// off_t block_num; + +// if (FLASH_CLOSED == sc->sc_flash.status) +// return FLASH_CLOSED; + + if (ei->ei_addr < 0 || ei->ei_len < chip->nc_block_size) + return EINVAL; + + if (ei->ei_addr + ei->ei_len > chip->nc_size) { + DPRINTF(("nand_flash_erase: erase address is over the end" + " of the device\n")); + return EINVAL; + } + + if (ei->ei_addr % chip->nc_block_size != 0) { + aprint_error_dev(self, + "nand_flash_erase: ei_addr (%ju) is not" + "the multiple of block size (%ju)", + (uintmax_t)ei->ei_addr, + (uintmax_t)chip->nc_block_size); + return EINVAL; + } + + if (ei->ei_len % chip->nc_block_size != 0) { + aprint_error_dev(self, + "nand_flash_erase: ei_len (%ju) is not" + "the multiple of block size (%ju)", + (uintmax_t)ei->ei_addr, + (uintmax_t)chip->nc_block_size); + return EINVAL; + } + + mutex_enter(&sc->sc_device_lock); + addr = ei->ei_addr; + while (addr < ei->ei_addr + ei->ei_len) { + if (nand_isbad(self, addr)) { + mutex_exit(&sc->sc_device_lock); + aprint_error_dev(self, "bad block encountered\n"); + ei->ei_state = FLASH_ERASE_FAILED; + return EIO; + } + + error = nand_erase_block(self, addr); + if (error) { + mutex_exit(&sc->sc_device_lock); + ei->ei_state = FLASH_ERASE_FAILED; + return error; + } + + addr += chip->nc_block_size; + } + mutex_exit(&sc->sc_device_lock); + + ei->ei_state = FLASH_ERASE_DONE; + if (ei->ei_callback != NULL) + ei->ei_callback(ei); + + return 0; +} + +static int +sysctl_nand_verify(SYSCTLFN_ARGS) +{ + int error, t; + struct sysctlnode node; + + node = *rnode; + t = *(int *)rnode->sysctl_data; + node.sysctl_data = &t; + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error || newp == NULL) + return error; + + if (node.sysctl_num == nand_cachesync_nodenum) { + if (t <= 0 || t > 60) + return EINVAL; + } else { + return EINVAL; + } + + *(int *)rnode->sysctl_data = t; + + return 0; +} + +SYSCTL_SETUP(sysctl_nand, "sysctl nand subtree setup") +{ + int rc, nand_root_num; + const struct sysctlnode *node; + + if ((rc = sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT, CTLTYPE_NODE, "hw", NULL, + NULL, 0, NULL, 0, CTL_HW, CTL_EOL)) != 0) { + goto error; + } + + if ((rc = sysctl_createv(clog, 0, NULL, &node, + CTLFLAG_PERMANENT, CTLTYPE_NODE, "nand", + SYSCTL_DESCR("NAND driver controls"), + NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL)) != 0) { + goto error; + } + + nand_root_num = node->sysctl_num; + + if ((rc = sysctl_createv(clog, 0, NULL, &node, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_INT, "cache_sync_timeout", + SYSCTL_DESCR("NAND write cache sync timeout in seconds"), + sysctl_nand_verify, 0, &nand_cachesync_timeout, + 0, CTL_HW, nand_root_num, CTL_CREATE, + CTL_EOL)) != 0) { + goto error; + } + + nand_cachesync_nodenum = node->sysctl_num; + + return; + +error: + aprint_error("%s: sysctl_createv failed (rc = %d)\n", __func__, rc); +} + +MODULE(MODULE_CLASS_DRIVER, nand, "flash"); + +#ifdef _MODULE +#include "ioconf.c" +#endif + +static int +nand_modcmd(modcmd_t cmd, void *opaque) +{ + switch (cmd) { + case MODULE_CMD_INIT: +#ifdef _MODULE + return config_init_component(cfdriver_ioconf_nand, + cfattach_ioconf_nand, cfdata_ioconf_nand); +#else + return 0; +#endif + case MODULE_CMD_FINI: +#ifdef _MODULE + return config_fini_component(cfdriver_ioconf_nand, + cfattach_ioconf_nand, cfdata_ioconf_nand); +#else + return 0; +#endif + default: + return ENOTTY; + } +} diff --git a/sys/dev/nand/nand.h b/sys/dev/nand/nand.h new file mode 100644 index 00000000000..a9e8c1f295a --- /dev/null +++ b/sys/dev/nand/nand.h @@ -0,0 +1,465 @@ +/* $NetBSD: nand.h,v 1.1 2011/02/26 18:07:31 ahoka Exp $ */ + +/*- + * Copyright (c) 2010 Department of Software Engineering, + * University of Szeged, Hungary + * Copyright (c) 2010 Adam Hoka <ahoka@NetBSD.org> + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by the Department of Software Engineering, University of Szeged, Hungary + * + * 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. + */ + +#ifndef _NAND_H_ +#define _NAND_H_ + +#include <sys/param.h> +#include <sys/cdefs.h> + +#include <sys/bufq.h> +#include <sys/buf.h> +#include <sys/time.h> + +#include <dev/flash/flash.h> + +/* flash interface implementation */ +int nand_flash_isbad(device_t, uint64_t); +int nand_flash_markbad(device_t, uint64_t); +int nand_flash_write(device_t, off_t, size_t, size_t *, const u_char *); +int nand_flash_read(device_t, off_t, size_t, size_t *, uint8_t *); +int nand_flash_erase(device_t, struct flash_erase_instruction *); + +/* nand specific functions */ +int nand_erase_block(device_t, size_t); + +int nand_io_submit(device_t, struct buf *); +void nand_sync_thread(void *); +int nand_sync_thread_start(device_t); +void nand_sync_thread_stop(device_t); + +bool nand_isfactorybad(device_t, flash_addr_t); +bool nand_iswornoutbad(device_t, flash_addr_t); +bool nand_isbad(device_t, flash_addr_t); +void nand_markbad(device_t, size_t); + +int nand_read_page(device_t, size_t, uint8_t *); +int nand_read_oob(device_t self, size_t page, void *oob); + +/* + * default functions for driver development + */ +void nand_default_select(device_t, bool); +int nand_default_ecc_compute(device_t, const uint8_t *, uint8_t *); +int nand_default_ecc_correct(device_t, uint8_t *, const uint8_t *, + const uint8_t *); + +static inline void nand_busy(device_t); +static inline void nand_select(device_t, bool); +static inline void nand_command(device_t, uint8_t); +static inline void nand_address(device_t, uint32_t); +static inline void nand_read_buf_byte(device_t, void *, size_t); +static inline void nand_read_buf_word(device_t, void *, size_t); +static inline void nand_read_byte(device_t, uint8_t *); +static inline void nand_write_buf_byte(device_t, const void *, size_t); +static inline void nand_write_buf_word(device_t, const void *, size_t); +//static inline bool nand_block_isbad(device_t, off_t); +//static inline void nand_block_markbad(device_t, off_t); +//static inline bool nand_isbusy(device_t); + +//#define NAND_DEBUG 1 +#ifdef NAND_DEBUG +#define DPRINTF(x) if (nanddebug) printf x +#define DPRINTFN(n,x) if (nanddebug>(n)) printf x +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define NAND_VERBOSE + +/* same as in linux for compatibility */ +enum { + NAND_BAD_MARKER_OFFSET = 0, + NAND_BAD_MARKER_OFFSET_SMALL = 5 +}; + +/* feature flags use in nc_flags */ +enum { + NC_BUSWIDTH_16 = (1<<0), + NC_SOURCE_SYNC = (1<<2), + NC_INTERLEAVED_PE = (1<<1), + NC_INTERLEAVED_R = (1<<3), + NC_EXTENDED_PARAM = (1<<4) +}; + +/* various quirks used in nc_quirks */ +enum { + NC_QUIRK_NO_READ_START = (1<<0) +}; + +enum { + NAND_ECC_READ, + NAND_ECC_WRITE +}; + +enum { + NAND_ECC_OK, + NAND_ECC_CORRECTED, + NAND_ECC_INVALID, + NAND_ECC_TWOBIT +}; + +enum { + NAND_ECC_TYPE_HW, + NAND_ECC_TYPE_SW +}; + +struct nand_bbt { + uint8_t *nbbt_bitmap; + size_t nbbt_size; +}; + +struct nand_ecc { + size_t necc_offset; /* offset of ecc data in oob */ + size_t necc_size; /* size of ecc data in oob */ + size_t necc_block_size; /* block size used in ecc calc */ + size_t necc_code_size; /* reduntant bytes per block */ + int necc_steps; /* pagesize / code size */ + int necc_type; /* type of the ecc engine */ +}; + +/** + * nand_chip: structure containing the required information + * about the NAND chip. + */ +struct nand_chip { + uint8_t *nc_oob_cache; /* buffer for oob cache */ + uint8_t *nc_page_cache; /* buffer for page cache */ + uint8_t *nc_ecc_cache; + size_t nc_size; /* storage size in bytes */ + size_t nc_page_size; /* page size in bytes */ + size_t nc_block_pages; /* block size in pages */ + size_t nc_block_size; /* block size in bytes */ + size_t nc_spare_size; /* spare (oob) size in bytes */ + uint32_t nc_flags; /* bitfield flags */ + uint32_t nc_quirks; /* bitfield quirks */ + unsigned int nc_page_shift; /* page shift for page alignment */ + unsigned int nc_page_mask; /* page mask for page alignment */ + unsigned int nc_block_shift; /* write shift */ + unsigned int nc_block_mask; /* write mask */ + uint8_t nc_manf_id; /* manufacturer id */ + uint8_t nc_dev_id; /* device id */ + uint8_t nc_addr_cycles_row; /* row cycles for addressing */ + uint8_t nc_addr_cycles_column; /* column cycles for addressing */ + uint8_t nc_badmarker_offs; /* offset for marking bad blocks */ + + struct nand_ecc *nc_ecc; +}; + +struct nand_write_cache { + struct bintime nwc_creation; + struct bintime nwc_last_write; + struct bufq_state *nwc_bufq; + uint8_t *nwc_data; + daddr_t nwc_block; + kmutex_t nwc_lock; + bool nwc_write_pending; +}; + +/* driver softc for nand */ +struct nand_softc { + device_t sc_dev; + device_t nand_dev; + struct nand_interface *nand_if; + void *nand_softc; + struct nand_chip sc_chip; + struct nand_bbt sc_bbt; + size_t sc_part_offset; + size_t sc_part_size; + kmutex_t sc_device_lock; /* serialize access to chip */ + + /* for the i/o thread */ + struct lwp *sc_sync_thread; + struct nand_write_cache sc_cache; + kmutex_t sc_io_lock; + kmutex_t sc_waitq_lock; + kcondvar_t sc_io_cv; + bool sc_io_running; +}; + +/* structure holding the nand api */ +struct nand_interface +{ + void (*select) (device_t, bool); + void (*command) (device_t, uint8_t); + void (*address) (device_t, uint8_t); + void (*read_buf_byte) (device_t, void *, size_t); + void (*read_buf_word) (device_t, void *, size_t); + void (*read_byte) (device_t, uint8_t *); + void (*read_word) (device_t, uint16_t *); + void (*write_buf_byte) (device_t, const void *, size_t); + void (*write_buf_word) (device_t, const void *, size_t); + void (*write_byte) (device_t, uint8_t); + void (*write_word) (device_t, uint16_t); + void (*busy) (device_t); + + /* functions specific to ecc computation */ + int (*ecc_prepare)(device_t, int); + int (*ecc_compute)(device_t, const uint8_t *, uint8_t *); + int (*ecc_correct)(device_t, uint8_t *, const uint8_t *, + const uint8_t *); + + struct nand_ecc ecc; + + /* flash partition information */ + const struct flash_partition *part_info; + int part_num; +}; + +/* attach args */ +struct nand_attach_args { + struct nand_interface *naa_nand_if; +}; + +device_t nand_attach_mi(struct nand_interface *nand_if, device_t dev); + +static inline void +nand_busy(device_t device) +{ + struct nand_softc *sc = device_private(device); + + KASSERT(sc->nand_if->select != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->select(sc->nand_dev, true); + + if (sc->nand_if->busy != NULL) { + sc->nand_if->busy(sc->nand_dev); + } + + sc->nand_if->select(sc->nand_dev, false); +} + +static inline void +nand_select(device_t self, bool enable) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->select != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->select(sc->nand_dev, enable); +} + +static inline void +nand_address(device_t self, uint32_t address) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->address != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->address(sc->nand_dev, address); +} + +static inline void +nand_command(device_t self, uint8_t command) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->command != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->command(sc->nand_dev, command); +} + +static inline void +nand_read_byte(device_t self, uint8_t *data) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->read_byte != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->read_byte(sc->nand_dev, data); +} + +static inline void +nand_write_byte(device_t self, uint8_t data) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->write_byte != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->write_byte(sc->nand_dev, data); +} + +static inline void +nand_read_word(device_t self, uint16_t *data) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->read_word != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->read_word(sc->nand_dev, data); +} + +static inline void +nand_write_word(device_t self, uint16_t data) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->write_word != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->write_word(sc->nand_dev, data); +} + +static inline void +nand_read_buf_byte(device_t self, void *buf, size_t size) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->read_buf_byte != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->read_buf_byte(sc->nand_dev, buf, size); +} + +static inline void +nand_read_buf_word(device_t self, void *buf, size_t size) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->read_buf_word != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->read_buf_word(sc->nand_dev, buf, size); +} + +static inline void +nand_write_buf_byte(device_t self, const void *buf, size_t size) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->write_buf_byte != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->write_buf_byte(sc->nand_dev, buf, size); +} + +static inline void +nand_write_buf_word(device_t self, const void *buf, size_t size) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->write_buf_word != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->write_buf_word(sc->nand_dev, buf, size); +} + +static inline int +nand_ecc_correct(device_t self, uint8_t *data, const uint8_t *oldcode, + const uint8_t *newcode) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->ecc_correct != NULL); + KASSERT(sc->nand_dev != NULL); + + return sc->nand_if->ecc_correct(sc->nand_dev, data, oldcode, newcode); +} + +static inline void +nand_ecc_compute(device_t self, const uint8_t *data, uint8_t *code) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->ecc_compute != NULL); + KASSERT(sc->nand_dev != NULL); + + sc->nand_if->ecc_compute(sc->nand_dev, data, code); +} + +static inline void +nand_ecc_prepare(device_t self, int mode) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_dev != NULL); + + if (sc->nand_if->ecc_prepare != NULL) + sc->nand_if->ecc_prepare(sc->nand_dev, mode); +} + +#if 0 +static inline bool +nand_block_isbad(device_t self, off_t block) +{ + struct nand_softc *sc = device_private(self); + + KASSERT(sc->nand_if->block_isbad != NULL); + KASSERT(sc->nand_dev != NULL); + + return sc->nand_if->block_isbad(sc->nand_dev, block); +} +#endif + +/* Manufacturer IDs defined by JEDEC */ +enum { + NAND_MFR_UNKNOWN = 0x00, + NAND_MFR_AMD = 0x01, + NAND_MFR_FUJITSU = 0x04, + NAND_MFR_RENESAS = 0x07, + NAND_MFR_STMICRO = 0x20, + NAND_MFR_MICRON = 0x2c, + NAND_MFR_NATIONAL = 0x8f, + NAND_MFR_TOSHIBA = 0x98, + NAND_MFR_HYNIX = 0xad, + NAND_MFR_SAMSUNG = 0xec +}; + +struct nand_manufacturer { + int id; + const char *name; +}; + +extern const struct nand_manufacturer nand_mfrs[]; + +static inline void +nand_dump_data(const char *name, void *data, size_t len) +{ + printf("dumping %s\n--------------\n", name); + uint8_t *dump = data; + for (int i = 0; i < len; i++) { + printf("0x%.2hhx ", *dump); + dump++; + } + printf("\n--------------\n"); +} + +#endif /* _NAND_H_ */ diff --git a/sys/dev/nand/nand_bbt.c b/sys/dev/nand/nand_bbt.c new file mode 100644 index 00000000000..362a1d52378 --- /dev/null +++ b/sys/dev/nand/nand_bbt.c @@ -0,0 +1,250 @@ +/* $NetBSD: nand_bbt.c,v 1.1 2011/02/26 18:07:31 ahoka Exp $ */ + +/*- + * Copyright (c) 2011 Department of Software Engineering, + * University of Szeged, Hungary + * Copyright (c) 2011 Adam Hoka <ahoka@NetBSD.org> + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by the Department of Software Engineering, University of Szeged, Hungary + * + * 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. + */ + +/* Support for Bad Block Tables (BBTs) */ + +#include <sys/param.h> +#include <sys/kmem.h> + +#include "nand.h" +#include "nand_bbt.h" + +void +nand_bbt_init(device_t self) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + struct nand_bbt *bbt = &sc->sc_bbt; + + bbt->nbbt_size = chip->nc_size / chip->nc_block_size / 4; + bbt->nbbt_bitmap = kmem_alloc(bbt->nbbt_size, KM_SLEEP); + + memset(bbt->nbbt_bitmap, 0xff, bbt->nbbt_size); +} + +void +nand_bbt_detach(device_t self) +{ + struct nand_softc *sc = device_private(self); + struct nand_bbt *bbt = &sc->sc_bbt; + + printf("freeing bbt bitmap..."); + kmem_free(bbt->nbbt_bitmap, bbt->nbbt_size); + printf("done!\n"); +} + +void +nand_bbt_scan(device_t self) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + flash_addr_t i, blocks, addr; + + blocks = chip->nc_size / chip->nc_block_size; + + aprint_normal_dev(self, "scanning for bad blocks\n"); + + addr = 0; + for (i = 0; i < blocks; i++) { + if (nand_isfactorybad(self, addr)) { + nand_bbt_block_markfactorybad(self, i); + } else if (nand_iswornoutbad(self, addr)) { + nand_bbt_block_markbad(self, i); + } + + addr += chip->nc_block_size; + } +} + +bool +nand_bbt_update(device_t self) +{ + return true; +} + +static bool +nand_bbt_page_has_bbt(device_t self, flash_addr_t addr) { + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + uint8_t *oob = chip->nc_oob_cache; + + nand_read_oob(self, addr, oob); + + if (oob[NAND_BBT_OFFSET] == 'B' && + oob[NAND_BBT_OFFSET + 1] == 'b' && + oob[NAND_BBT_OFFSET + 2] == 't') { + return true; + } else { + return false; + } +} + +static bool +nand_bbt_get_bbt_from_page(device_t self, flash_addr_t addr) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + struct nand_bbt *bbt = &sc->sc_bbt; + uint8_t *bbtp, *buf = chip->nc_page_cache; + size_t left, bbt_pages, i; + + bbt_pages = bbt->nbbt_size / chip->nc_page_size; + if (bbt->nbbt_size % chip->nc_page_size) + bbt_pages++; + + if (nand_isbad(self, addr)) { + return false; + } + + if (nand_bbt_page_has_bbt(self, addr)) { + bbtp = bbt->nbbt_bitmap; + left = bbt->nbbt_size; + + for (i = 0; i < bbt_pages; i++) { + nand_read_page(self, addr, buf); + + if (i == bbt_pages - 1) { + KASSERT(left <= chip->nc_page_size); + memcpy(bbtp, buf, left); + } else { + memcpy(bbtp, buf, chip->nc_page_size); + } + + bbtp += chip->nc_page_size; + left -= chip->nc_page_size; + addr += chip->nc_page_size; + } + + return true; + } else { + return false; + } +} + +bool +nand_bbt_load(device_t self) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + flash_addr_t blockaddr; + int n; + + blockaddr = chip->nc_size - chip->nc_block_size; + /* XXX currently we check the last 4 blocks */ + for (n = 0; n < 4; n++) { + if (nand_bbt_get_bbt_from_page(self, blockaddr)) { + break; + } else { + blockaddr -= chip->nc_block_size; + } + } + + return true; +} + +void +nand_bbt_block_markbad(device_t self, flash_addr_t block) +{ + if (nand_bbt_block_isbad(self, block)) { + aprint_error_dev(self, + "trying to mark block bad already marked in bbt\n"); + } + /* XXX check if this is the correct marker */ + nand_bbt_block_mark(self, block, NAND_BBT_MARKER_WORNOUT_BAD); +} + +void +nand_bbt_block_markfactorybad(device_t self, flash_addr_t block) +{ + if (nand_bbt_block_isbad(self, block)) { + aprint_error_dev(self, + "trying to mark block factory bad already" + " marked in bbt\n"); + } + nand_bbt_block_mark(self, block, NAND_BBT_MARKER_FACTORY_BAD); +} + +void +nand_bbt_block_mark(device_t self, flash_addr_t block, uint8_t marker) +{ + struct nand_softc *sc = device_private(self); +#ifdef DIAGNOSTIC + struct nand_chip *chip = &sc->sc_chip; +#endif + struct nand_bbt *bbt = &sc->sc_bbt; + uint8_t clean; + + KASSERT(block < chip->nc_size / chip->nc_block_size); + + clean = (~0x03 << ((block % 4) * 2)); + marker = (marker << ((block % 4) * 2)); + + /* set byte containing the 2 bit marker for this block */ + bbt->nbbt_bitmap[block / 4] &= clean; + bbt->nbbt_bitmap[block / 4] |= marker; +} + +bool +nand_bbt_block_isbad(device_t self, flash_addr_t block) +{ + struct nand_softc *sc = device_private(self); +#ifdef DIAGNOSTIC + struct nand_chip *chip = &sc->sc_chip; +#endif + struct nand_bbt *bbt = &sc->sc_bbt; + uint8_t byte, marker; + bool result; + + KASSERT(block < chip->nc_size / chip->nc_block_size); + + /* get byte containing the 2 bit marker for this block */ + byte = bbt->nbbt_bitmap[block / 4]; + + /* extract the 2 bit marker from the byte */ + marker = (byte >> ((block % 4) * 2)) & 0x03; + + switch (marker) { + case NAND_BBT_MARKER_FACTORY_BAD: + case NAND_BBT_MARKER_WORNOUT_BAD: + case NAND_BBT_MARKER_RESERVED: + result = true; + break; + case NAND_BBT_MARKER_GOOD: + result = false; + break; + default: + panic("error in marker extraction"); + } + + return result; +} diff --git a/sys/dev/nand/nand_bbt.h b/sys/dev/nand/nand_bbt.h new file mode 100644 index 00000000000..dec73fff465 --- /dev/null +++ b/sys/dev/nand/nand_bbt.h @@ -0,0 +1,58 @@ +/* $NetBSD: nand_bbt.h,v 1.1 2011/02/26 18:07:31 ahoka Exp $ */ + +/*- + * Copyright (c) 2010 Department of Software Engineering, + * University of Szeged, Hungary + * Copyright (c) 2010 Adam Hoka <ahoka@NetBSD.org> + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by the Department of Software Engineering, University of Szeged, Hungary + * + * 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. + */ + +#ifndef _NAND_BBT_H_ +#define _NAND_BBT_H_ + +enum { + NAND_BBT_MARKER_FACTORY_BAD = 0x00, /* 00 */ + NAND_BBT_MARKER_WORNOUT_BAD = 0x01, /* 01 */ + NAND_BBT_MARKER_RESERVED = 0x02, /* 10 */ + NAND_BBT_MARKER_GOOD = 0x03 /* 11 */ +}; + +enum { + NAND_BBT_OFFSET = 0x0e +}; + +void nand_bbt_init(device_t); +void nand_bbt_detach(device_t); +void nand_bbt_scan(device_t); +bool nand_bbt_update(device_t); +bool nand_bbt_load(device_t); +void nand_bbt_block_mark(device_t, flash_addr_t, uint8_t); +void nand_bbt_block_markbad(device_t, flash_addr_t); +void nand_bbt_block_markfactorybad(device_t, flash_addr_t); +bool nand_bbt_block_isbad(device_t, flash_addr_t); + +#endif diff --git a/sys/dev/nand/nand_crc.c b/sys/dev/nand/nand_crc.c new file mode 100644 index 00000000000..490c07bc77c --- /dev/null +++ b/sys/dev/nand/nand_crc.c @@ -0,0 +1,63 @@ +/* $NetBSD: nand_crc.c,v 1.1 2011/02/26 18:07:31 ahoka Exp $ */ + +/*- + * Copyright (c) 2010 Department of Software Engineering, + * University of Szeged, Hungary + * Copyright (c) 2010 Adam Hoka <ahoka@NetBSD.org> + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by the Department of Software Engineering, University of Szeged, Hungary + * + * 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. + */ + +/* Implements CRC-16 as required by the ONFI 2.3 specification */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: nand_crc.c,v 1.1 2011/02/26 18:07:31 ahoka Exp $"); + +#include "nand_crc.h" + +uint16_t +nand_crc16(uint8_t *data, size_t len) +{ + const uint16_t init = 0x4f4e; + const uint16_t polynom = 0x8005; + const uint16_t highbit = 0x0001 << 15; + uint16_t crc = init; + size_t i; + int j; + + for (i = 0; i < len; i++) { + crc ^= data[i] << 8; + + for (j = 0; j < 8; j++) { + if ((crc & highbit) != 0x00) + crc = (crc << 1) ^ polynom; + else + crc <<= 1; + } + } + + return crc; +} diff --git a/sys/dev/nand/nand_crc.h b/sys/dev/nand/nand_crc.h new file mode 100644 index 00000000000..eea450c32eb --- /dev/null +++ b/sys/dev/nand/nand_crc.h @@ -0,0 +1,42 @@ +/* $NetBSD: nand_crc.h,v 1.1 2011/02/26 18:07:31 ahoka Exp $ */ + +/*- + * Copyright (c) 2010 Department of Software Engineering, + * University of Szeged, Hungary + * Copyright (c) 2010 Adam Hoka <ahoka@NetBSD.org> + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by the Department of Software Engineering, University of Szeged, Hungary + * + * 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. + */ + +#ifndef _NAND_CRC_H_ +#define _NAND_CRC_H_ + +#include <sys/param.h> +#include <sys/types.h> + +uint16_t nand_crc16(uint8_t *, size_t); + +#endif diff --git a/sys/dev/nand/nand_io.c b/sys/dev/nand/nand_io.c new file mode 100644 index 00000000000..b883943bfd4 --- /dev/null +++ b/sys/dev/nand/nand_io.c @@ -0,0 +1,373 @@ +/* $NetBSD: nand_io.c,v 1.1 2011/02/26 18:07:31 ahoka Exp $ */ + +/*- + * Copyright (c) 2011 Department of Software Engineering, + * University of Szeged, Hungary + * Copyright (c) 2011 Adam Hoka <ahoka@NetBSD.org> + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by the Department of Software Engineering, University of Szeged, Hungary + * + * 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. + */ + +/* Inspired by the similar code in the NetBSD SPI driver, but I + * decided to do a rewrite from scratch to be suitable for NAND. + */ + +#include <sys/param.h> +#include <sys/buf.h> +#include <sys/bufq.h> +#include <sys/kernel.h> +#include <sys/kmem.h> +#include <sys/kthread.h> +#include <sys/mutex.h> + +#include <dev/flash/flash.h> +#include "nand.h" + +extern int nanddebug; +extern int nand_cachesync_timeout; + +void nand_io_read(device_t, struct buf *); +void nand_io_write(device_t, struct buf *); +void nand_io_done(device_t, int error, struct buf *); +int nand_io_cache_write(device_t, daddr_t, struct buf *); +void nand_io_cache_sync(device_t); + +static int +nand_timestamp_diff(struct bintime *bt, struct bintime *b2) +{ + struct bintime b1 = *bt; + struct timeval tv; + + bintime_sub(&b1, b2); + bintime2timeval(&b1, &tv); + + return tvtohz(&tv); +} + +static daddr_t +nand_io_getblock(device_t self, struct buf *bp) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + flash_addr_t block, last; + + /* get block number of first byte */ + block = bp->b_rawblkno * DEV_BSIZE / chip->nc_block_size; + + /* block of the last bite */ + last = (bp->b_rawblkno * DEV_BSIZE + bp->b_resid - 1) + / chip->nc_block_size; + + /* spans trough multiple blocks, needs special handling */ + if (last != block) { + printf("0x%jx -> 0x%jx\n", + bp->b_rawblkno * DEV_BSIZE, + bp->b_rawblkno * DEV_BSIZE + bp->b_resid - 1); + panic("TODO: multiple block write. last: %jd, current: %jd", + (intmax_t )last, (intmax_t )block); + } + + return block; +} + +int +nand_sync_thread_start(device_t self) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + struct nand_write_cache *wc = &sc->sc_cache; + int error; + + DPRINTF(("starting nand io thread\n")); + + sc->sc_cache.nwc_data = kmem_alloc(chip->nc_block_size, KM_SLEEP); + + mutex_init(&sc->sc_io_lock, MUTEX_DEFAULT, IPL_NONE); + mutex_init(&wc->nwc_lock, MUTEX_DEFAULT, IPL_NONE); + cv_init(&sc->sc_io_cv, "nandcv"); + + error = bufq_alloc(&wc->nwc_bufq, "fcfs", BUFQ_SORT_RAWBLOCK); + if (error) + goto err_bufq; + + sc->sc_io_running = true; + wc->nwc_write_pending = false; + + /* arrange to allocate the kthread */ + error = kthread_create(PRI_NONE, KTHREAD_JOINABLE | KTHREAD_MPSAFE, + NULL, nand_sync_thread, self, &sc->sc_sync_thread, "nandio"); + + if (!error) + return 0; + + bufq_free(wc->nwc_bufq); +err_bufq: + cv_destroy(&sc->sc_io_cv); + + mutex_destroy(&sc->sc_io_lock); + mutex_destroy(&wc->nwc_lock); + + kmem_free(sc->sc_cache.nwc_data, chip->nc_block_size); + + return error; +} + +void +nand_sync_thread_stop(device_t self) +{ + struct nand_softc *sc = device_private(self); + struct nand_chip *chip = &sc->sc_chip; + struct nand_write_cache *wc = &sc->sc_cache; + + DPRINTF(("stopping nand io thread\n")); + + kmem_free(wc->nwc_data, chip->nc_block_size); + + sc->sc_io_running = false; + kthread_join(sc->sc_sync_thread); + + bufq_free(wc->nwc_bufq); + + mutex_destroy(&sc->sc_io_lock); + mutex_destroy(&sc->sc_waitq_lock); + mutex_destroy(&wc->nwc_lock); + + cv_destroy(&sc->sc_io_cv); +} + +int +nand_io_submit(device_t self, struct buf *bp) +{ + struct nand_softc *sc = device_private(self); + struct nand_write_cache *wc = &sc->sc_cache; + + DPRINTF(("submitting job to nand io thread: %p\n", bp)); + + if (BUF_ISREAD(bp)) { + DPRINTF(("we have a read job\n")); + + mutex_enter(&wc->nwc_lock); + if (wc->nwc_write_pending) + nand_io_cache_sync(self); + mutex_exit(&wc->nwc_lock); + + nand_io_read(self, bp); + } else { + DPRINTF(("we have a write job\n")); + + nand_io_write(self, bp); + } + + return 0; +} + +int +nand_io_cache_write(device_t self, daddr_t block, struct buf *bp) +{ + struct nand_softc *sc = device_private(self); + struct nand_write_cache *wc = &sc->sc_cache; + struct nand_chip *chip = &sc->sc_chip; + size_t retlen; + daddr_t base, offset; + int error; + + KASSERT(chip->nc_block_size != 0); + + base = block * chip->nc_block_size; + offset = bp->b_rawblkno * DEV_BSIZE - base; + + DPRINTF(("io cache write, offset: %jd\n", (intmax_t )offset)); + + if (!wc->nwc_write_pending) { + wc->nwc_block = block; + /* + * fill the cache with data from flash, + * so we dont have to bother with gaps later + */ + DPRINTF(("filling buffer from offset %ju\n", (uintmax_t)base)); + error = nand_flash_read(self, + base, chip->nc_block_size, + &retlen, wc->nwc_data); + DPRINTF(("cache filled\n")); + + if (error) + return error; + + wc->nwc_write_pending = true; + /* save creation time for aging */ + binuptime(&sc->sc_cache.nwc_creation); + } + /* copy data to cache */ + memcpy(wc->nwc_data + offset, bp->b_data, bp->b_resid); + bufq_put(wc->nwc_bufq, bp); + + /* update timestamp */ + binuptime(&wc->nwc_last_write); + + return 0; +} + +/* must be called with nwc_lock hold */ +void +nand_io_cache_sync(device_t self) +{ + struct nand_softc *sc = device_private(self); + struct nand_write_cache *wc = &sc->sc_cache; + struct nand_chip *chip = &sc->sc_chip; + struct flash_erase_instruction ei; + struct buf *bp; + size_t retlen; + daddr_t base; + int error; + + if (!wc->nwc_write_pending) { + DPRINTF(("trying to sync with an invalid buffer\n")); + return; + } + + base = wc->nwc_block * chip->nc_block_size; + + DPRINTF(("eraseing block at 0x%jx\n", (uintmax_t )base)); + ei.ei_addr = base; + ei.ei_len = chip->nc_block_size; + ei.ei_callback = NULL; + error = nand_flash_erase(self, &ei); + + if (error) { + aprint_error_dev(self, "cannot erase nand flash!\n"); + goto out; + } + + DPRINTF(("writing %zu bytes to 0x%jx\n", + chip->nc_block_size, (uintmax_t )base)); + + error = nand_flash_write(self, + base, chip->nc_block_size, &retlen, wc->nwc_data); + + if (error || retlen != chip->nc_block_size) { + aprint_error_dev(self, "can't sync write cache: %d\n", error); + goto out; + } + +out: + while ((bp = bufq_get(wc->nwc_bufq)) != NULL) + nand_io_done(self, error, bp); + + wc->nwc_block = -1; + wc->nwc_write_pending = false; +} + +void +nand_sync_thread(void * arg) +{ + device_t self = arg; + struct nand_softc *sc = device_private(self); + struct nand_write_cache *wc = &sc->sc_cache; + struct bintime now; + + /* sync thread waking in every seconds */ + while (sc->sc_io_running) { + mutex_enter(&sc->sc_io_lock); + cv_timedwait_sig(&sc->sc_io_cv, &sc->sc_io_lock, hz / 4); + mutex_exit(&sc->sc_io_lock); + + mutex_enter(&wc->nwc_lock); + + if (!wc->nwc_write_pending) { + mutex_exit(&wc->nwc_lock); + continue; + } + + /* see if the cache is older than 3 seconds (safety limit), + * or if we havent touched the cache since more than 1 ms + */ + binuptime(&now); + if (nand_timestamp_diff(&now, &wc->nwc_last_write) + > hz / 5 || + nand_timestamp_diff(&now, &wc->nwc_creation) + > 3 * hz) { + printf("syncing write cache after timeout\n"); + nand_io_cache_sync(self); + } + mutex_exit(&wc->nwc_lock); + } + + kthread_exit(0); +} + +void +nand_io_read(device_t self, struct buf *bp) +{ + size_t retlen; + daddr_t offset; + int error; + + DPRINTF(("nand io read\n")); + + offset = bp->b_rawblkno * DEV_BSIZE; + + error = nand_flash_read(self, offset, bp->b_resid, + &retlen, bp->b_data); + + nand_io_done(self, error, bp); +} + +void +nand_io_write(device_t self, struct buf *bp) +{ + struct nand_softc *sc = device_private(self); + struct nand_write_cache *wc = &sc->sc_cache; + daddr_t block; + + DPRINTF(("nand io write\n")); + + block = nand_io_getblock(self, bp); + DPRINTF(("write to block %jd\n", (intmax_t )block)); + + mutex_enter(&wc->nwc_lock); + + if (wc->nwc_write_pending && wc->nwc_block != block) { + DPRINTF(("writing to new block, syncing caches\n")); + nand_io_cache_sync(self); + } + + nand_io_cache_write(self, block, bp); + + mutex_exit(&wc->nwc_lock); +} + +void +nand_io_done(device_t self, int error, struct buf *bp) +{ + DPRINTF(("io done: %p\n", bp)); + + if (error == 0) + bp->b_resid = 0; + + bp->b_error = error; + + biodone(bp); +} diff --git a/sys/dev/nand/nandemulator.c b/sys/dev/nand/nandemulator.c new file mode 100644 index 00000000000..51bbb65a8e2 --- /dev/null +++ b/sys/dev/nand/nandemulator.c @@ -0,0 +1,779 @@ +/* $NetBSD: nandemulator.c,v 1.1 2011/02/26 18:07:31 ahoka Exp $ */ + +/*- + * Copyright (c) 2011 Department of Software Engineering, + * University of Szeged, Hungary + * Copyright (c) 2011 Adam Hoka <ahoka@NetBSD.org> + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by the Department of Software Engineering, University of Szeged, Hungary + * + * 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> +__KERNEL_RCSID(0, "$NetBSD: nandemulator.c,v 1.1 2011/02/26 18:07:31 ahoka Exp $"); + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/conf.h> +#include <sys/kmem.h> +#include <sys/kernel.h> + +#include "nandemulator.h" + +#include <dev/nand/nand.h> +#include <dev/nand/onfi.h> +#include <dev/nand/nand_crc.h> + +extern struct cfdriver nandemulator_cd; +void nandemulatorattach(int n); + +static int nandemulator_match(device_t, cfdata_t, void *); +static void nandemulator_attach(device_t, device_t, void *); +static int nandemulator_detach(device_t, int); + +static void nandemulator_device_reset(device_t); +static void nandemulator_command(device_t, uint8_t); +static void nandemulator_address(device_t, uint8_t); +static void nandemulator_busy(device_t); +static void nandemulator_read_byte(device_t, uint8_t *); +static void nandemulator_write_byte(device_t, uint8_t); +static void nandemulator_read_word(device_t, uint16_t *); +static void nandemulator_write_word(device_t, uint16_t); +static void nandemulator_read_buf_byte(device_t, void *, size_t); +static void nandemulator_read_buf_word(device_t, void *, size_t); +static void nandemulator_write_buf_byte(device_t, const void *, size_t); +static void nandemulator_write_buf_word(device_t, const void *, size_t); + +static size_t nandemulator_address_to_page(device_t); +static size_t nandemulator_page_to_backend_offset(device_t, size_t); +static size_t nandemulator_column_address_to_subpage(device_t); +/* +#define NANDEMULATOR_DEBUG 1 + +#ifdef NANDEMULATOR_DEBUG +#warning debug enabled +#define DPRINTF(x) if (nandemulatordebug) printf x +#define DPRINTFN(n,x) if (nandemulatordebug>(n)) printf x +#else +#error no debug +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#ifdef NANDEMULATOR_DEBUG +int nandemulatordebug = NANDEMULATOR_DEBUG; +#endif +*/ + +extern int nanddebug; + +enum { + NANDEMULATOR_8BIT, + NANDEMULATOR_16BIT +}; + +struct nandemulator_softc { + device_t sc_dev; + device_t sc_nanddev; + + int sc_buswidth; + + struct nand_interface sc_nand_if; + + uint8_t sc_command; + size_t sc_io_len; + uint8_t *sc_io_pointer; + uint64_t sc_address; + + uint8_t *sc_backend; + size_t sc_backend_size; + size_t sc_device_size; + bool sc_register_writable; + + uint8_t sc_status_register; + uint8_t sc_ids[2]; + uint8_t sc_onfi[4]; + + size_t sc_page_size; + size_t sc_block_size; + size_t sc_spare_size; + size_t sc_lun_size; + uint8_t sc_row_cycles; + uint8_t sc_column_cycles; + uint64_t sc_row_mask; + + int sc_address_counter; + + struct onfi_parameter_page *sc_parameter_page; +}; + +CFATTACH_DECL_NEW(nandemulator, sizeof(struct nandemulator_softc), + nandemulator_match, nandemulator_attach, nandemulator_detach, NULL); + +void +nandemulatorattach(int n) +{ + int i, err; + cfdata_t cf; + + aprint_debug("nandemulator: requested %d units\n", n); + + err = config_cfattach_attach(nandemulator_cd.cd_name, + &nandemulator_ca); + if (err) { + aprint_error("%s: couldn't register cfattach: %d\n", + nandemulator_cd.cd_name, err); + config_cfdriver_detach(&nandemulator_cd); + return; + } + for (i = 0; i < n; i++) { + cf = kmem_alloc(sizeof(struct cfdata), KM_NOSLEEP); + if (cf == NULL) { + aprint_error("%s: couldn't allocate cfdata\n", + nandemulator_cd.cd_name); + continue; + } + cf->cf_name = nandemulator_cd.cd_name; + cf->cf_atname = nandemulator_cd.cd_name; + cf->cf_unit = i; + cf->cf_fstate = FSTATE_STAR; + + (void)config_attach_pseudo(cf); + } +} + +/* ARGSUSED */ +static int +nandemulator_match(device_t parent, cfdata_t match, void *aux) +{ + /* pseudo device, always attaches */ + return 1; +} + +static void +nandemulator_attach(device_t parent, device_t self, void *aux) +{ + struct nandemulator_softc *sc = device_private(self); + int i; + + aprint_normal_dev(self, "NAND emulator\n"); + + sc->sc_dev = self; + + sc->sc_nand_if.select = &nand_default_select; + sc->sc_nand_if.command = &nandemulator_command; + sc->sc_nand_if.address = &nandemulator_address; + sc->sc_nand_if.read_buf_byte = &nandemulator_read_buf_byte; + sc->sc_nand_if.read_buf_word = &nandemulator_read_buf_word; + sc->sc_nand_if.read_byte = &nandemulator_read_byte; + sc->sc_nand_if.read_word = &nandemulator_read_word; + sc->sc_nand_if.write_buf_byte = &nandemulator_write_buf_byte; + sc->sc_nand_if.write_buf_word = &nandemulator_write_buf_word; + sc->sc_nand_if.write_byte = &nandemulator_write_byte; + sc->sc_nand_if.write_word = &nandemulator_write_word; + sc->sc_nand_if.busy = &nandemulator_busy; + + sc->sc_nand_if.ecc_compute = &nand_default_ecc_compute; + sc->sc_nand_if.ecc_correct = &nand_default_ecc_correct; + sc->sc_nand_if.ecc_prepare = NULL; + sc->sc_nand_if.ecc.necc_code_size = 3; + sc->sc_nand_if.ecc.necc_block_size = 256; + sc->sc_nand_if.ecc.necc_type = NAND_ECC_TYPE_SW; + + if (!pmf_device_register1(sc->sc_dev, NULL, NULL, NULL)) + aprint_error_dev(sc->sc_dev, + "couldn't establish power handler\n"); + + sc->sc_buswidth = NANDEMULATOR_16BIT; /* 16bit for now */ + + /* hardcode these now, make it configurable later */ + sc->sc_device_size = 32 * 1024 * 1024; /* 32MB */ + sc->sc_page_size = 2048; + sc->sc_block_size = 64; + sc->sc_lun_size = + sc->sc_device_size / (sc->sc_page_size * sc->sc_block_size); + KASSERT(sc->sc_device_size % + (sc->sc_page_size * sc->sc_block_size) == 0); + sc->sc_spare_size = 64; + + sc->sc_column_cycles = 2; + sc->sc_row_cycles = 3; + + /* init the emulator data structures */ + sc->sc_backend_size = + sc->sc_device_size + + sc->sc_device_size / sc->sc_page_size * sc->sc_spare_size; + + sc->sc_backend = kmem_alloc(sc->sc_backend_size, KM_SLEEP); + memset(sc->sc_backend, 0xff, sc->sc_backend_size); + + sc->sc_parameter_page = + kmem_zalloc(sizeof(struct onfi_parameter_page) * 4, KM_SLEEP); + + struct onfi_parameter_page *opp; + uint8_t sig[4] = { 'O', 'N', 'F', 'I' }; + + for (i = 0; i < 4; i++) { + opp = &sc->sc_parameter_page[i]; + + opp->param_signature = *(uint32_t *)sig; + opp->param_pagesize = sc->sc_page_size; + opp->param_blocksize = sc->sc_block_size; + opp->param_sparesize = sc->sc_spare_size; + opp->param_lunsize = sc->sc_lun_size; + opp->param_numluns = 1; + + opp->param_manufacturer_id = 0x00; + memcpy(opp->param_manufacturer, + "NETBSD", strlen("NETBSD")); + memcpy(opp->param_model, + "NANDEMULATOR", strlen("NANDEMULATOR")); + + opp->param_features = ONFI_FEATURE_16BIT; + + /* the lower 4 bits contain the row address cycles + * the upper 4 bits contain the column address cycles + */ + opp->param_addr_cycles = sc->sc_row_cycles; + opp->param_addr_cycles |= (sc->sc_column_cycles << 4); + + opp->param_integrity_crc = nand_crc16((uint8_t *)opp, 254); + } + + sc->sc_ids[0] = 0x00; + sc->sc_ids[1] = 0x00; + + sc->sc_onfi[0] = 'O'; + sc->sc_onfi[1] = 'N'; + sc->sc_onfi[2] = 'F'; + sc->sc_onfi[3] = 'I'; + + sc->sc_row_mask = 0x00; + for (i = 0; i < sc->sc_row_cycles; i++) { + sc->sc_row_mask <<= 8; + sc->sc_row_mask |= 0xff; + } + + nandemulator_device_reset(self); + + sc->sc_nanddev = nand_attach_mi(&sc->sc_nand_if, sc->sc_dev); +} + +static int +nandemulator_detach(device_t self, int flags) +{ + struct nandemulator_softc *sc = device_private(self); + int ret = 0; + + aprint_normal_dev(sc->sc_dev, "detaching emulator\n"); + + pmf_device_deregister(sc->sc_dev); + + if (sc->sc_nanddev != NULL) + ret = config_detach(sc->sc_nanddev, flags); + + kmem_free(sc->sc_backend, sc->sc_backend_size); + kmem_free(sc->sc_parameter_page, + sizeof(struct onfi_parameter_page) * 4); + + return ret; +} + +/** + * bring the emulated device to a known state + */ +static void +nandemulator_device_reset(device_t self) +{ + struct nandemulator_softc *sc = device_private(self); + + sc->sc_command = 0; + sc->sc_register_writable = false; + sc->sc_io_len = 0; + sc->sc_io_pointer = NULL; + sc->sc_address = 0; + sc->sc_address_counter = 0; + + sc->sc_status_register = ONFI_STATUS_RDY | ONFI_STATUS_WP; +} + +static void +nandemulator_address_chip(device_t self) +{ + struct nandemulator_softc *sc = device_private(self); + size_t page, offset; + + if (sc->sc_address_counter != + sc->sc_column_cycles + sc->sc_row_cycles) { + aprint_error_dev(self, "incorrect number of address cycles\n"); + aprint_error_dev(self, "cc: %d, rc: %d, ac: %d\n", + sc->sc_column_cycles, sc->sc_row_cycles, + sc->sc_address_counter); + } + + page = nandemulator_address_to_page(self); + offset = sc->sc_page_size * page; + + DPRINTF(("READ/PROGRAM; page: 0x%jx (row addr: 0x%jx)\n", + (uintmax_t )page, + (uintmax_t )offset)); + + if (offset >= sc->sc_device_size) { + aprint_error_dev(self, "address > device size!\n"); + sc->sc_io_len = 0; + } else { + size_t addr = + nandemulator_page_to_backend_offset(self, page); + size_t pageoff = + nandemulator_column_address_to_subpage(self); + + DPRINTF(("subpage: 0x%jx\n", (uintmax_t )pageoff)); + + KASSERT(pageoff < + sc->sc_page_size + sc->sc_spare_size); + KASSERT(addr < sc->sc_backend_size); + + sc->sc_io_pointer = sc->sc_backend + addr + pageoff; + sc->sc_io_len = + sc->sc_page_size + sc->sc_spare_size - pageoff; + } +} + +static void +nandemulator_command(device_t self, uint8_t command) +{ + struct nandemulator_softc *sc = device_private(self); + size_t offset, page; + + sc->sc_command = command; + sc->sc_register_writable = false; + + DPRINTF(("nandemulator command: 0x%hhx\n", command)); + + switch (command) { + case ONFI_READ_STATUS: + sc->sc_io_pointer = &sc->sc_status_register; + sc->sc_io_len = 1; + break; + case ONFI_RESET: + nandemulator_device_reset(self); + break; + case ONFI_PAGE_PROGRAM: + sc->sc_register_writable = true; + case ONFI_READ: + case ONFI_BLOCK_ERASE: + sc->sc_address_counter = 0; + case ONFI_READ_ID: + case ONFI_READ_PARAMETER_PAGE: + sc->sc_io_len = 0; + sc->sc_address = 0; + break; + case ONFI_PAGE_PROGRAM_START: + /* XXX the program should only happen here */ + break; + case ONFI_READ_START: + nandemulator_address_chip(self); + break; + case ONFI_BLOCK_ERASE_START: + page = nandemulator_address_to_page(self); + offset = sc->sc_page_size * page; + + KASSERT(offset % + (sc->sc_block_size * sc->sc_page_size) == 0); + + if (offset >= sc->sc_device_size) { + aprint_error_dev(self, "address > device size!\n"); + } else { + size_t addr = + nandemulator_page_to_backend_offset(self, page); + + size_t blocklen = + sc->sc_block_size * + (sc->sc_page_size + sc->sc_spare_size); + + KASSERT(addr < sc->sc_backend_size); + uint8_t *block = sc->sc_backend + addr; + + DPRINTF(("erasing block at 0x%jx\n", + (uintmax_t )offset)); + + memset(block, 0xff, blocklen); + } + sc->sc_io_len = 0; + break; + default: + aprint_error_dev(self, + "invalid nand command (0x%hhx)\n", command); + sc->sc_io_len = 0; + } +}; + +static void +nandemulator_address(device_t self, uint8_t address) +{ + struct nandemulator_softc *sc = device_private(self); + + /** + * we have to handle read id/parameter page here, + * as we can read right after giving the address. + */ + switch (sc->sc_command) { + case ONFI_READ_ID: + if (address == 0x00) { + sc->sc_io_len = 2; + sc->sc_io_pointer = sc->sc_ids; + } else if (address == 0x20) { + sc->sc_io_len = 4; + sc->sc_io_pointer = sc->sc_onfi; + } else { + sc->sc_io_len = 0; + } + break; + case ONFI_READ_PARAMETER_PAGE: + if (address == 0x00) { + sc->sc_io_len = sizeof(struct onfi_parameter_page) * 4; + sc->sc_io_pointer = (uint8_t *)sc->sc_parameter_page; + } else { + sc->sc_io_len = 0; + } + break; + case ONFI_PAGE_PROGRAM: + sc->sc_address <<= 8; + sc->sc_address |= address; + sc->sc_address_counter++; + + if (sc->sc_address_counter == + sc->sc_column_cycles + sc->sc_row_cycles) { + nandemulator_address_chip(self); + } + break; + default: + sc->sc_address <<= 8; + sc->sc_address |= address; + sc->sc_address_counter++; + } +}; + +static void +nandemulator_busy(device_t self) +{ +#ifdef NANDEMULATOR_DELAYS + struct nandemulator_softc *sc = device_private(self); + + /* do some delay depending on command */ + switch (sc->sc_command) { + case ONFI_PAGE_PROGRAM_START: + case ONFI_BLOCK_ERASE_START: + DELAY(10); + break; + case ONFI_READ_START: + default: + DELAY(1); + } +#endif +} + +static void +nandemulator_read_byte(device_t self, uint8_t *data) +{ + struct nandemulator_softc *sc = device_private(self); + + if (sc->sc_io_len > 0) { + *data = *sc->sc_io_pointer; + + sc->sc_io_pointer++; + sc->sc_io_len--; + } else { + aprint_error_dev(self, "reading byte from invalid location\n"); + *data = 0xff; + } +} + +static void +nandemulator_write_byte(device_t self, uint8_t data) +{ + struct nandemulator_softc *sc = device_private(self); + + if (!sc->sc_register_writable) { + aprint_error_dev(self, + "trying to write read only location without effect\n"); + return; + } + + if (sc->sc_io_len > 0) { + *sc->sc_io_pointer = data; + + sc->sc_io_pointer++; + sc->sc_io_len--; + } else { + aprint_error_dev(self, "write to invalid location\n"); + } +} + +static void +nandemulator_read_word(device_t self, uint16_t *data) +{ + struct nandemulator_softc *sc = device_private(self); + + if (sc->sc_buswidth != NANDEMULATOR_16BIT) { + aprint_error_dev(self, + "trying to read a word on an 8bit chip\n"); + return; + } + + if (sc->sc_io_len > 1) { + *data = *(uint16_t *)sc->sc_io_pointer; + + sc->sc_io_pointer += 2; + sc->sc_io_len -= 2; + } else { + aprint_error_dev(self, "reading word from invalid location\n"); + *data = 0xffff; + } +} + +static void +nandemulator_write_word(device_t self, uint16_t data) +{ + struct nandemulator_softc *sc = device_private(self); + + if (!sc->sc_register_writable) { + aprint_error_dev(self, + "trying to write read only location without effect\n"); + return; + } + + if (sc->sc_buswidth != NANDEMULATOR_16BIT) { + aprint_error_dev(self, + "trying to write a word to an 8bit chip"); + return; + } + + if (sc->sc_io_len > 1) { + *(uint16_t *)sc->sc_io_pointer = data; + + sc->sc_io_pointer += 2; + sc->sc_io_len -= 2; + } else { + aprint_error_dev(self, "writing to invalid location"); + } +} + +static void +nandemulator_read_buf_byte(device_t self, void *buf, size_t len) +{ + uint8_t *addr; + + KASSERT(buf != NULL); + KASSERT(len >= 1); + + addr = buf; + while (len > 0) { + nandemulator_read_byte(self, addr); + addr++, len--; + } +} + +static void +nandemulator_read_buf_word(device_t self, void *buf, size_t len) +{ + uint16_t *addr; + + KASSERT(buf != NULL); + KASSERT(len >= 2); + KASSERT(!(len & 0x01)); + + addr = buf; + len /= 2; + while (len > 0) { + nandemulator_read_word(self, addr); + addr++, len--; + } +} + +static void +nandemulator_write_buf_byte(device_t self, const void *buf, size_t len) +{ + const uint8_t *addr; + + KASSERT(buf != NULL); + KASSERT(len >= 1); + + addr = buf; + while (len > 0) { + nandemulator_write_byte(self, *addr); + addr++, len--; + } +} + +static void +nandemulator_write_buf_word(device_t self, const void *buf, size_t len) +{ + const uint16_t *addr; + + KASSERT(buf != NULL); + KASSERT(len >= 2); + KASSERT(!(len & 0x01)); + + addr = buf; + len /= 2; + while (len > 0) { + nandemulator_write_word(self, *addr); + addr++, len--; + } +} + +static size_t +nandemulator_address_to_page(device_t self) +{ + struct nandemulator_softc *sc = device_private(self); + uint64_t address, offset; + int i; + + address = htole64(sc->sc_address); + address &= sc->sc_row_mask; + + offset = 0; + for (i = 0; i < sc->sc_row_cycles; i++) { + offset <<= 8; + offset |= (address & 0xff); + address >>= 8; + } + + return le64toh(offset); +} + +static size_t +nandemulator_column_address_to_subpage(device_t self) +{ + struct nandemulator_softc *sc = device_private(self); + uint64_t address, offset; + int i; + + address = htole64(sc->sc_address); + address >>= (8 * sc->sc_row_cycles); + + offset = 0; + for (i = 0; i < sc->sc_column_cycles; i++) { + offset <<= 8; + offset |= (address & 0xff); + address >>= 8; + } + + if (sc->sc_buswidth == NANDEMULATOR_16BIT) + return (size_t )le64toh(offset << 1); + else + return (size_t )le64toh(offset); +} + +static size_t +nandemulator_page_to_backend_offset(device_t self, size_t page) +{ + struct nandemulator_softc *sc = device_private(self); + + return (sc->sc_page_size + sc->sc_spare_size) * page; +} + +#ifdef _MODULE + +MODULE(MODULE_CLASS_DRIVER, nandemulator, "nand"); + +static const struct cfiattrdata nandbuscf_iattrdata = { + "nandbus", 0, { { NULL, NULL, 0 }, } +}; +static const struct cfiattrdata * const nandemulator_attrs[] = { + &nandbuscf_iattrdata, NULL +}; + +CFDRIVER_DECL(nandemulator, DV_DULL, nandemulator_attrs); +extern struct cfattach nandemulator_ca; +static int nandemulatorloc[] = { -1, -1 }; + +static struct cfdata nandemulator_cfdata[] = { + { + .cf_name = "nandemulator", + .cf_atname = "nandemulator", + .cf_unit = 0, + .cf_fstate = FSTATE_STAR, + .cf_loc = nandemulatorloc, + .cf_flags = 0, + .cf_pspec = NULL, + }, + { NULL, NULL, 0, 0, NULL, 0, NULL } +}; + +static int +nandemulator_modcmd(modcmd_t cmd, void *arg) +{ + int error; + + switch (cmd) { + case MODULE_CMD_INIT: + error = config_cfdriver_attach(&nandemulator_cd); + if (error) { + return error; + } + + error = config_cfattach_attach(nandemulator_cd.cd_name, + &nandemulator_ca); + if (error) { + config_cfdriver_detach(&nandemulator_cd); + aprint_error("%s: unable to register cfattach\n", + nandemulator_cd.cd_name); + + return error; + } + + error = config_cfdata_attach(nandemulator_cfdata, 1); + if (error) { + config_cfattach_detach(nandemulator_cd.cd_name, + &nandemulator_ca); + config_cfdriver_detach(&nandemulator_cd); + aprint_error("%s: unable to register cfdata\n", + nandemulator_cd.cd_name); + + return error; + } + + (void)config_attach_pseudo(nandemulator_cfdata); + + return 0; + case MODULE_CMD_FINI: + error = config_cfdata_detach(nandemulator_cfdata); + if (error) { + return error; + } + + config_cfattach_detach(nandemulator_cd.cd_name, + &nandemulator_ca); + config_cfdriver_detach(&nandemulator_cd); + + return 0; + default: + return ENOTTY; + } +} + +#endif diff --git a/sys/dev/nand/nandemulator.h b/sys/dev/nand/nandemulator.h new file mode 100644 index 00000000000..155921e943e --- /dev/null +++ b/sys/dev/nand/nandemulator.h @@ -0,0 +1,5 @@ +#ifndef _NANDEMULATOR_H_ +#define _NANDEMULATOR_H_ +#else + +#endif diff --git a/sys/dev/nand/onfi.h b/sys/dev/nand/onfi.h new file mode 100644 index 00000000000..113edc7f629 --- /dev/null +++ b/sys/dev/nand/onfi.h @@ -0,0 +1,180 @@ +/* $NetBSD: onfi.h,v 1.1 2011/02/26 18:07:31 ahoka Exp $ */ + +/*- + * Copyright (c) 2010 Department of Software Engineering, + * University of Szeged, Hungary + * Copyright (c) 2010 Adam Hoka <ahoka@NetBSD.org> + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by the Department of Software Engineering, University of Szeged, Hungary + * + * 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. + */ + +#ifndef _ONFI_H_ +#define _ONFI_H_ + +/** + * ONFI 2.2, Section 5.1: Command Set + * + * Indented ones are 2nd or 3rd cycle commands. + */ + +enum { + ONFI_READ = 0x00, /* M */ + ONFI_READ_START = 0x30, /* M */ + ONFI_READ_INTERLEAVED = 0x32, /* O */ + ONFI_READ_COPYBACK = 0x35, /* O */ + ONFI_READ_CACHE_RANDOM = 0x31, /* O */ + + ONFI_CHANGE_READ_COLUMN = 0x05, /* M */ + ONFI_CHANGE_READ_COLUMN_ENHANCED = 0x06, /* O */ + ONFI_CHANGE_READ_COLUMN_START = 0xe0, /* M */ + + ONFI_READ_CACHE_SEQUENTIAL = 0x31, /* O */ + ONFI_READ_CACHE_END = 0x3f, /* O */ + + ONFI_BLOCK_ERASE = 0x60, /* M */ + ONFI_BLOCK_ERASE_START = 0xd0, /* M */ + ONFI_BLOCK_ERASE_INTERLEAVED = 0xd1, /* O */ + + ONFI_READ_STATUS = 0x70, /* M */ + ONFI_READ_STATUS_ENHANCED = 0x78, /* O */ + + ONFI_PAGE_PROGRAM = 0x80, /* M */ + ONFI_PAGE_PROGRAM_START = 0x10, /* M */ + ONFI_PAGE_PROGRAM_INTERLEAVED = 0x11, /* O */ + ONFI_PAGE_CACHE_PROGRAM = 0x15, /* O */ + + ONFI_COPYBACK_PROGRAM = 0x85, /* O */ + ONFI_COPYBACK_PROGRAM_START = 0x10, /* O */ + ONFI_COPYBACK_PROGRAM_INTERLEAVED = 0x11, /* O */ +/*- + * Small Data's first opcode may be 80h if the operation is a program only + * with no data output. For the last second cycle of a Small Data Move, + * it is a 10h command to confirm the Program or Copyback operation + */ + ONFI_SMALL_DATA_MOVE = 0x85, /* O */ + ONFI_SMALL_DATA_MOVE_START = 0x11, /* O */ + + ONFI_CHANGE_WRITE_COLUMN = 0x85, /* M */ + ONFI_CHANGE_ROW_ADDRESS = 0x85, /* O */ + + ONFI_READ_ID = 0x90, /* M */ + ONFI_READ_PARAMETER_PAGE = 0xec, /* M */ + ONFI_READ_UNIQUE_ID = 0xed, /* O */ + ONFI_GET_FEATURES = 0xee, /* O */ + ONFI_SET_FEATURES = 0xef, /* O */ + ONFI_RESET_LUN = 0xfa, /* O */ + ONFI_SYNCHRONOUS_RESET = 0xfc, /* O */ + ONFI_RESET = 0xff /* M */ +}; + +/** + * status codes from ONFI_READ_STATUS + */ +enum { + ONFI_STATUS_FAIL = (1<<0), + ONFI_STATUS_FAILC = (1<<1), + ONFI_STATUS_R = (1<<2), + ONFI_STATUS_CSP = (1<<3), + ONFI_STATUS_VSP = (1<<4), + ONFI_STATUS_ARDY = (1<<5), + ONFI_STATUS_RDY = (1<<6), + ONFI_STATUS_WP = (1<<7) +}; + +enum { + ONFI_FEATURE_16BIT = (1<<0), + ONFI_FEATURE_EXTENDED_PARAM = (1<<7) +}; + +/* 5.7.1. Parameter Page Data Structure Definition */ +struct onfi_parameter_page { + /* Revision information and features block */ + uint32_t param_signature; /* M: onfi signature ({'O','N','F','I'}) */ + uint16_t param_revision; /* M: revision number */ + uint16_t param_features; /* M: features supported */ + uint16_t param_optional_cmds; /* M: optional commands */ + uint16_t param_reserved_1; /* R: reserved */ + uint16_t param_extended_len; /* O: extended parameter page lenght */ + uint8_t param_num_param_pg; /* O: number of parameter pages */ + uint8_t param_reserved_2[17]; /* R: reserved */ + /* Manufacturer information block */ + uint8_t param_manufacturer[12]; /* M: device manufacturer (ASCII) */ + uint8_t param_model[20]; /* M: device model (ASCII) */ + uint8_t param_manufacturer_id; /* M: JEDEC ID of manufacturer */ + uint16_t param_date; /* O: date code (BCD) */ + uint8_t param_reserved_3[13]; /* R: reserved */ + /* Memory organization block */ + uint32_t param_pagesize; /* M: number of data bytes per page */ + uint16_t param_sparesize; /* M: number of spare bytes per page */ + uint32_t param_part_pagesize; /* O: obsolete */ + uint16_t param_part_sparesize; /* O: obsolete */ + uint32_t param_blocksize; /* M: number of pages per block */ + uint32_t param_lunsize; /* M: number of blocks per LUN */ + uint8_t param_numluns; /* M: number of LUNs */ + uint8_t param_addr_cycles; /* M: address cycles: + col: 4-7 (high), row: 0-3 (low) */ + uint8_t param_cellsize; /* M: number of bits per cell */ + uint16_t param_lun_maxbad; /* M: maximum badblocks per LUN */ + uint16_t param_block_endurance; /* M: block endurance */ + uint8_t param_guaranteed_blocks; /* M: guaranteed valid blocks at + begginning of target */ + uint16_t param_guaranteed_endurance; /* M: block endurance of + guranteed blocks */ + uint8_t param_programs_per_page; /* M: number of programs per page */ + uint8_t param_partial_programming_attr; /* O: obsolete */ + uint8_t param_ecc_correctable_bits; /* M: number of bits + ECC correctability */ + uint8_t param_interleaved_addr_bits; /* M: num of interleaved address + bits (only low half is valid) */ + uint8_t param_interleaved_op_attrs; /* O: obsolete */ + uint8_t param_reserved_4[13]; /* R: reserved */ + /* Electrical parameters block */ + uint8_t param_io_c_max; /* M: I/O pin capacitance, maximum */ + uint16_t param_async_timing_mode; /* M: async timing mode support */ + uint16_t param_async_progcache_timing_mode; /* O: obsolete */ + uint16_t param_t_prog; /* M: maximum page program time (us) */ + uint16_t param_t_bers; /* M: maximum block erase time (us) */ + uint16_t param_t_r; /* M: maximum page read time (us) */ + uint16_t param_ccs; /* M: minimum change column setup time (ns) */ + uint16_t param_sync_timing_mode; /* source sync timing mode support */ + uint8_t param_sync_features; /* M: source sync features */ + uint16_t param_clk_input_c; /* O: CLK input pin cap., typical */ + uint16_t param_io_c; /* O: I/O pin capacitance, typical */ + uint16_t param_input_c; /* O: input pin capacitance, typical */ + uint8_t param_input_c_max; /* M: input pin capacitance, maximum */ + uint8_t param_driver_strength; /* M: driver strength support */ + uint16_t param_t_r_interleaved; /* O: maximum interleaved + page read time (us) */ + uint16_t param_t_adl; /* O: program page register clear enhancement + tADL value (ns) */ + uint8_t param_reserved_5[8]; /* R: reserved */ + /* Vendor block */ + uint16_t param_vendor_revision; /* M: vendor specific rev number */ + uint8_t param_vendor_specific[88]; /* vendor specific information */ + uint16_t param_integrity_crc; /* M: integrity CRC */ +} __packed; + +#endif /* _ONFI_H_ */ |
