diff options
Diffstat (limited to 'sys/dev/nand')
-rw-r--r-- | sys/dev/nand/nand.c | 832 | ||||
-rw-r--r-- | sys/dev/nand/nand.h | 385 | ||||
-rw-r--r-- | sys/dev/nand/nand_bbt.c | 273 | ||||
-rw-r--r-- | sys/dev/nand/nand_cdev.c | 413 | ||||
-rw-r--r-- | sys/dev/nand/nand_dev.h | 90 | ||||
-rw-r--r-- | sys/dev/nand/nand_ecc_pos.h | 56 | ||||
-rw-r--r-- | sys/dev/nand/nand_generic.c | 1320 | ||||
-rw-r--r-- | sys/dev/nand/nand_geom.c | 414 | ||||
-rw-r--r-- | sys/dev/nand/nand_id.c | 60 | ||||
-rw-r--r-- | sys/dev/nand/nand_if.m | 168 | ||||
-rw-r--r-- | sys/dev/nand/nandbus.c | 530 | ||||
-rw-r--r-- | sys/dev/nand/nandbus.h | 49 | ||||
-rw-r--r-- | sys/dev/nand/nandbus_if.m | 100 | ||||
-rw-r--r-- | sys/dev/nand/nandsim.c | 665 | ||||
-rw-r--r-- | sys/dev/nand/nandsim.h | 175 | ||||
-rw-r--r-- | sys/dev/nand/nandsim_chip.c | 901 | ||||
-rw-r--r-- | sys/dev/nand/nandsim_chip.h | 159 | ||||
-rw-r--r-- | sys/dev/nand/nandsim_ctrl.c | 396 | ||||
-rw-r--r-- | sys/dev/nand/nandsim_log.c | 186 | ||||
-rw-r--r-- | sys/dev/nand/nandsim_log.h | 52 | ||||
-rw-r--r-- | sys/dev/nand/nandsim_swap.c | 389 | ||||
-rw-r--r-- | sys/dev/nand/nandsim_swap.h | 64 | ||||
-rw-r--r-- | sys/dev/nand/nfc_if.m | 165 | ||||
-rw-r--r-- | sys/dev/nand/nfc_mv.c | 236 |
24 files changed, 8078 insertions, 0 deletions
diff --git a/sys/dev/nand/nand.c b/sys/dev/nand/nand.c new file mode 100644 index 0000000..ad5bc40 --- /dev/null +++ b/sys/dev/nand/nand.c @@ -0,0 +1,832 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/callout.h> +#include <sys/sysctl.h> + +#include <dev/nand/nand.h> +#include <dev/nand/nandbus.h> +#include <dev/nand/nand_ecc_pos.h> +#include "nfc_if.h" +#include "nand_if.h" +#include "nandbus_if.h" +#include <machine/stdarg.h> + +#define NAND_RESET_DELAY 1000 /* tRST */ +#define NAND_ERASE_DELAY 3000 /* tBERS */ +#define NAND_PROG_DELAY 700 /* tPROG */ +#define NAND_READ_DELAY 50 /* tR */ + +#define BIT0(x) ((x) & 0x1) +#define BIT1(x) (BIT0(x >> 1)) +#define BIT2(x) (BIT0(x >> 2)) +#define BIT3(x) (BIT0(x >> 3)) +#define BIT4(x) (BIT0(x >> 4)) +#define BIT5(x) (BIT0(x >> 5)) +#define BIT6(x) (BIT0(x >> 6)) +#define BIT7(x) (BIT0(x >> 7)) + +#define SOFTECC_SIZE 256 +#define SOFTECC_BYTES 3 + +int nand_debug_flag = 0; +SYSCTL_INT(_debug, OID_AUTO, nand_debug, CTLFLAG_RW, &nand_debug_flag, 0, + "NAND subsystem debug flag"); + +static void +nand_tunable_init(void *arg) +{ + + TUNABLE_INT_FETCH("debug.nand", &nand_debug_flag); +} + +SYSINIT(nand_tunables, SI_SUB_VFS, SI_ORDER_ANY, nand_tunable_init, NULL); + +MALLOC_DEFINE(M_NAND, "NAND", "NAND dynamic data"); + +static void calculate_ecc(const uint8_t *, uint8_t *); +static int correct_ecc(uint8_t *, uint8_t *, uint8_t *); + +void +nand_debug(int level, const char *fmt, ...) +{ + va_list ap; + + if (!(nand_debug_flag & level)) + return; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf("\n"); +} + +void +nand_init(struct nand_softc *nand, device_t dev, int ecc_mode, + int ecc_bytes, int ecc_size, uint16_t *eccposition, char *cdev_name) +{ + + nand->ecc.eccmode = ecc_mode; + nand->chip_cdev_name = cdev_name; + + if (ecc_mode == NAND_ECC_SOFT) { + nand->ecc.eccbytes = SOFTECC_BYTES; + nand->ecc.eccsize = SOFTECC_SIZE; + } else if (ecc_mode != NAND_ECC_NONE) { + nand->ecc.eccbytes = ecc_bytes; + nand->ecc.eccsize = ecc_size; + if (eccposition) + nand->ecc.eccpositions = eccposition; + } +} + +void +nand_onfi_set_params(struct nand_chip *chip, struct onfi_params *params) +{ + struct chip_geom *cg; + + cg = &chip->chip_geom; + + init_chip_geom(cg, params->luns, params->blocks_per_lun, + params->pages_per_block, params->bytes_per_page, + params->spare_bytes_per_page); + chip->t_bers = params->t_bers; + chip->t_prog = params->t_prog; + chip->t_r = params->t_r; + chip->t_ccs = params->t_ccs; + + if (params->features & ONFI_FEAT_16BIT) + chip->flags |= NAND_16_BIT; +} + +void +nand_set_params(struct nand_chip *chip, struct nand_params *params) +{ + struct chip_geom *cg; + uint32_t blocks_per_chip; + + cg = &chip->chip_geom; + blocks_per_chip = (params->chip_size << 20) / + (params->page_size * params->pages_per_block); + + init_chip_geom(cg, 1, blocks_per_chip, + params->pages_per_block, params->page_size, + params->oob_size); + + chip->t_bers = NAND_ERASE_DELAY; + chip->t_prog = NAND_PROG_DELAY; + chip->t_r = NAND_READ_DELAY; + chip->t_ccs = 0; + + if (params->flags & NAND_16_BIT) + chip->flags |= NAND_16_BIT; +} + +int +nand_init_stat(struct nand_chip *chip) +{ + struct block_stat *blk_stat; + struct page_stat *pg_stat; + struct chip_geom *cg; + uint32_t blks, pgs; + + cg = &chip->chip_geom; + blks = cg->blks_per_lun * cg->luns; + blk_stat = malloc(sizeof(struct block_stat) * blks, M_NAND, + M_WAITOK | M_ZERO); + if (!blk_stat) + return (ENOMEM); + + pgs = blks * cg->pgs_per_blk; + pg_stat = malloc(sizeof(struct page_stat) * pgs, M_NAND, + M_WAITOK | M_ZERO); + if (!pg_stat) { + free(blk_stat, M_NAND); + return (ENOMEM); + } + + chip->blk_stat = blk_stat; + chip->pg_stat = pg_stat; + + return (0); +} + +void +nand_destroy_stat(struct nand_chip *chip) +{ + + free(chip->pg_stat, M_NAND); + free(chip->blk_stat, M_NAND); +} + +int +init_chip_geom(struct chip_geom *cg, uint32_t luns, uint32_t blks_per_lun, + uint32_t pgs_per_blk, uint32_t pg_size, uint32_t oob_size) +{ + int shift; + + if (!cg) + return (-1); + + cg->luns = luns; + cg->blks_per_lun = blks_per_lun; + cg->blks_per_chip = blks_per_lun * luns; + cg->pgs_per_blk = pgs_per_blk; + + cg->page_size = pg_size; + cg->oob_size = oob_size; + cg->block_size = cg->page_size * cg->pgs_per_blk; + cg->chip_size = cg->block_size * cg->blks_per_chip; + + shift = fls(cg->pgs_per_blk - 1); + cg->pg_mask = (1 << shift) - 1; + cg->blk_shift = shift; + + if (cg->blks_per_lun > 0) { + shift = fls(cg->blks_per_lun - 1); + cg->blk_mask = ((1 << shift) - 1) << cg->blk_shift; + } else { + shift = 0; + cg->blk_mask = 0; + } + + cg->lun_shift = shift + cg->blk_shift; + shift = fls(cg->luns - 1); + cg->lun_mask = ((1 << shift) - 1) << cg->lun_shift; + + nand_debug(NDBG_NAND, "Masks: lun 0x%x blk 0x%x page 0x%x\n" + "Shifts: lun %d blk %d", + cg->lun_mask, cg->blk_mask, cg->pg_mask, + cg->lun_shift, cg->blk_shift); + + return (0); +} + +int +nand_row_to_blkpg(struct chip_geom *cg, uint32_t row, uint32_t *lun, + uint32_t *blk, uint32_t *pg) +{ + + if (!cg || !lun || !blk || !pg) + return (-1); + + if (row & ~(cg->lun_mask | cg->blk_mask | cg->pg_mask)) { + nand_debug(NDBG_NAND,"Address out of bounds\n"); + return (-1); + } + + *lun = (row & cg->lun_mask) >> cg->lun_shift; + *blk = (row & cg->blk_mask) >> cg->blk_shift; + *pg = (row & cg->pg_mask); + + nand_debug(NDBG_NAND,"address %x-%x-%x\n", *lun, *blk, *pg); + + return (0); +} + +int page_to_row(struct chip_geom *cg, uint32_t page, uint32_t *row) +{ + uint32_t lun, block, pg_in_blk; + + if (!cg || !row) + return (-1); + + block = page / cg->pgs_per_blk; + pg_in_blk = page % cg->pgs_per_blk; + + lun = block / cg->blks_per_lun; + block = block % cg->blks_per_lun; + + *row = (lun << cg->lun_shift) & cg->lun_mask; + *row |= ((block << cg->blk_shift) & cg->blk_mask); + *row |= (pg_in_blk & cg->pg_mask); + + return (0); +} + +int +nand_check_page_boundary(struct nand_chip *chip, uint32_t page) +{ + struct chip_geom* cg; + + cg = &chip->chip_geom; + if (page >= (cg->pgs_per_blk * cg->blks_per_lun * cg->luns)) { + nand_debug(NDBG_GEN,"%s: page number too big %#x\n", + __func__, page); + return (1); + } + + return (0); +} + +void +nand_get_chip_param(struct nand_chip *chip, struct chip_param_io *param) +{ + struct chip_geom *cg; + + cg = &chip->chip_geom; + param->page_size = cg->page_size; + param->oob_size = cg->oob_size; + + param->blocks = cg->blks_per_lun * cg->luns; + param->pages_per_block = cg->pgs_per_blk; +} + +static uint16_t * +default_software_ecc_positions(struct nand_chip *chip) +{ + struct nand_ecc_data *eccd; + + eccd = &chip->nand->ecc; + + if (eccd->eccpositions) + return (eccd->eccpositions); + + switch (chip->chip_geom.oob_size) { + case 16: + return ((uint16_t *)&default_software_ecc_positions_16); + case 64: + return ((uint16_t *)&default_software_ecc_positions_64); + case 128: + return ((uint16_t *)&default_software_ecc_positions_128); + default: + return (NULL); /* No ecc bytes positions defs available */ + } + + return (NULL); +} + +static void +calculate_ecc(const uint8_t *buf, uint8_t *ecc) +{ + uint8_t p8, byte; + int i; + + memset(ecc, 0, 3); + + for (i = 0; i < 256; i++) { + byte = buf[i]; + ecc[0] ^= (BIT0(byte) ^ BIT2(byte) ^ BIT4(byte) ^ + BIT6(byte)) << 2; + ecc[0] ^= (BIT1(byte) ^ BIT3(byte) ^ BIT5(byte) ^ + BIT7(byte)) << 3; + ecc[0] ^= (BIT0(byte) ^ BIT1(byte) ^ BIT4(byte) ^ + BIT5(byte)) << 4; + ecc[0] ^= (BIT2(byte) ^ BIT3(byte) ^ BIT6(byte) ^ + BIT7(byte)) << 5; + ecc[0] ^= (BIT0(byte) ^ BIT1(byte) ^ BIT2(byte) ^ + BIT3(byte)) << 6; + ecc[0] ^= (BIT4(byte) ^ BIT5(byte) ^ BIT6(byte) ^ + BIT7(byte)) << 7; + + p8 = BIT0(byte) ^ BIT1(byte) ^ BIT2(byte) ^ + BIT3(byte) ^ BIT4(byte) ^ BIT5(byte) ^ BIT6(byte) ^ + BIT7(byte); + + if (p8) { + ecc[2] ^= (0x1 << BIT0(i)); + ecc[2] ^= (0x4 << BIT1(i)); + ecc[2] ^= (0x10 << BIT2(i)); + ecc[2] ^= (0x40 << BIT3(i)); + + ecc[1] ^= (0x1 << BIT4(i)); + ecc[1] ^= (0x4 << BIT5(i)); + ecc[1] ^= (0x10 << BIT6(i)); + ecc[1] ^= (0x40 << BIT7(i)); + } + } + ecc[0] = ~ecc[0]; + ecc[1] = ~ecc[1]; + ecc[2] = ~ecc[2]; + ecc[0] |= 3; +} + +static int +correct_ecc(uint8_t *buf, uint8_t *calc_ecc, uint8_t *read_ecc) +{ + uint8_t ecc0, ecc1, ecc2, onesnum, bit, byte; + uint16_t addr = 0; + + ecc0 = calc_ecc[0] ^ read_ecc[0]; + ecc1 = calc_ecc[1] ^ read_ecc[1]; + ecc2 = calc_ecc[2] ^ read_ecc[2]; + + if (!ecc0 && !ecc1 && !ecc2) + return (ECC_OK); + + addr = BIT3(ecc0) | (BIT5(ecc0) << 1) | (BIT7(ecc0) << 2); + addr |= (BIT1(ecc2) << 3) | (BIT3(ecc2) << 4) | + (BIT5(ecc2) << 5) | (BIT7(ecc2) << 6); + addr |= (BIT1(ecc1) << 7) | (BIT3(ecc1) << 8) | + (BIT5(ecc1) << 9) | (BIT7(ecc1) << 10); + + onesnum = 0; + while (ecc0 || ecc1 || ecc2) { + if (ecc0 & 1) + onesnum++; + if (ecc1 & 1) + onesnum++; + if (ecc2 & 1) + onesnum++; + + ecc0 >>= 1; + ecc1 >>= 1; + ecc2 >>= 1; + } + + if (onesnum == 11) { + /* Correctable error */ + bit = addr & 7; + byte = addr >> 3; + buf[byte] ^= (1 << bit); + return (ECC_CORRECTABLE); + } else if (onesnum == 1) { + /* ECC error */ + return (ECC_ERROR_ECC); + } else { + /* Uncorrectable error */ + return (ECC_UNCORRECTABLE); + } + + return (0); +} + +int +nand_softecc_get(device_t dev, uint8_t *buf, int pagesize, uint8_t *ecc) +{ + int steps = pagesize / SOFTECC_SIZE; + int i = 0, j = 0; + + for (; i < (steps * SOFTECC_BYTES); + i += SOFTECC_BYTES, j += SOFTECC_SIZE) { + calculate_ecc(&buf[j], &ecc[i]); + } + + return (0); +} + +int +nand_softecc_correct(device_t dev, uint8_t *buf, int pagesize, + uint8_t *readecc, uint8_t *calcecc) +{ + int steps = pagesize / SOFTECC_SIZE; + int i = 0, j = 0, ret = 0; + + for (i = 0; i < (steps * SOFTECC_BYTES); + i += SOFTECC_BYTES, j += SOFTECC_SIZE) { + ret += correct_ecc(&buf[j], &calcecc[i], &readecc[i]); + if (ret < 0) + return (ret); + } + + return (ret); +} + +static int +offset_to_page(struct chip_geom *cg, uint32_t offset) +{ + + return (offset / cg->page_size); +} + +int +nand_read_pages(struct nand_chip *chip, uint32_t offset, void *buf, + uint32_t len) +{ + struct chip_geom *cg; + struct nand_ecc_data *eccd; + struct page_stat *pg_stat; + device_t nandbus; + void *oob = NULL; + uint8_t *ptr; + uint16_t *eccpos = NULL; + uint32_t page, num, steps = 0; + int i, retval = 0, needwrite; + + nand_debug(NDBG_NAND,"%p read page %x[%x]", chip, offset, len); + cg = &chip->chip_geom; + eccd = &chip->nand->ecc; + page = offset_to_page(cg, offset); + num = len / cg->page_size; + + if (eccd->eccmode != NAND_ECC_NONE) { + steps = cg->page_size / eccd->eccsize; + eccpos = default_software_ecc_positions(chip); + oob = malloc(cg->oob_size, M_NAND, M_WAITOK); + } + + nandbus = device_get_parent(chip->dev); + NANDBUS_LOCK(nandbus); + NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num); + + ptr = (uint8_t *)buf; + while (num--) { + pg_stat = &(chip->pg_stat[page]); + + if (NAND_READ_PAGE(chip->dev, page, ptr, cg->page_size, 0)) { + retval = ENXIO; + break; + } + + if (eccd->eccmode != NAND_ECC_NONE) { + if (NAND_GET_ECC(chip->dev, ptr, eccd->ecccalculated, + &needwrite)) { + retval = ENXIO; + break; + } + nand_debug(NDBG_ECC,"%s: ECC calculated:", + __func__); + if (nand_debug_flag & NDBG_ECC) + for (i = 0; i < (eccd->eccbytes * steps); i++) + printf("%x ", eccd->ecccalculated[i]); + + nand_debug(NDBG_ECC,"\n"); + + if (NAND_READ_OOB(chip->dev, page, oob, cg->oob_size, + 0)) { + retval = ENXIO; + break; + } + for (i = 0; i < (eccd->eccbytes * steps); i++) + eccd->eccread[i] = ((uint8_t *)oob)[eccpos[i]]; + + nand_debug(NDBG_ECC,"%s: ECC read:", __func__); + if (nand_debug_flag & NDBG_ECC) + for (i = 0; i < (eccd->eccbytes * steps); i++) + printf("%x ", eccd->eccread[i]); + nand_debug(NDBG_ECC,"\n"); + + retval = NAND_CORRECT_ECC(chip->dev, ptr, eccd->eccread, + eccd->ecccalculated); + + nand_debug(NDBG_ECC, "NAND_CORRECT_ECC() returned %d", + retval); + + if (retval == 0) + pg_stat->ecc_stat.ecc_succeded++; + else if (retval > 0) { + pg_stat->ecc_stat.ecc_corrected += retval; + retval = ECC_CORRECTABLE; + } else { + pg_stat->ecc_stat.ecc_failed++; + break; + } + } + + pg_stat->page_read++; + page++; + ptr += cg->page_size; + } + + NANDBUS_UNLOCK(nandbus); + + if (oob) + free(oob, M_NAND); + + return (retval); +} + +int +nand_read_pages_raw(struct nand_chip *chip, uint32_t offset, void *buf, + uint32_t len) +{ + struct chip_geom *cg; + device_t nandbus; + uint8_t *ptr; + uint32_t page, num, end, begin = 0, begin_off; + int retval = 0; + + cg = &chip->chip_geom; + page = offset_to_page(cg, offset); + begin_off = offset - page * cg->page_size; + if (begin_off) { + begin = cg->page_size - begin_off; + len -= begin; + } + num = len / cg->page_size; + end = len % cg->page_size; + + nandbus = device_get_parent(chip->dev); + NANDBUS_LOCK(nandbus); + NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num); + + ptr = (uint8_t *)buf; + if (begin_off) { + if (NAND_READ_PAGE(chip->dev, page, ptr, begin, begin_off)) { + NANDBUS_UNLOCK(nandbus); + return (ENXIO); + } + + page++; + ptr += begin; + } + + while (num--) { + if (NAND_READ_PAGE(chip->dev, page, ptr, cg->page_size, 0)) { + NANDBUS_UNLOCK(nandbus); + return (ENXIO); + } + + page++; + ptr += cg->page_size; + } + + if (end) + if (NAND_READ_PAGE(chip->dev, page, ptr, end, 0)) { + NANDBUS_UNLOCK(nandbus); + return (ENXIO); + } + + NANDBUS_UNLOCK(nandbus); + + return (retval); +} + + +int +nand_prog_pages(struct nand_chip *chip, uint32_t offset, uint8_t *buf, + uint32_t len) +{ + struct chip_geom *cg; + struct page_stat *pg_stat; + struct nand_ecc_data *eccd; + device_t nandbus; + uint32_t page, num; + uint8_t *oob = NULL; + uint16_t *eccpos = NULL; + int steps = 0, i, needwrite, err = 0; + + nand_debug(NDBG_NAND,"%p prog page %x[%x]", chip, offset, len); + + eccd = &chip->nand->ecc; + cg = &chip->chip_geom; + page = offset_to_page(cg, offset); + num = len / cg->page_size; + + if (eccd->eccmode != NAND_ECC_NONE) { + steps = cg->page_size / eccd->eccsize; + oob = malloc(cg->oob_size, M_NAND, M_WAITOK); + eccpos = default_software_ecc_positions(chip); + } + + nandbus = device_get_parent(chip->dev); + NANDBUS_LOCK(nandbus); + NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num); + + while (num--) { + if (NAND_PROGRAM_PAGE(chip->dev, page, buf, cg->page_size, 0)) { + err = ENXIO; + break; + } + + if (eccd->eccmode != NAND_ECC_NONE) { + if (NAND_GET_ECC(chip->dev, buf, &eccd->ecccalculated, + &needwrite)) { + err = ENXIO; + break; + } + nand_debug(NDBG_ECC,"ECC calculated:"); + if (nand_debug_flag & NDBG_ECC) + for (i = 0; i < (eccd->eccbytes * steps); i++) + printf("%x ", eccd->ecccalculated[i]); + + nand_debug(NDBG_ECC,"\n"); + + if (needwrite) { + if (NAND_READ_OOB(chip->dev, page, oob, cg->oob_size, + 0)) { + err = ENXIO; + break; + } + + for (i = 0; i < (eccd->eccbytes * steps); i++) + oob[eccpos[i]] = eccd->ecccalculated[i]; + + if (NAND_PROGRAM_OOB(chip->dev, page, oob, + cg->oob_size, 0)) { + err = ENXIO; + break; + } + } + } + + pg_stat = &(chip->pg_stat[page]); + pg_stat->page_written++; + + page++; + buf += cg->page_size; + } + + NANDBUS_UNLOCK(nandbus); + + if (oob) + free(oob, M_NAND); + + return (err); +} + +int +nand_prog_pages_raw(struct nand_chip *chip, uint32_t offset, void *buf, + uint32_t len) +{ + struct chip_geom *cg; + device_t nandbus; + uint8_t *ptr; + uint32_t page, num, end, begin = 0, begin_off; + int retval = 0; + + cg = &chip->chip_geom; + page = offset_to_page(cg, offset); + begin_off = offset - page * cg->page_size; + if (begin_off) { + begin = cg->page_size - begin_off; + len -= begin; + } + num = len / cg->page_size; + end = len % cg->page_size; + + nandbus = device_get_parent(chip->dev); + NANDBUS_LOCK(nandbus); + NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num); + + ptr = (uint8_t *)buf; + if (begin_off) { + if (NAND_PROGRAM_PAGE(chip->dev, page, ptr, begin, begin_off)) { + NANDBUS_UNLOCK(nandbus); + return (ENXIO); + } + + page++; + ptr += begin; + } + + while (num--) { + if (NAND_PROGRAM_PAGE(chip->dev, page, ptr, cg->page_size, 0)) { + NANDBUS_UNLOCK(nandbus); + return (ENXIO); + } + + page++; + ptr += cg->page_size; + } + + if (end) + retval = NAND_PROGRAM_PAGE(chip->dev, page, ptr, end, 0); + + NANDBUS_UNLOCK(nandbus); + + return (retval); +} + +int +nand_read_oob(struct nand_chip *chip, uint32_t page, void *buf, + uint32_t len) +{ + device_t nandbus; + int retval = 0; + + nandbus = device_get_parent(chip->dev); + NANDBUS_LOCK(nandbus); + NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num); + + retval = NAND_READ_OOB(chip->dev, page, buf, len, 0); + + NANDBUS_UNLOCK(nandbus); + + return (retval); +} + + +int +nand_prog_oob(struct nand_chip *chip, uint32_t page, void *buf, + uint32_t len) +{ + device_t nandbus; + int retval = 0; + + nandbus = device_get_parent(chip->dev); + NANDBUS_LOCK(nandbus); + NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num); + + retval = NAND_PROGRAM_OOB(chip->dev, page, buf, len, 0); + + NANDBUS_UNLOCK(nandbus); + + return (retval); +} + +int +nand_erase_blocks(struct nand_chip *chip, off_t offset, size_t len) +{ + device_t nandbus; + struct chip_geom *cg; + uint32_t block, num_blocks; + int err = 0; + + cg = &chip->chip_geom; + if ((offset % cg->block_size) || (len % cg->block_size)) + return (EINVAL); + + block = offset / cg->block_size; + num_blocks = len / cg->block_size; + nand_debug(NDBG_NAND,"%p erase blocks %d[%d]", chip, block, num_blocks); + + nandbus = device_get_parent(chip->dev); + NANDBUS_LOCK(nandbus); + NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num); + + while (num_blocks--) { + if (!nand_check_bad_block(chip, block)) { + if (NAND_ERASE_BLOCK(chip->dev, block)) { + nand_debug(NDBG_NAND,"%p erase blocks %d error", + chip, block); + nand_mark_bad_block(chip, block); + err = ENXIO; + } + } else + err = ENXIO; + + block++; + }; + + NANDBUS_UNLOCK(nandbus); + + if (err) + nand_update_bbt(chip); + + return (err); +} diff --git a/sys/dev/nand/nand.h b/sys/dev/nand/nand.h new file mode 100644 index 0000000..05101ac --- /dev/null +++ b/sys/dev/nand/nand.h @@ -0,0 +1,385 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +#ifndef _DEV_NAND_H_ +#define _DEV_NAND_H_ + +#include <sys/bus.h> +#include <sys/param.h> +#include <sys/lock.h> +#include <sys/sx.h> +#include <sys/taskqueue.h> +#include <sys/queue.h> +#include <sys/bio.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/malloc.h> + +#include <dev/nand/nand_dev.h> + +MALLOC_DECLARE(M_NAND); + +/* Read commands */ +#define NAND_CMD_READ 0x00 +#define NAND_CMD_CHNG_READ_COL 0x05 +#define NAND_CMD_READ_END 0x30 +#define NAND_CMD_READ_CACHE 0x31 +#define NAND_CMD_READ_CPBK 0x35 +#define NAND_CMD_READ_CACHE_END 0x3F +#define NAND_CMD_CHNG_READ_COL_END 0xE0 + +/* Erase commands */ +#define NAND_CMD_ERASE 0x60 +#define NAND_CMD_ERASE_END 0xD0 +#define NAND_CMD_ERASE_INTLV 0xD1 + +/* Program commands */ +#define NAND_CMD_PROG 0x80 +#define NAND_CMD_CHNG_WRITE_COL 0x85 +#define NAND_CMD_PROG_END 0x10 +#define NAND_CMD_PROG_INTLV 0x11 +#define NAND_CMD_PROG_CACHE 0x15 + +/* Misc commands */ +#define NAND_CMD_STATUS 0x70 +#define NAND_CMD_STATUS_ENH 0x78 +#define NAND_CMD_READ_ID 0x90 +#define NAND_CMD_READ_PARAMETER 0xec +#define NAND_CMD_READ_UNIQUE_ID 0xed +#define NAND_CMD_GET_FEATURE 0xee +#define NAND_CMD_SET_FEATURE 0xef + +/* Reset commands */ +#define NAND_CMD_SYNCH_RESET 0xfc +#define NAND_CMD_RESET 0xff + +/* Small page flash commands */ +#define NAND_CMD_SMALLA 0x00 +#define NAND_CMD_SMALLB 0x01 +#define NAND_CMD_SMALLOOB 0x50 + +#define NAND_STATUS_FAIL 0x1 +#define NAND_STATUS_FAILC 0x2 +#define NAND_STATUS_ARDY 0x20 +#define NAND_STATUS_RDY 0x40 +#define NAND_STATUS_WP 0x80 + +#define NAND_LP_OOB_COLUMN_START 0x800 +#define NAND_LP_OOBSZ 0x40 +#define NAND_SP_OOB_COLUMN_START 0x200 +#define NAND_SP_OOBSZ 0x10 + +#define PAGE_PARAM_LENGTH 0x100 +#define PAGE_PARAMETER_DEF 0x0 +#define PAGE_PARAMETER_RED_1 0x100 +#define PAGE_PARAMETER_RED_2 0x200 + +#define ONFI_SIG_ADDR 0x20 + +#define NAND_MAX_CHIPS 0x4 +#define NAND_MAX_OOBSZ 512 +#define NAND_MAX_PAGESZ 16384 + +#define NAND_SMALL_PAGE_SIZE 0x200 + +#define NAND_16_BIT 0x00000001 + +#define NAND_ECC_NONE 0x0 +#define NAND_ECC_SOFT 0x1 +#define NAND_ECC_FULLHW 0x2 +#define NAND_ECC_PARTHW 0x4 +#define NAND_ECC_MODE_MASK 0x7 + +#define ECC_OK 0 +#define ECC_CORRECTABLE 1 +#define ECC_ERROR_ECC (-1) +#define ECC_UNCORRECTABLE (-2) + +#define NAND_MAN_SAMSUNG 0xec +#define NAND_MAN_HYNIX 0xad +#define NAND_MAN_STMICRO 0x20 + +struct nand_id { + uint8_t man_id; + uint8_t dev_id; +}; + +struct nand_params { + struct nand_id id; + char *name; + uint32_t chip_size; + uint32_t page_size; + uint32_t oob_size; + uint32_t pages_per_block; + uint32_t flags; +}; + +/* nand debug levels */ +#define NDBG_NAND 0x01 +#define NDBG_CDEV 0x02 +#define NDBG_GEN 0x04 +#define NDBG_GEOM 0x08 +#define NDBG_BUS 0x10 +#define NDBG_SIM 0x20 +#define NDBG_CTRL 0x40 +#define NDBG_DRV 0x80 +#define NDBG_ECC 0x100 + +/* nand_debug_function */ +void nand_debug(int level, const char *fmt, ...); +extern int nand_debug_flag; + +/* ONFI features bit*/ +#define ONFI_FEAT_16BIT 0x01 +#define ONFI_FEAT_MULT_LUN 0x02 +#define ONFI_FEAT_INTLV_OPS 0x04 +#define ONFI_FEAT_CPBK_RESTRICT 0x08 +#define ONFI_FEAT_SRC_SYNCH 0x10 + +/* ONFI optional commands bits */ +#define ONFI_OPTCOM_PROG_CACHE 0x01 +#define ONFI_OPTCOM_READ_CACHE 0x02 +#define ONFI_OPTCOM_GETSET_FEAT 0x04 +#define ONFI_OPTCOM_STATUS_ENH 0x08 +#define ONFI_OPTCOM_COPYBACK 0x10 +#define ONFI_OPTCOM_UNIQUE_ID 0x20 + + +/* Layout of parameter page is defined in ONFI */ +struct onfi_params { + char signature[4]; + uint16_t rev; + uint16_t features; + uint16_t optional_commands; + uint8_t res1[22]; + char manufacturer_name[12]; + char device_model[20]; + uint8_t manufacturer_id; + uint16_t date; + uint8_t res2[13]; + uint32_t bytes_per_page; + uint16_t spare_bytes_per_page; + uint32_t bytes_per_partial_page; + uint16_t spare_bytes_per_partial_page; + uint32_t pages_per_block; + uint32_t blocks_per_lun; + uint8_t luns; + uint8_t address_cycles; + uint8_t bits_per_cell; + uint16_t max_bad_block_per_lun; + uint16_t block_endurance; + uint8_t guaranteed_valid_blocks; + uint16_t valid_block_endurance; + uint8_t programs_per_page; + uint8_t partial_prog_attr; + uint8_t bits_of_ecc; + uint8_t interleaved_addr_bits; + uint8_t interleaved_oper_attr; + uint8_t res3[13]; + uint8_t pin_capacitance; + uint16_t asynch_timing_mode_support; + uint16_t asynch_prog_cache_timing_mode_support; + uint16_t t_prog; /* us, max page program time */ + uint16_t t_bers; /* us, max block erase time */ + uint16_t t_r; /* us, max page read time */ + uint16_t t_ccs; /* ns, min change column setup time */ + uint16_t source_synch_timing_mode_support; + uint8_t source_synch_feat; + uint16_t clk_input_capacitance; + uint16_t io_capacitance; + uint16_t input_capacitance; + uint8_t input_capacitance_max; + uint8_t driver_strength_support; + uint8_t res4[12]; + uint16_t vendor_rev; + uint8_t vendor_spec[8]; + uint16_t crc; +}; + +struct nand_ecc_data { + int eccsize; /* Number of data bytes per ECC step */ + int eccmode; + int eccbytes; /* Number of ECC bytes per step */ + + uint16_t *eccpositions; /* Positions of ecc bytes */ + uint8_t ecccalculated[NAND_MAX_OOBSZ]; + uint8_t eccread[NAND_MAX_OOBSZ]; +}; + +struct ecc_stat { + uint32_t ecc_succeded; + uint32_t ecc_corrected; + uint32_t ecc_failed; +}; + +struct page_stat { + struct ecc_stat ecc_stat; + uint32_t page_read; + uint32_t page_raw_read; + uint32_t page_written; + uint32_t page_raw_written; +}; + +struct block_stat { + uint32_t block_erased; +}; + +struct chip_geom { + uint32_t chip_size; + uint32_t block_size; + uint32_t page_size; + uint32_t oob_size; + + uint32_t luns; + uint32_t blks_per_lun; + uint32_t blks_per_chip; + uint32_t pgs_per_blk; + + uint32_t pg_mask; + uint32_t blk_mask; + uint32_t lun_mask; + uint8_t blk_shift; + uint8_t lun_shift; +}; + +struct nand_chip { + device_t dev; + struct nand_id id; + struct chip_geom chip_geom; + + uint16_t t_prog; /* us, max page program time */ + uint16_t t_bers; /* us, max block erase time */ + uint16_t t_r; /* us, max page read time */ + uint16_t t_ccs; /* ns, min change column setup time */ + uint8_t num; + uint8_t flags; + + struct page_stat *pg_stat; + struct block_stat *blk_stat; + struct nand_softc *nand; + struct nand_bbt *bbt; + struct nand_ops *ops; + struct cdev *cdev; + + struct disk *ndisk; + struct disk *rdisk; + struct bio_queue_head bioq; /* bio queue */ + struct mtx qlock; /* bioq lock */ + struct taskqueue *tq; /* private task queue for i/o request */ + struct task iotask; /* i/o processing */ + +}; + +struct nand_softc { + uint8_t flags; + + char *chip_cdev_name; + struct nand_ecc_data ecc; +}; + +/* NAND ops */ +int nand_erase_blocks(struct nand_chip *chip, off_t offset, size_t len); +int nand_prog_pages(struct nand_chip *chip, uint32_t offset, uint8_t *buf, + uint32_t len); +int nand_read_pages(struct nand_chip *chip, uint32_t offset, void *buf, + uint32_t len); +int nand_read_pages_raw(struct nand_chip *chip, uint32_t offset, void *buf, + uint32_t len); +int nand_prog_pages_raw(struct nand_chip *chip, uint32_t offset, void *buf, + uint32_t len); +int nand_read_oob(struct nand_chip *chip, uint32_t page, void *buf, + uint32_t len); +int nand_prog_oob(struct nand_chip *chip, uint32_t page, void *buf, + uint32_t len); + +int nand_select_cs(device_t dev, uint8_t cs); + +int nand_read_parameter(struct nand_softc *nand, struct onfi_params *param); +int nand_synch_reset(struct nand_softc *nand); +int nand_chng_read_col(device_t dev, uint32_t col, void *buf, size_t len); +int nand_chng_write_col(device_t dev, uint32_t col, void *buf, size_t len); +int nand_get_feature(device_t dev, uint8_t feat, void* buf); +int nand_set_feature(device_t dev, uint8_t feat, void* buf); + + +int nand_erase_block_intlv(device_t dev, uint32_t block); +int nand_copyback_read(device_t dev, uint32_t page, uint32_t col, + void *buf, size_t len); +int nand_copyback_prog(device_t dev, uint32_t page, uint32_t col, + void *buf, size_t len); +int nand_copyback_prog_intlv(device_t dev, uint32_t page); +int nand_prog_cache(device_t dev, uint32_t page, uint32_t col, + void *buf, size_t len, uint8_t end); +int nand_prog_intlv(device_t dev, uint32_t page, uint32_t col, + void *buf, size_t len); +int nand_read_cache(device_t dev, uint32_t page, uint32_t col, + void *buf, size_t len, uint8_t end); + +int nand_write_ecc(struct nand_softc *nand, uint32_t page, uint8_t *data); +int nand_read_ecc(struct nand_softc *nand, uint32_t page, uint8_t *data); + +int nand_softecc_get(device_t dev, uint8_t *buf, int pagesize, uint8_t *ecc); +int nand_softecc_correct(device_t dev, uint8_t *buf, int pagesize, + uint8_t *readecc, uint8_t *calcecc); + +/* Chip initialization */ +void nand_init(struct nand_softc *nand, device_t dev, int ecc_mode, + int ecc_bytes, int ecc_size, uint16_t* eccposition, char* cdev_name); +void nand_detach(struct nand_softc *nand); +struct nand_params *nand_get_params(struct nand_id *id); + +void nand_onfi_set_params(struct nand_chip *chip, struct onfi_params *params); +void nand_set_params(struct nand_chip *chip, struct nand_params *params); +int nand_init_stat(struct nand_chip *chip); +void nand_destroy_stat(struct nand_chip *chip); + +/* BBT */ +int nand_init_bbt(struct nand_chip *chip); +void nand_destroy_bbt(struct nand_chip *chip); +int nand_update_bbt(struct nand_chip *chip); +int nand_mark_bad_block(struct nand_chip* chip, uint32_t block_num); +int nand_check_bad_block(struct nand_chip* chip, uint32_t block_num); + +/* cdev creation/removal */ +int nand_make_dev(struct nand_chip* chip); +void nand_destroy_dev(struct nand_chip *chip); + +int create_geom_disk(struct nand_chip* chip); +int create_geom_raw_disk(struct nand_chip *chip); +void destroy_geom_disk(struct nand_chip *chip); +void destroy_geom_raw_disk(struct nand_chip *chip); + +int init_chip_geom(struct chip_geom* cg, uint32_t luns, uint32_t blks_per_lun, + uint32_t pgs_per_blk, uint32_t pg_size, uint32_t oob_size); +int nand_row_to_blkpg(struct chip_geom *cg, uint32_t row, uint32_t *lun, + uint32_t *blk, uint32_t *pg); +int page_to_row(struct chip_geom *cg, uint32_t page, uint32_t *row); +int nand_check_page_boundary(struct nand_chip *chip, uint32_t page); +void nand_get_chip_param(struct nand_chip *chip, struct chip_param_io *param); + +#endif /* _DEV_NAND_H_ */ diff --git a/sys/dev/nand/nand_bbt.c b/sys/dev/nand/nand_bbt.c new file mode 100644 index 0000000..d3f163a --- /dev/null +++ b/sys/dev/nand/nand_bbt.c @@ -0,0 +1,273 @@ +/*- + * Copyright (c) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +#include <sys/cdefs.h> + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/malloc.h> +#include <sys/bus.h> + +#include <dev/nand/nand.h> + +#include "nand_if.h" + +#define BBT_PRIMARY_PATTERN 0x01020304 +#define BBT_SECONDARY_PATTERN 0x05060708 + +enum bbt_place { + BBT_NONE, + BBT_PRIMARY, + BBT_SECONDARY +}; + +struct nand_bbt { + struct nand_chip *chip; + uint32_t primary_map; + uint32_t secondary_map; + enum bbt_place active; + struct bbt_header *hdr; + uint32_t tab_len; + uint32_t *table; +}; + +struct bbt_header { + uint32_t pattern; + int32_t seq_nr; +}; + +static int nand_bbt_save(struct nand_bbt *); +static int nand_bbt_load_hdr(struct nand_bbt *, struct bbt_header *, int8_t); +static int nand_bbt_load_table(struct nand_bbt *); +static int nand_bbt_prescan(struct nand_bbt *); + +int +nand_init_bbt(struct nand_chip *chip) +{ + struct chip_geom *cg; + struct nand_bbt *bbt; + int err; + + cg = &chip->chip_geom; + + bbt = malloc(sizeof(struct nand_bbt), M_NAND, M_ZERO | M_WAITOK); + if (!bbt) { + device_printf(chip->dev, + "Cannot allocate memory for bad block struct"); + return (ENOMEM); + } + + bbt->chip = chip; + bbt->active = BBT_NONE; + bbt->primary_map = cg->chip_size - cg->block_size; + bbt->secondary_map = cg->chip_size - 2 * cg->block_size; + bbt->tab_len = cg->blks_per_chip * sizeof(uint32_t); + bbt->hdr = malloc(sizeof(struct bbt_header) + bbt->tab_len, M_NAND, + M_WAITOK); + if (!bbt->hdr) { + device_printf(chip->dev, "Cannot allocate %d bytes for BB " + "Table", bbt->tab_len); + free(bbt, M_NAND); + return (ENOMEM); + } + bbt->hdr->seq_nr = 0; + bbt->table = (uint32_t *)((uint8_t *)bbt->hdr + + sizeof(struct bbt_header)); + + err = nand_bbt_load_table(bbt); + if (err) { + free(bbt->table, M_NAND); + free(bbt, M_NAND); + return (err); + } + + chip->bbt = bbt; + if (bbt->active == BBT_NONE) { + bbt->active = BBT_PRIMARY; + memset(bbt->table, 0xff, bbt->tab_len); + nand_bbt_prescan(bbt); + nand_bbt_save(bbt); + } else + device_printf(chip->dev, "Found BBT table for chip\n"); + + return (0); +} + +void +nand_destroy_bbt(struct nand_chip *chip) +{ + + if (chip->bbt) { + nand_bbt_save(chip->bbt); + + free(chip->bbt->hdr, M_NAND); + free(chip->bbt, M_NAND); + chip->bbt = NULL; + } +} + +int +nand_update_bbt(struct nand_chip *chip) +{ + + nand_bbt_save(chip->bbt); + + return (0); +} + +static int +nand_bbt_save(struct nand_bbt *bbt) +{ + enum bbt_place next; + uint32_t addr; + int32_t err; + + if (bbt->active == BBT_PRIMARY) { + addr = bbt->secondary_map; + bbt->hdr->pattern = BBT_SECONDARY_PATTERN; + next = BBT_SECONDARY; + } else { + addr = bbt->primary_map; + bbt->hdr->pattern = BBT_PRIMARY_PATTERN; + next = BBT_PRIMARY; + } + + err = nand_erase_blocks(bbt->chip, addr, + bbt->chip->chip_geom.block_size); + if (err) + return (err); + + bbt->hdr->seq_nr++; + + err = nand_prog_pages_raw(bbt->chip, addr, bbt->hdr, + bbt->tab_len + sizeof(struct bbt_header)); + if (err) + return (err); + + bbt->active = next; + return (0); +} + +static int +nand_bbt_load_hdr(struct nand_bbt *bbt, struct bbt_header *hdr, int8_t primary) +{ + uint32_t addr; + + if (primary) + addr = bbt->primary_map; + else + addr = bbt->secondary_map; + + return (nand_read_pages_raw(bbt->chip, addr, hdr, + sizeof(struct bbt_header))); +} + +static int +nand_bbt_load_table(struct nand_bbt *bbt) +{ + struct bbt_header hdr1, hdr2; + uint32_t address = 0; + int err = 0; + + bzero(&hdr1, sizeof(hdr1)); + bzero(&hdr2, sizeof(hdr2)); + + nand_bbt_load_hdr(bbt, &hdr1, 1); + if (hdr1.pattern == BBT_PRIMARY_PATTERN) { + bbt->active = BBT_PRIMARY; + address = bbt->primary_map; + } else + bzero(&hdr1, sizeof(hdr1)); + + + nand_bbt_load_hdr(bbt, &hdr2, 0); + if ((hdr2.pattern == BBT_SECONDARY_PATTERN) && + (hdr2.seq_nr > hdr1.seq_nr)) { + bbt->active = BBT_SECONDARY; + address = bbt->secondary_map; + } else + bzero(&hdr2, sizeof(hdr2)); + + if (bbt->active != BBT_NONE) + err = nand_read_pages_raw(bbt->chip, address, bbt->hdr, + bbt->tab_len + sizeof(struct bbt_header)); + + return (err); +} + +static int +nand_bbt_prescan(struct nand_bbt *bbt) +{ + int32_t i; + uint8_t bad; + bool printed_hash = 0; + + device_printf(bbt->chip->dev, "No BBT found. Prescan chip...\n"); + for (i = 0; i < bbt->chip->chip_geom.blks_per_chip; i++) { + if (NAND_IS_BLK_BAD(bbt->chip->dev, i, &bad)) + return (ENXIO); + + if (bad) { + device_printf(bbt->chip->dev, "Bad block(%d)\n", i); + bbt->table[i] = 0x0FFFFFFF; + } + if (!(i % 100)) { + printf("#"); + printed_hash = 1; + } + } + + if (printed_hash) + printf("\n"); + + return (0); +} + +int +nand_check_bad_block(struct nand_chip *chip, uint32_t block_number) +{ + + if (!chip || !chip->bbt) + return (0); + + if ((chip->bbt->table[block_number] & 0xF0000000) == 0) + return (1); + + return (0); +} + +int +nand_mark_bad_block(struct nand_chip *chip, uint32_t block_number) +{ + + chip->bbt->table[block_number] = 0x0FFFFFFF; + + return (0); +} diff --git a/sys/dev/nand/nand_cdev.c b/sys/dev/nand/nand_cdev.c new file mode 100644 index 0000000..ac27ff3 --- /dev/null +++ b/sys/dev/nand/nand_cdev.c @@ -0,0 +1,413 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <sys/uio.h> +#include <sys/bio.h> + +#include <dev/nand/nand.h> +#include <dev/nand/nandbus.h> +#include <dev/nand/nand_dev.h> +#include "nand_if.h" +#include "nandbus_if.h" + +static int nand_page_stat(struct nand_chip *, struct page_stat_io *); +static int nand_block_stat(struct nand_chip *, struct block_stat_io *); + +static d_ioctl_t nand_ioctl; +static d_open_t nand_open; +static d_strategy_t nand_strategy; + +static struct cdevsw nand_cdevsw = { + .d_version = D_VERSION, + .d_name = "nand", + .d_open = nand_open, + .d_read = physread, + .d_write = physwrite, + .d_ioctl = nand_ioctl, + .d_strategy = nand_strategy, +}; + +static int +offset_to_page(struct chip_geom *cg, uint32_t offset) +{ + + return (offset / cg->page_size); +} + +static int +offset_to_page_off(struct chip_geom *cg, uint32_t offset) +{ + + return (offset % cg->page_size); +} + +int +nand_make_dev(struct nand_chip *chip) +{ + struct nandbus_ivar *ivar; + device_t parent, nandbus; + int parent_unit, unit; + char *name; + + ivar = device_get_ivars(chip->dev); + nandbus = device_get_parent(chip->dev); + + if (ivar->chip_cdev_name) { + name = ivar->chip_cdev_name; + + /* + * If we got distinct name for chip device we can enumarete it + * based on contoller number. + */ + parent = device_get_parent(nandbus); + } else { + name = "nand"; + parent = nandbus; + } + + parent_unit = device_get_unit(parent); + unit = parent_unit * 4 + chip->num; + chip->cdev = make_dev(&nand_cdevsw, unit, UID_ROOT, GID_WHEEL, + 0666, "%s%d.%d", name, parent_unit, chip->num); + + if (chip->cdev == NULL) + return (ENXIO); + + if (bootverbose) + device_printf(chip->dev, "Created cdev %s%d.%d for chip " + "[0x%0x, 0x%0x]\n", name, parent_unit, chip->num, + ivar->man_id, ivar->dev_id); + + chip->cdev->si_drv1 = chip; + + return (0); +} + +void +nand_destroy_dev(struct nand_chip *chip) +{ + + if (chip->cdev) + destroy_dev(chip->cdev); +} + +static int +nand_open(struct cdev *dev, int oflags, int devtype, struct thread *td) +{ + + return (0); +} + +static int +nand_read(struct nand_chip *chip, uint32_t offset, void *buf, uint32_t len) +{ + struct chip_geom *cg; + device_t nandbus; + int start_page, count, off, err = 0; + uint8_t *ptr, *tmp; + + nand_debug(NDBG_CDEV, "Read from chip%d [%p] at %d\n", chip->num, + chip, offset); + + nandbus = device_get_parent(chip->dev); + NANDBUS_LOCK(nandbus); + NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num); + + cg = &chip->chip_geom; + start_page = offset_to_page(cg, offset); + off = offset_to_page_off(cg, offset); + count = (len > cg->page_size - off) ? cg->page_size - off : len; + + ptr = (uint8_t *)buf; + while (len > 0) { + if (len < cg->page_size) { + tmp = malloc(cg->page_size, M_NAND, M_WAITOK); + if (!tmp) { + err = ENOMEM; + break; + } + err = NAND_READ_PAGE(chip->dev, start_page, + tmp, cg->page_size, 0); + if (err) { + free(tmp, M_NAND); + break; + } + bcopy(tmp + off, ptr, count); + free(tmp, M_NAND); + } else { + err = NAND_READ_PAGE(chip->dev, start_page, + ptr, cg->page_size, 0); + if (err) + break; + } + + len -= count; + start_page++; + ptr += count; + count = (len > cg->page_size) ? cg->page_size : len; + off = 0; + } + + NANDBUS_UNLOCK(nandbus); + return (err); +} + +static int +nand_write(struct nand_chip *chip, uint32_t offset, void* buf, uint32_t len) +{ + struct chip_geom *cg; + device_t nandbus; + int off, start_page, err = 0; + uint8_t *ptr; + + nand_debug(NDBG_CDEV, "Write to chip %d [%p] at %d\n", chip->num, + chip, offset); + + nandbus = device_get_parent(chip->dev); + NANDBUS_LOCK(nandbus); + NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num); + + cg = &chip->chip_geom; + start_page = offset_to_page(cg, offset); + off = offset_to_page_off(cg, offset); + + if (off != 0 || (len % cg->page_size) != 0) { + printf("Not aligned write start [0x%08x] size [0x%08x]\n", + off, len); + NANDBUS_UNLOCK(nandbus); + return (EINVAL); + } + + ptr = (uint8_t *)buf; + while (len > 0) { + err = NAND_PROGRAM_PAGE(chip->dev, start_page, ptr, + cg->page_size, 0); + if (err) + break; + + len -= cg->page_size; + start_page++; + ptr += cg->page_size; + } + + NANDBUS_UNLOCK(nandbus); + return (err); +} + +static void +nand_strategy(struct bio *bp) +{ + struct nand_chip *chip; + struct cdev *dev; + int err = 0; + + dev = bp->bio_dev; + chip = dev->si_drv1; + + nand_debug(NDBG_CDEV, "Strategy %s on chip %d [%p]\n", + (bp->bio_cmd & BIO_READ) == BIO_READ ? "READ" : "WRITE", + chip->num, chip); + + if ((bp->bio_cmd & BIO_READ) == BIO_READ) { + err = nand_read(chip, + bp->bio_offset & 0xffffffff, + bp->bio_data, bp->bio_bcount); + } else { + err = nand_write(chip, + bp->bio_offset & 0xffffffff, + bp->bio_data, bp->bio_bcount); + } + + if (err == 0) + bp->bio_resid = 0; + else { + bp->bio_error = EIO; + bp->bio_flags |= BIO_ERROR; + bp->bio_resid = bp->bio_bcount; + } + + biodone(bp); +} + +static int +nand_oob_access(struct nand_chip *chip, uint32_t page, uint32_t offset, + uint32_t len, uint8_t *data, uint8_t write) +{ + struct chip_geom *cg; + uint8_t *buf = NULL; + int ret = 0; + + cg = &chip->chip_geom; + + buf = malloc(cg->oob_size, M_NAND, M_WAITOK); + if (!buf) + return (ENOMEM); + + memset(buf, 0xff, cg->oob_size); + + if (!write) { + ret = nand_read_oob(chip, page, buf, cg->oob_size); + copyout(buf, data, len); + } else { + copyin(data, buf, len); + ret = nand_prog_oob(chip, page, buf, cg->oob_size); + } + + free(buf, M_NAND); + + return (ret); +} + +static int +nand_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, + struct thread *td) +{ + struct nand_chip *chip; + struct nand_oob_rw *oob_rw = NULL; + struct nand_raw_rw *raw_rw = NULL; + device_t nandbus; + uint8_t *buf = NULL; + int ret = 0; + uint8_t status; + + chip = (struct nand_chip *)dev->si_drv1; + nandbus = device_get_parent(chip->dev); + + if ((cmd == NAND_IO_RAW_READ) || (cmd == NAND_IO_RAW_PROG)) { + raw_rw = (struct nand_raw_rw *)data; + buf = malloc(raw_rw->len, M_NAND, M_WAITOK); + } + switch(cmd) { + case NAND_IO_ERASE: + ret = nand_erase_blocks(chip, ((off_t *)data)[0], + ((off_t *)data)[1]); + break; + + case NAND_IO_OOB_READ: + oob_rw = (struct nand_oob_rw *)data; + ret = nand_oob_access(chip, oob_rw->page, 0, + oob_rw->len, oob_rw->data, 0); + break; + + case NAND_IO_OOB_PROG: + oob_rw = (struct nand_oob_rw *)data; + ret = nand_oob_access(chip, oob_rw->page, 0, + oob_rw->len, oob_rw->data, 1); + break; + + case NAND_IO_GET_STATUS: + NANDBUS_LOCK(nandbus); + ret = NANDBUS_GET_STATUS(nandbus, &status); + if (ret == 0) + *(uint8_t *)data = status; + NANDBUS_UNLOCK(nandbus); + break; + + case NAND_IO_RAW_PROG: + ret = copyin(raw_rw->data, buf, raw_rw->len); + if (ret) + break; + ret = nand_prog_pages_raw(chip, raw_rw->off, buf, + raw_rw->len); + break; + + case NAND_IO_RAW_READ: + ret = nand_read_pages_raw(chip, raw_rw->off, buf, + raw_rw->len); + if (ret) + break; + ret = copyout(buf, raw_rw->data, raw_rw->len); + break; + + case NAND_IO_PAGE_STAT: + ret = nand_page_stat(chip, (struct page_stat_io *)data); + break; + + case NAND_IO_BLOCK_STAT: + ret = nand_block_stat(chip, (struct block_stat_io *)data); + break; + + case NAND_IO_GET_CHIP_PARAM: + nand_get_chip_param(chip, (struct chip_param_io *)data); + break; + + default: + printf("Unknown nand_ioctl request \n"); + ret = EIO; + } + + if (buf) + free(buf, M_NAND); + + return (ret); +} + +static int +nand_page_stat(struct nand_chip *chip, struct page_stat_io *page_stat) +{ + struct chip_geom *cg; + struct page_stat *stat; + int num_pages; + + cg = &chip->chip_geom; + num_pages = cg->pgs_per_blk * cg->blks_per_lun * cg->luns; + if (page_stat->page_num >= num_pages) + return (EINVAL); + + stat = &chip->pg_stat[page_stat->page_num]; + page_stat->page_read = stat->page_read; + page_stat->page_written = stat->page_written; + page_stat->page_raw_read = stat->page_raw_read; + page_stat->page_raw_written = stat->page_raw_written; + page_stat->ecc_succeded = stat->ecc_stat.ecc_succeded; + page_stat->ecc_corrected = stat->ecc_stat.ecc_corrected; + page_stat->ecc_failed = stat->ecc_stat.ecc_failed; + + return (0); +} + +static int +nand_block_stat(struct nand_chip *chip, struct block_stat_io *block_stat) +{ + struct chip_geom *cg; + uint32_t block_num = block_stat->block_num; + + cg = &chip->chip_geom; + if (block_num >= cg->blks_per_lun * cg->luns) + return (EINVAL); + + block_stat->block_erased = chip->blk_stat[block_num].block_erased; + + return (0); +} diff --git a/sys/dev/nand/nand_dev.h b/sys/dev/nand/nand_dev.h new file mode 100644 index 0000000..bc7d6c4 --- /dev/null +++ b/sys/dev/nand/nand_dev.h @@ -0,0 +1,90 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +#ifndef _DEV_NAND_CDEV_H_ +#define _DEV_NAND_CDEV_H_ + +#include <sys/ioccom.h> +#include <sys/param.h> + +struct nand_raw_rw { + off_t off; + off_t len; + uint8_t *data; +}; + +struct nand_oob_rw { + uint32_t page; + off_t len; + uint8_t *data; +}; + +#define NAND_IOCTL_GROUP 'N' +#define NAND_IO_ERASE _IOWR(NAND_IOCTL_GROUP, 0x0, off_t[2]) + +#define NAND_IO_OOB_READ _IOWR(NAND_IOCTL_GROUP, 0x1, struct nand_oob_rw) + +#define NAND_IO_OOB_PROG _IOWR(NAND_IOCTL_GROUP, 0x2, struct nand_oob_rw) + +#define NAND_IO_RAW_READ _IOWR(NAND_IOCTL_GROUP, 0x3, struct nand_raw_rw) + +#define NAND_IO_RAW_PROG _IOWR(NAND_IOCTL_GROUP, 0x4, struct nand_raw_rw) + +#define NAND_IO_GET_STATUS _IOWR(NAND_IOCTL_GROUP, 0x5, uint8_t) + +struct page_stat_io { + uint32_t page_num; + uint32_t page_read; + uint32_t page_written; + uint32_t page_raw_read; + uint32_t page_raw_written; + uint32_t ecc_succeded; + uint32_t ecc_corrected; + uint32_t ecc_failed; +}; +#define NAND_IO_PAGE_STAT _IOWR(NAND_IOCTL_GROUP, 0x6, \ + struct page_stat_io) + +struct block_stat_io { + uint32_t block_num; + uint32_t block_erased; +}; +#define NAND_IO_BLOCK_STAT _IOWR(NAND_IOCTL_GROUP, 0x7, \ + struct block_stat_io) + +struct chip_param_io { + uint32_t page_size; + uint32_t oob_size; + + uint32_t blocks; + uint32_t pages_per_block; +}; +#define NAND_IO_GET_CHIP_PARAM _IOWR(NAND_IOCTL_GROUP, 0x8, \ + struct chip_param_io) + +#endif /* _DEV_NAND_CDEV_H_ */ diff --git a/sys/dev/nand/nand_ecc_pos.h b/sys/dev/nand/nand_ecc_pos.h new file mode 100644 index 0000000..f40415c --- /dev/null +++ b/sys/dev/nand/nand_ecc_pos.h @@ -0,0 +1,56 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +#ifndef _DEV_NAND_ECC_POS_H_ +#define _DEV_NAND_ECC_POS_H_ + +static uint16_t default_software_ecc_positions_16[] = {2, 0, 1, 7, 4, 6}; + +static uint16_t default_software_ecc_positions_64[] = { + + 42, 40, 41, 45, 43, 44, 48, 46, + 47, 51, 49, 50, 54, 52, 53, 57, + 55, 56, 60, 58, 59, 63, 61, 62 +}; + +static uint16_t default_software_ecc_positions_128[] = { + 8, 9, 10, 11, 12, 13, + 18, 19, 20, 21, 22, 23, + 28, 29, 30, 31, 32, 33, + 38, 39, 40, 41, 42, 43, + 48, 49, 50, 51, 52, 53, + 58, 59, 60, 61, 62, 63, + 68, 69, 70, 71, 72, 73, + 78, 79, 80, 81, 82, 83, + 88, 89, 90, 91, 92, 93, + 98, 99, 100, 101, 102, 103, + 108, 109, 110, 111, 112, 113, + 118, 119, 120, 121, 122, 123, +}; +#endif /* _DEV_NAND_ECC_POS_H_ */ + diff --git a/sys/dev/nand/nand_generic.c b/sys/dev/nand/nand_generic.c new file mode 100644 index 0000000..85e81be --- /dev/null +++ b/sys/dev/nand/nand_generic.c @@ -0,0 +1,1320 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + */ + +/* Generic NAND driver */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/proc.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/rman.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/time.h> +#include <sys/malloc.h> + +#include <dev/nand/nand.h> +#include <dev/nand/nandbus.h> +#include "nfc_if.h" +#include "nand_if.h" +#include "nandbus_if.h" + + +static int onfi_nand_probe(device_t dev); +static int large_nand_probe(device_t dev); +static int small_nand_probe(device_t dev); +static int generic_nand_attach(device_t dev); +static int generic_nand_detach(device_t dev); + +static int generic_erase_block(device_t, uint32_t); +static int generic_erase_block_intlv(device_t, uint32_t); +static int generic_read_page (device_t, uint32_t, void *, uint32_t, uint32_t); +static int generic_read_oob(device_t, uint32_t, void *, uint32_t, uint32_t); +static int generic_program_page(device_t, uint32_t, void *, uint32_t, uint32_t); +static int generic_program_page_intlv(device_t, uint32_t, void *, uint32_t, + uint32_t); +static int generic_program_oob(device_t, uint32_t, void *, uint32_t, uint32_t); +static int generic_is_blk_bad(device_t, uint32_t, uint8_t *); +static int generic_get_ecc(device_t, void *, void *, int *); +static int generic_correct_ecc(device_t, void *, void *, void *); + +static int small_read_page(device_t, uint32_t, void *, uint32_t, uint32_t); +static int small_read_oob(device_t, uint32_t, void *, uint32_t, uint32_t); +static int small_program_page(device_t, uint32_t, void *, uint32_t, uint32_t); +static int small_program_oob(device_t, uint32_t, void *, uint32_t, uint32_t); + +static int onfi_is_blk_bad(device_t, uint32_t, uint8_t *); +static int onfi_read_parameter(struct nand_chip *, struct onfi_params *); + +static int nand_send_address(device_t, int32_t, int32_t, int8_t); + +static device_method_t onand_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, onfi_nand_probe), + DEVMETHOD(device_attach, generic_nand_attach), + DEVMETHOD(device_detach, generic_nand_detach), + + DEVMETHOD(nand_read_page, generic_read_page), + DEVMETHOD(nand_program_page, generic_program_page), + DEVMETHOD(nand_program_page_intlv, generic_program_page_intlv), + DEVMETHOD(nand_read_oob, generic_read_oob), + DEVMETHOD(nand_program_oob, generic_program_oob), + DEVMETHOD(nand_erase_block, generic_erase_block), + DEVMETHOD(nand_erase_block_intlv, generic_erase_block_intlv), + + DEVMETHOD(nand_is_blk_bad, onfi_is_blk_bad), + DEVMETHOD(nand_get_ecc, generic_get_ecc), + DEVMETHOD(nand_correct_ecc, generic_correct_ecc), + { 0, 0 } +}; + +static device_method_t lnand_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, large_nand_probe), + DEVMETHOD(device_attach, generic_nand_attach), + DEVMETHOD(device_detach, generic_nand_detach), + + DEVMETHOD(nand_read_page, generic_read_page), + DEVMETHOD(nand_program_page, generic_program_page), + DEVMETHOD(nand_read_oob, generic_read_oob), + DEVMETHOD(nand_program_oob, generic_program_oob), + DEVMETHOD(nand_erase_block, generic_erase_block), + + DEVMETHOD(nand_is_blk_bad, generic_is_blk_bad), + DEVMETHOD(nand_get_ecc, generic_get_ecc), + DEVMETHOD(nand_correct_ecc, generic_correct_ecc), + { 0, 0 } +}; + +static device_method_t snand_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, small_nand_probe), + DEVMETHOD(device_attach, generic_nand_attach), + DEVMETHOD(device_detach, generic_nand_detach), + + DEVMETHOD(nand_read_page, small_read_page), + DEVMETHOD(nand_program_page, small_program_page), + DEVMETHOD(nand_read_oob, small_read_oob), + DEVMETHOD(nand_program_oob, small_program_oob), + DEVMETHOD(nand_erase_block, generic_erase_block), + + DEVMETHOD(nand_is_blk_bad, generic_is_blk_bad), + DEVMETHOD(nand_get_ecc, generic_get_ecc), + DEVMETHOD(nand_correct_ecc, generic_correct_ecc), + { 0, 0 } +}; + +devclass_t onand_devclass; +devclass_t lnand_devclass; +devclass_t snand_devclass; + +driver_t onand_driver = { + "onand", + onand_methods, + sizeof(struct nand_chip) +}; + +driver_t lnand_driver = { + "lnand", + lnand_methods, + sizeof(struct nand_chip) +}; + +driver_t snand_driver = { + "snand", + snand_methods, + sizeof(struct nand_chip) +}; + +DRIVER_MODULE(onand, nandbus, onand_driver, onand_devclass, 0, 0); +DRIVER_MODULE(lnand, nandbus, lnand_driver, lnand_devclass, 0, 0); +DRIVER_MODULE(snand, nandbus, snand_driver, snand_devclass, 0, 0); + +static int +onfi_nand_probe(device_t dev) +{ + struct nandbus_ivar *ivar; + + ivar = device_get_ivars(dev); + if (ivar && ivar->is_onfi) { + device_set_desc(dev, "ONFI compliant NAND"); + return (BUS_PROBE_DEFAULT); + } + + return (ENODEV); +} + +static int +large_nand_probe(device_t dev) +{ + struct nandbus_ivar *ivar; + + ivar = device_get_ivars(dev); + if (ivar && !ivar->is_onfi && ivar->params->page_size >= 512) { + device_set_desc(dev, ivar->params->name); + return (BUS_PROBE_DEFAULT); + } + + return (ENODEV); +} + +static int +small_nand_probe(device_t dev) +{ + struct nandbus_ivar *ivar; + + ivar = device_get_ivars(dev); + if (ivar && !ivar->is_onfi && ivar->params->page_size == 512) { + device_set_desc(dev, ivar->params->name); + return (BUS_PROBE_DEFAULT); + } + + return (ENODEV); +} + +static int +generic_nand_attach(device_t dev) +{ + struct nand_chip *chip; + struct nandbus_ivar *ivar; + struct onfi_params *onfi_params; + device_t nandbus, nfc; + int err; + + chip = device_get_softc(dev); + chip->dev = dev; + + ivar = device_get_ivars(dev); + chip->id.man_id = ivar->man_id; + chip->id.dev_id = ivar->dev_id; + chip->num = ivar->cs; + + /* TODO remove when HW ECC supported */ + nandbus = device_get_parent(dev); + nfc = device_get_parent(nandbus); + + chip->nand = device_get_softc(nfc); + + if (ivar->is_onfi) { + onfi_params = malloc(sizeof(struct onfi_params), + M_NAND, M_WAITOK | M_ZERO); + if (onfi_params == NULL) + return (ENXIO); + + if (onfi_read_parameter(chip, onfi_params)) { + nand_debug(NDBG_GEN,"Could not read parameter page!\n"); + free(onfi_params, M_NAND); + return (ENXIO); + } + + nand_onfi_set_params(chip, onfi_params); + /* Set proper column and row cycles */ + ivar->cols = (onfi_params->address_cycles >> 4) & 0xf; + ivar->rows = onfi_params->address_cycles & 0xf; + free(onfi_params, M_NAND); + + } else { + + nand_set_params(chip, ivar->params); + } + + err = nand_init_stat(chip); + if (err) { + generic_nand_detach(dev); + return (err); + } + + err = nand_init_bbt(chip); + if (err) { + generic_nand_detach(dev); + return (err); + } + + err = nand_make_dev(chip); + if (err) { + generic_nand_detach(dev); + return (err); + } + + err = create_geom_disk(chip); + if (err) { + generic_nand_detach(dev); + return (err); + } + + return (0); +} + +static int +generic_nand_detach(device_t dev) +{ + struct nand_chip *chip; + + chip = device_get_softc(dev); + + nand_destroy_bbt(chip); + destroy_geom_disk(chip); + nand_destroy_dev(chip); + nand_destroy_stat(chip); + + return (0); +} + +static int +can_write(device_t nandbus) +{ + uint8_t status; + + if (NANDBUS_WAIT_READY(nandbus, &status)) + return (0); + + if (!(status & NAND_STATUS_WP)) { + nand_debug(NDBG_GEN,"Chip is write-protected"); + return (0); + } + + return (1); +} + +static int +check_fail(device_t nandbus) +{ + uint8_t status; + + NANDBUS_WAIT_READY(nandbus, &status); + if (status & NAND_STATUS_FAIL) { + nand_debug(NDBG_GEN,"Status failed %x", status); + return (ENXIO); + } + + return (0); +} + +static int +onfi_read_parameter(struct nand_chip *chip, struct onfi_params *params) +{ + device_t nandbus; + + nand_debug(NDBG_GEN,"read parameter"); + + nandbus = device_get_parent(chip->dev); + + NANDBUS_SELECT_CS(nandbus, chip->num); + + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_READ_PARAMETER)) + return (ENXIO); + + if (nand_send_address(chip->dev, -1, -1, PAGE_PARAMETER_DEF)) + return (ENXIO); + + if (NANDBUS_START_COMMAND(nandbus)) + return (ENXIO); + + NANDBUS_READ_BUFFER(nandbus, params, sizeof(struct onfi_params)); + + /* TODO */ + /* Check for signature */ + /* Check CRC */ + /* Use redundant page if necessary */ + + return (0); +} + +static int +send_read_page(device_t nand, uint8_t start_command, uint8_t end_command, + uint32_t row, uint32_t column) +{ + device_t nandbus = device_get_parent(nand); + + if (NANDBUS_SEND_COMMAND(nandbus, start_command)) + return (ENXIO); + + if (nand_send_address(nand, row, column, -1)) + return (ENXIO); + + if (NANDBUS_SEND_COMMAND(nandbus, end_command)) + return (ENXIO); + + if (NANDBUS_START_COMMAND(nandbus)) + return (ENXIO); + + return (0); +} + +static int +generic_read_page(device_t nand, uint32_t page, void *buf, uint32_t len, + uint32_t offset) +{ + struct nand_chip *chip; + struct page_stat *pg_stat; + device_t nandbus; + uint32_t row; + + nand_debug(NDBG_GEN,"%p raw read page %x[%x] at %x", nand, page, len, offset); + chip = device_get_softc(nand); + nandbus = device_get_parent(nand); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + + if (send_read_page(nand, NAND_CMD_READ, NAND_CMD_READ_END, row, + offset)) + return (ENXIO); + + DELAY(chip->t_r); + + NANDBUS_READ_BUFFER(nandbus, buf, len); + + if (check_fail(nandbus)) + return (ENXIO); + + pg_stat = &(chip->pg_stat[page]); + pg_stat->page_raw_read++; + + return (0); +} + +static int +generic_read_oob(device_t nand, uint32_t page, void* buf, uint32_t len, + uint32_t offset) +{ + struct nand_chip *chip; + device_t nandbus; + uint32_t row; + + nand_debug(NDBG_GEN,"%p raw read oob %x[%x] at %x", nand, page, len, offset); + chip = device_get_softc(nand); + nandbus = device_get_parent(nand); + + if (nand_check_page_boundary(chip, page)) { + nand_debug(NDBG_GEN,"page boundary check failed: %08x\n", page); + return (ENXIO); + } + + page_to_row(&chip->chip_geom, page, &row); + + offset += chip->chip_geom.page_size; + + if (send_read_page(nand, NAND_CMD_READ, NAND_CMD_READ_END, row, + offset)) + return (ENXIO); + + DELAY(chip->t_r); + + NANDBUS_READ_BUFFER(nandbus, buf, len); + + if (check_fail(nandbus)) + return (ENXIO); + + return (0); +} + +static int +send_start_program_page(device_t nand, uint32_t row, uint32_t column) +{ + device_t nandbus = device_get_parent(nand); + + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_PROG)) + return (ENXIO); + + if (nand_send_address(nand, row, column, -1)) + return (ENXIO); + + return (0); +} + +static int +send_end_program_page(device_t nandbus, uint8_t end_command) +{ + + if (NANDBUS_SEND_COMMAND(nandbus, end_command)) + return (ENXIO); + + if (NANDBUS_START_COMMAND(nandbus)) + return (ENXIO); + + return (0); +} + +static int +generic_program_page(device_t nand, uint32_t page, void *buf, uint32_t len, + uint32_t offset) +{ + struct nand_chip *chip; + struct page_stat *pg_stat; + device_t nandbus; + uint32_t row; + + nand_debug(NDBG_GEN,"%p raw prog page %x[%x] at %x", nand, page, len, + offset); + chip = device_get_softc(nand); + nandbus = device_get_parent(nand); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + + if (!can_write(nandbus)) + return (ENXIO); + + if (send_start_program_page(nand, row, offset)) + return (ENXIO); + + NANDBUS_WRITE_BUFFER(nandbus, buf, len); + + if (send_end_program_page(nandbus, NAND_CMD_PROG_END)) + return (ENXIO); + + DELAY(chip->t_prog); + + if (check_fail(nandbus)) + return (ENXIO); + + pg_stat = &(chip->pg_stat[page]); + pg_stat->page_raw_written++; + + return (0); +} + +static int +generic_program_page_intlv(device_t nand, uint32_t page, void *buf, + uint32_t len, uint32_t offset) +{ + struct nand_chip *chip; + struct page_stat *pg_stat; + device_t nandbus; + uint32_t row; + + nand_debug(NDBG_GEN,"%p raw prog page %x[%x] at %x", nand, page, len, offset); + chip = device_get_softc(nand); + nandbus = device_get_parent(nand); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + + if (!can_write(nandbus)) + return (ENXIO); + + if (send_start_program_page(nand, row, offset)) + return (ENXIO); + + NANDBUS_WRITE_BUFFER(nandbus, buf, len); + + if (send_end_program_page(nandbus, NAND_CMD_PROG_INTLV)) + return (ENXIO); + + DELAY(chip->t_prog); + + if (check_fail(nandbus)) + return (ENXIO); + + pg_stat = &(chip->pg_stat[page]); + pg_stat->page_raw_written++; + + return (0); +} + +static int +generic_program_oob(device_t nand, uint32_t page, void* buf, uint32_t len, + uint32_t offset) +{ + struct nand_chip *chip; + device_t nandbus; + uint32_t row; + + nand_debug(NDBG_GEN,"%p raw prog oob %x[%x] at %x", nand, page, len, + offset); + chip = device_get_softc(nand); + nandbus = device_get_parent(nand); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + offset += chip->chip_geom.page_size; + + if (!can_write(nandbus)) + return (ENXIO); + + if (send_start_program_page(nand, row, offset)) + return (ENXIO); + + NANDBUS_WRITE_BUFFER(nandbus, buf, len); + + if (send_end_program_page(nandbus, NAND_CMD_PROG_END)) + return (ENXIO); + + DELAY(chip->t_prog); + + if (check_fail(nandbus)) + return (ENXIO); + + return (0); +} + +static int +send_erase_block(device_t nand, uint32_t row, uint8_t second_command) +{ + device_t nandbus = device_get_parent(nand); + + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_ERASE)) + return (ENXIO); + + if (nand_send_address(nand, row, -1, -1)) + return (ENXIO); + + if (NANDBUS_SEND_COMMAND(nandbus, second_command)) + return (ENXIO); + + if (NANDBUS_START_COMMAND(nandbus)) + return (ENXIO); + + return (0); +} + +static int +generic_erase_block(device_t nand, uint32_t block) +{ + struct block_stat *blk_stat; + struct nand_chip *chip; + device_t nandbus; + int row; + + nand_debug(NDBG_GEN,"%p erase block %x", nand, block); + nandbus = device_get_parent(nand); + chip = device_get_softc(nand); + + if (block >= (chip->chip_geom.blks_per_lun * chip->chip_geom.luns)) + return (ENXIO); + + row = (block << chip->chip_geom.blk_shift) & + chip->chip_geom.blk_mask; + + nand_debug(NDBG_GEN,"%p erase block row %x", nand, row); + + if (!can_write(nandbus)) + return (ENXIO); + + send_erase_block(nand, row, NAND_CMD_ERASE_END); + + DELAY(chip->t_bers); + + if (check_fail(nandbus)) + return (ENXIO); + + blk_stat = &(chip->blk_stat[block]); + blk_stat->block_erased++; + + return (0); +} + +static int +generic_erase_block_intlv(device_t nand, uint32_t block) +{ + struct block_stat *blk_stat; + struct nand_chip *chip; + device_t nandbus; + int row; + + nand_debug(NDBG_GEN,"%p erase block %x", nand, block); + nandbus = device_get_parent(nand); + chip = device_get_softc(nand); + + if (block >= (chip->chip_geom.blks_per_lun * chip->chip_geom.luns)) + return (ENXIO); + + row = (block << chip->chip_geom.blk_shift) & + chip->chip_geom.blk_mask; + + if (!can_write(nandbus)) + return (ENXIO); + + send_erase_block(nand, row, NAND_CMD_ERASE_INTLV); + + DELAY(chip->t_bers); + + if (check_fail(nandbus)) + return (ENXIO); + + blk_stat = &(chip->blk_stat[block]); + blk_stat->block_erased++; + + return (0); + +} + +static int +onfi_is_blk_bad(device_t device, uint32_t block_number, uint8_t *bad) +{ + struct nand_chip *chip; + int page_number, i, j, err; + uint8_t *oob; + + chip = device_get_softc(device); + + oob = malloc(chip->chip_geom.oob_size, M_NAND, M_WAITOK); + if (!oob) { + device_printf(device, "%s: cannot allocate oob\n", __func__); + return (ENOMEM); + } + + page_number = block_number * chip->chip_geom.pgs_per_blk; + *bad = 0; + /* Check OOB of first and last page */ + for (i = 0; i < 2; i++, page_number+= chip->chip_geom.pgs_per_blk - 1) { + err = generic_read_oob(device, page_number, oob, + chip->chip_geom.oob_size, 0); + if (err) { + device_printf(device, "%s: cannot allocate oob\n", + __func__); + free(oob, M_NAND); + return (ENOMEM); + } + + for (j = 0; j < chip->chip_geom.oob_size; j++) { + if (!oob[j]) { + *bad = 1; + free(oob, M_NAND); + return (0); + } + } + } + + free(oob, M_NAND); + + return (0); +} + +static int +send_small_read_page(device_t nand, uint8_t start_command, + uint32_t row, uint32_t column) +{ + device_t nandbus = device_get_parent(nand); + + if (NANDBUS_SEND_COMMAND(nandbus, start_command)) + return (ENXIO); + + if (nand_send_address(nand, row, column, -1)) + return (ENXIO); + + if (NANDBUS_START_COMMAND(nandbus)) + return (ENXIO); + + return (0); +} + + +static int +small_read_page(device_t nand, uint32_t page, void *buf, uint32_t len, + uint32_t offset) +{ + struct nand_chip *chip; + struct page_stat *pg_stat; + device_t nandbus; + uint32_t row; + + nand_debug(NDBG_GEN,"%p small read page %x[%x] at %x", nand, page, len, offset); + chip = device_get_softc(nand); + nandbus = device_get_parent(nand); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + + if (offset < 256) { + if (send_small_read_page(nand, NAND_CMD_SMALLA, row, offset)) + return (ENXIO); + } else { + offset -= 256; + if (send_small_read_page(nandbus, NAND_CMD_SMALLB, row, offset)) + return (ENXIO); + } + + DELAY(chip->t_r); + + NANDBUS_READ_BUFFER(nandbus, buf, len); + + if (check_fail(nandbus)) + return (ENXIO); + + pg_stat = &(chip->pg_stat[page]); + pg_stat->page_raw_read++; + + return (0); +} + +static int +small_read_oob(device_t nand, uint32_t page, void *buf, uint32_t len, + uint32_t offset) +{ + struct nand_chip *chip; + struct page_stat *pg_stat; + device_t nandbus; + uint32_t row; + + nand_debug(NDBG_GEN,"%p small read oob %x[%x] at %x", nand, page, len, offset); + chip = device_get_softc(nand); + nandbus = device_get_parent(nand); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + + if (send_small_read_page(nand, NAND_CMD_SMALLOOB, row, 0)) + return (ENXIO); + + DELAY(chip->t_r); + + NANDBUS_READ_BUFFER(nandbus, buf, len); + + if (check_fail(nandbus)) + return (ENXIO); + + pg_stat = &(chip->pg_stat[page]); + pg_stat->page_raw_read++; + + return (0); +} + +static int +small_program_page(device_t nand, uint32_t page, void* buf, uint32_t len, + uint32_t offset) +{ + struct nand_chip *chip; + device_t nandbus; + uint32_t row; + + nand_debug(NDBG_GEN,"%p small prog page %x[%x] at %x", nand, page, len, offset); + chip = device_get_softc(nand); + nandbus = device_get_parent(nand); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + + if (!can_write(nandbus)) + return (ENXIO); + + if (offset < 256) { + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_SMALLA)) + return (ENXIO); + } else { + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_SMALLB)) + return (ENXIO); + } + + if (send_start_program_page(nand, row, offset)) + return (ENXIO); + + NANDBUS_WRITE_BUFFER(nandbus, buf, len); + + if (send_end_program_page(nandbus, NAND_CMD_PROG_END)) + return (ENXIO); + + DELAY(chip->t_prog); + + if (check_fail(nandbus)) + return (ENXIO); + + return (0); +} + +static int +small_program_oob(device_t nand, uint32_t page, void* buf, uint32_t len, + uint32_t offset) +{ + struct nand_chip *chip; + device_t nandbus; + uint32_t row; + + nand_debug(NDBG_GEN,"%p small prog oob %x[%x] at %x", nand, page, len, offset); + chip = device_get_softc(nand); + nandbus = device_get_parent(nand); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + + if (!can_write(nandbus)) + return (ENXIO); + + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_SMALLOOB)) + return (ENXIO); + + if (send_start_program_page(nand, row, offset)) + return (ENXIO); + + NANDBUS_WRITE_BUFFER(nandbus, buf, len); + + if (send_end_program_page(nandbus, NAND_CMD_PROG_END)) + return (ENXIO); + + DELAY(chip->t_prog); + + if (check_fail(nandbus)) + return (ENXIO); + + return (0); +} + +int +nand_send_address(device_t nand, int32_t row, int32_t col, int8_t id) +{ + struct nandbus_ivar *ivar; + device_t nandbus; + uint8_t addr; + int err = 0; + int i; + + nandbus = device_get_parent(nand); + ivar = device_get_ivars(nand); + + if (id != -1) { + nand_debug(NDBG_GEN,"send_address: send id %02x", id); + err = NANDBUS_SEND_ADDRESS(nandbus, id); + } + + if (!err && col != -1) { + for (i = 0; i < ivar->cols; i++, col >>= 8) { + addr = (uint8_t)(col & 0xff); + nand_debug(NDBG_GEN,"send_address: send address column " + "%02x", addr); + err = NANDBUS_SEND_ADDRESS(nandbus, addr); + if (err) + break; + } + } + + if (!err && row != -1) { + for (i = 0; i < ivar->rows; i++, row >>= 8) { + addr = (uint8_t)(row & 0xff); + nand_debug(NDBG_GEN,"send_address: send address row " + "%02x", addr); + err = NANDBUS_SEND_ADDRESS(nandbus, addr); + if (err) + break; + } + } + + return (err); +} + +static int +generic_is_blk_bad(device_t dev, uint32_t block, uint8_t *bad) +{ + struct nand_chip *chip; + int page_number, err, i; + uint8_t *oob; + + chip = device_get_softc(dev); + + oob = malloc(chip->chip_geom.oob_size, M_NAND, M_WAITOK); + if (!oob) { + device_printf(dev, "%s: cannot allocate OOB\n", __func__); + return (ENOMEM); + } + + page_number = block * chip->chip_geom.pgs_per_blk; + *bad = 0; + + /* Check OOB of first and second page */ + for (i = 0; i < 2; i++) { + err = NAND_READ_OOB(dev, page_number + i, oob, + chip->chip_geom.oob_size, 0); + if (err) { + device_printf(dev, "%s: cannot allocate OOB\n", + __func__); + free(oob, M_NAND); + return (ENOMEM); + } + + if (!oob[0]) { + *bad = 1; + free(oob, M_NAND); + return (0); + } + } + + free(oob, M_NAND); + + return (0); +} + +static int +generic_get_ecc(device_t dev, void *buf, void *ecc, int *needwrite) +{ + struct nand_chip *chip = device_get_softc(dev); + struct chip_geom *cg = &chip->chip_geom; + + return (NANDBUS_GET_ECC(device_get_parent(dev), buf, cg->page_size, + ecc, needwrite)); +} + +static int +generic_correct_ecc(device_t dev, void *buf, void *readecc, void *calcecc) +{ + struct nand_chip *chip = device_get_softc(dev); + struct chip_geom *cg = &chip->chip_geom; + + return (NANDBUS_CORRECT_ECC(device_get_parent(dev), buf, + cg->page_size, readecc, calcecc)); +} + + +#if 0 +int +nand_chng_read_col(device_t nand, uint32_t col, void *buf, size_t len) +{ + struct nand_chip *chip; + device_t nandbus; + + chip = device_get_softc(nand); + nandbus = device_get_parent(nand); + + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_CHNG_READ_COL)) + return (ENXIO); + + if (NANDBUS_SEND_ADDRESS(nandbus, -1, col, -1)) + return (ENXIO); + + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_CHNG_READ_COL_END)) + return (ENXIO); + + if (NANDBUS_START_COMMAND(nandbus)) + return (ENXIO); + + if (buf != NULL && len > 0) + NANDBUS_READ_BUFFER(nandbus, buf, len); + + return (0); +} + +int +nand_chng_write_col(device_t dev, uint32_t col, void *buf, + size_t len) +{ + struct nand_chip *chip; + device_t nandbus; + + chip = device_get_softc(dev); + nandbus = device_get_parent(dev); + + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_CHNG_WRITE_COL)) + return (ENXIO); + + if (NANDBUS_SEND_ADDRESS(nandbus, -1, col, -1)) + return (ENXIO); + + if (buf != NULL && len > 0) + NANDBUS_WRITE_BUFFER(nandbus, buf, len); + + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_CHNG_READ_COL_END)) + return (ENXIO); + + if (NANDBUS_START_COMMAND(nandbus)) + return (ENXIO); + + return (0); +} + +int +nand_copyback_read(device_t dev, uint32_t page, uint32_t col, + void *buf, size_t len) +{ + struct nand_chip *chip; + struct page_stat *pg_stat; + device_t nandbus; + uint32_t row; + + nand_debug(NDBG_GEN," raw read page %x[%x] at %x", page, col, len); + chip = device_get_softc(dev); + nandbus = device_get_parent(dev); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + + if (send_read_page(nand, NAND_CMD_READ, NAND_CMD_READ_CPBK, row, 0)) + return (ENXIO); + + DELAY(chip->t_r); + if (check_fail(nandbus)) + return (ENXIO); + + if (buf != NULL && len > 0) + NANDBUS_READ_BUFFER(nandbus, buf, len); + + pg_stat = &(chip->pg_stat[page]); + pg_stat->page_raw_read++; + + return (0); +} + +int +nand_copyback_prog(device_t dev, uint32_t page, uint32_t col, + void *buf, size_t len) +{ + struct nand_chip *chip; + struct page_stat *pg_stat; + device_t nandbus; + uint32_t row; + + nand_debug(NDBG_GEN,"copyback prog page %x[%x]", page, len); + chip = device_get_softc(dev); + nandbus = device_get_parent(dev); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + + if (!can_write(nandbus)) + return (ENXIO); + + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_CHNG_WRITE_COL)) + return (ENXIO); + + if (NANDBUS_SEND_ADDRESS(nandbus, row, col, -1)) + return (ENXIO); + + if (buf != NULL && len > 0) + NANDBUS_WRITE_BUFFER(nandbus, buf, len); + + if (send_end_program_page(nandbus, NAND_CMD_PROG_END)) + return (ENXIO); + + DELAY(chip->t_prog); + + if (check_fail(nandbus)) + return (ENXIO); + + pg_stat = &(chip->pg_stat[page]); + pg_stat->page_raw_written++; + + return (0); +} + +int +nand_copyback_prog_intlv(device_t dev, uint32_t page) +{ + struct nand_chip *chip; + struct page_stat *pg_stat; + device_t nandbus; + uint32_t row; + + nand_debug(NDBG_GEN,"cache prog page %x", page); + chip = device_get_softc(dev); + nandbus = device_get_parent(dev); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + + if (!can_write(nandbus)) + return (ENXIO); + + if (send_start_program_page(nand, row, 0)) + return (ENXIO); + + if (send_end_program_page(nandbus, NAND_CMD_PROG_INTLV)) + return (ENXIO); + + DELAY(chip->t_prog); + + if (check_fail(nandbus)) + return (ENXIO); + + pg_stat = &(chip->pg_stat[page]); + pg_stat->page_raw_written++; + + return (0); +} + +int +nand_prog_cache(device_t dev, uint32_t page, uint32_t col, + void *buf, size_t len, uint8_t end) +{ + struct nand_chip *chip; + struct page_stat *pg_stat; + device_t nandbus; + uint32_t row; + uint8_t command; + + nand_debug(NDBG_GEN,"cache prog page %x[%x]", page, len); + chip = device_get_softc(dev); + nandbus = device_get_parent(dev); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + + if (!can_write(nandbus)) + return (ENXIO); + + if (send_start_program_page(dev, row, 0)) + return (ENXIO); + + NANDBUS_WRITE_BUFFER(nandbus, buf, len); + + if (end) + command = NAND_CMD_PROG_END; + else + command = NAND_CMD_PROG_CACHE; + + if (send_end_program_page(nandbus, command)) + return (ENXIO); + + DELAY(chip->t_prog); + + if (check_fail(nandbus)) + return (ENXIO); + + pg_stat = &(chip->pg_stat[page]); + pg_stat->page_raw_written++; + + return (0); +} + +int +nand_read_cache(device_t dev, uint32_t page, uint32_t col, + void *buf, size_t len, uint8_t end) +{ + struct nand_chip *chip; + struct page_stat *pg_stat; + device_t nandbus; + uint32_t row; + uint8_t command; + + nand_debug(NDBG_GEN,"cache read page %x[%x] ", page, len); + chip = device_get_softc(dev); + nandbus = device_get_parent(dev); + + if (nand_check_page_boundary(chip, page)) + return (ENXIO); + + page_to_row(&chip->chip_geom, page, &row); + + if (page != -1) { + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_READ)) + return (ENXIO); + + if (NANDBUS_SEND_ADDRESS(nandbus, row, col, -1)) + return (ENXIO); + } + + if (end) + command = NAND_CMD_READ_CACHE_END; + else + command = NAND_CMD_READ_CACHE; + + if (NANDBUS_SEND_COMMAND(nandbus, command)) + return (ENXIO); + + if (NANDBUS_START_COMMAND(nandbus)) + return (ENXIO); + + DELAY(chip->t_r); + if (check_fail(nandbus)) + return (ENXIO); + + if (buf != NULL && len > 0) + NANDBUS_READ_BUFFER(nandbus, buf, len); + + pg_stat = &(chip->pg_stat[page]); + pg_stat->page_raw_read++; + + return (0); +} + +int +nand_get_feature(device_t dev, uint8_t feat, void *buf) +{ + struct nand_chip *chip; + device_t nandbus; + + nand_debug(NDBG_GEN,"nand get feature"); + + chip = device_get_softc(dev); + nandbus = device_get_parent(dev); + + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_GET_FEATURE)) + return (ENXIO); + + if (NANDBUS_SEND_ADDRESS(nandbus, -1, -1, feat)) + return (ENXIO); + + if (NANDBUS_START_COMMAND(nandbus)) + return (ENXIO); + + DELAY(chip->t_r); + NANDBUS_READ_BUFFER(nandbus, buf, 4); + + return (0); +} + +int +nand_set_feature(device_t dev, uint8_t feat, void *buf) +{ + struct nand_chip *chip; + device_t nandbus; + + nand_debug(NDBG_GEN,"nand set feature"); + + chip = device_get_softc(dev); + nandbus = device_get_parent(dev); + + if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_SET_FEATURE)) + return (ENXIO); + + if (NANDBUS_SEND_ADDRESS(nandbus, -1, -1, feat)) + return (ENXIO); + + NANDBUS_WRITE_BUFFER(nandbus, buf, 4); + + if (NANDBUS_START_COMMAND(nandbus)) + return (ENXIO); + + return (0); +} +#endif diff --git a/sys/dev/nand/nand_geom.c b/sys/dev/nand/nand_geom.c new file mode 100644 index 0000000..a8bdba2 --- /dev/null +++ b/sys/dev/nand/nand_geom.c @@ -0,0 +1,414 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <sys/uio.h> +#include <sys/bio.h> +#include <geom/geom.h> +#include <geom/geom_disk.h> + +#include <dev/nand/nand.h> +#include <dev/nand/nandbus.h> +#include <dev/nand/nand_dev.h> +#include "nand_if.h" +#include "nandbus_if.h" + +#define BIO_NAND_STD ((void *)1) +#define BIO_NAND_RAW ((void *)2) + +static disk_ioctl_t nand_ioctl; +static disk_getattr_t nand_getattr; +static disk_strategy_t nand_strategy; +static disk_strategy_t nand_strategy_raw; + +static int +nand_read(struct nand_chip *chip, uint32_t offset, void *buf, uint32_t len) +{ + + nand_debug(NDBG_GEOM, "Read from chip %d [%p] at %d", chip->num, chip, + offset); + + return (nand_read_pages(chip, offset, buf, len)); +} + +static int +nand_write(struct nand_chip *chip, uint32_t offset, void* buf, uint32_t len) +{ + + nand_debug(NDBG_GEOM, "Write to chip %d [%p] at %d", chip->num, chip, + offset); + + return (nand_prog_pages(chip, offset, buf, len)); +} + +static int +nand_read_raw(struct nand_chip *chip, uint32_t offset, void *buf, uint32_t len) +{ + nand_debug(NDBG_GEOM, "Raw read from chip %d [%p] at %d", chip->num, + chip, offset); + + return (nand_read_pages_raw(chip, offset, buf, len)); +} + +static int +nand_write_raw(struct nand_chip *chip, uint32_t offset, void *buf, uint32_t len) +{ + + nand_debug(NDBG_GEOM, "Raw write to chip %d [%p] at %d", chip->num, + chip, offset); + + return (nand_prog_pages_raw(chip, offset, buf, len)); +} + +static void +nand_strategy(struct bio *bp) +{ + struct nand_chip *chip; + + chip = (struct nand_chip *)bp->bio_disk->d_drv1; + + bp->bio_driver1 = BIO_NAND_STD; + + nand_debug(NDBG_GEOM, "Strategy %s on chip %d [%p]", + (bp->bio_cmd & BIO_READ) == BIO_READ ? "READ" : + ((bp->bio_cmd & BIO_WRITE) == BIO_WRITE ? "WRITE" : + ((bp->bio_cmd & BIO_DELETE) == BIO_DELETE ? "DELETE" : "UNKNOWN")), + chip->num, chip); + + mtx_lock(&chip->qlock); + bioq_insert_tail(&chip->bioq, bp); + mtx_unlock(&chip->qlock); + taskqueue_enqueue(chip->tq, &chip->iotask); +} + +static void +nand_strategy_raw(struct bio *bp) +{ + struct nand_chip *chip; + + chip = (struct nand_chip *)bp->bio_disk->d_drv1; + + /* Inform taskqueue that it's a raw access */ + bp->bio_driver1 = BIO_NAND_RAW; + + nand_debug(NDBG_GEOM, "Strategy %s on chip %d [%p]", + (bp->bio_cmd & BIO_READ) == BIO_READ ? "READ" : + ((bp->bio_cmd & BIO_WRITE) == BIO_WRITE ? "WRITE" : + ((bp->bio_cmd & BIO_DELETE) == BIO_DELETE ? "DELETE" : "UNKNOWN")), + chip->num, chip); + + mtx_lock(&chip->qlock); + bioq_insert_tail(&chip->bioq, bp); + mtx_unlock(&chip->qlock); + taskqueue_enqueue(chip->tq, &chip->iotask); +} + +static int +nand_oob_access(struct nand_chip *chip, uint32_t page, uint32_t offset, + uint32_t len, uint8_t *data, uint8_t write) +{ + struct chip_geom *cg; + int ret = 0; + + cg = &chip->chip_geom; + + if (!write) + ret = nand_read_oob(chip, page, data, cg->oob_size); + else + ret = nand_prog_oob(chip, page, data, cg->oob_size); + + return (ret); +} + +static int +nand_getattr(struct bio *bp) +{ + struct nand_chip *chip; + struct chip_geom *cg; + device_t dev; + + if (bp->bio_disk == NULL || bp->bio_disk->d_drv1 == NULL) + return (ENXIO); + + chip = (struct nand_chip *)bp->bio_disk->d_drv1; + cg = &(chip->chip_geom); + + dev = device_get_parent(chip->dev); + dev = device_get_parent(dev); + + do { + if (g_handleattr_int(bp, "NAND::oobsize", cg->oob_size)) + break; + else if (g_handleattr_int(bp, "NAND::pagesize", cg->page_size)) + break; + else if (g_handleattr_int(bp, "NAND::blocksize", + cg->block_size)) + break; + else if (g_handleattr(bp, "NAND::device", &(dev), + sizeof(device_t))) + break; + + return (ERESTART); + } while (0); + + return (EJUSTRETURN); +} + +static int +nand_ioctl(struct disk *ndisk, u_long cmd, void *data, int fflag, + struct thread *td) +{ + struct nand_chip *chip; + struct nand_oob_rw *oob_rw = NULL; + struct nand_raw_rw *raw_rw = NULL; + device_t nandbus; + uint8_t *buf = NULL; + int ret = 0; + uint8_t status; + + chip = (struct nand_chip *)ndisk->d_drv1; + nandbus = device_get_parent(chip->dev); + + if ((cmd == NAND_IO_RAW_READ) || (cmd == NAND_IO_RAW_PROG)) { + raw_rw = (struct nand_raw_rw *)data; + buf = malloc(raw_rw->len, M_NAND, M_WAITOK); + } + switch (cmd) { + case NAND_IO_ERASE: + ret = nand_erase_blocks(chip, ((off_t *)data)[0], + ((off_t *)data)[1]); + break; + + case NAND_IO_OOB_READ: + oob_rw = (struct nand_oob_rw *)data; + ret = nand_oob_access(chip, oob_rw->page, 0, + oob_rw->len, oob_rw->data, 0); + break; + + case NAND_IO_OOB_PROG: + oob_rw = (struct nand_oob_rw *)data; + ret = nand_oob_access(chip, oob_rw->page, 0, + oob_rw->len, oob_rw->data, 1); + break; + + case NAND_IO_GET_STATUS: + NANDBUS_LOCK(nandbus); + ret = NANDBUS_GET_STATUS(nandbus, &status); + if (ret == 0) + *(uint8_t *)data = status; + NANDBUS_UNLOCK(nandbus); + break; + + case NAND_IO_RAW_PROG: + copyin(raw_rw->data, buf, raw_rw->len); + ret = nand_prog_pages_raw(chip, raw_rw->off, buf, + raw_rw->len); + break; + + case NAND_IO_RAW_READ: + ret = nand_read_pages_raw(chip, raw_rw->off, buf, + raw_rw->len); + copyout(buf, raw_rw->data, raw_rw->len); + break; + + case NAND_IO_GET_CHIP_PARAM: + nand_get_chip_param(chip, (struct chip_param_io *)data); + break; + + default: + printf("Unknown nand_ioctl request \n"); + ret = EIO; + } + + if (buf) + free(buf, M_NAND); + + return (ret); +} + +static void +nand_io_proc(void *arg, int pending) +{ + struct nand_chip *chip = arg; + struct bio *bp; + int err = 0; + + for (;;) { + mtx_lock(&chip->qlock); + bp = bioq_takefirst(&chip->bioq); + mtx_unlock(&chip->qlock); + if (bp == NULL) + break; + + if (bp->bio_driver1 == BIO_NAND_STD) { + if ((bp->bio_cmd & BIO_READ) == BIO_READ) { + err = nand_read(chip, + bp->bio_offset & 0xffffffff, + bp->bio_data, bp->bio_bcount); + } else if ((bp->bio_cmd & BIO_WRITE) == BIO_WRITE) { + err = nand_write(chip, + bp->bio_offset & 0xffffffff, + bp->bio_data, bp->bio_bcount); + } + } else if (bp->bio_driver1 == BIO_NAND_RAW) { + if ((bp->bio_cmd & BIO_READ) == BIO_READ) { + err = nand_read_raw(chip, + bp->bio_offset & 0xffffffff, + bp->bio_data, bp->bio_bcount); + } else if ((bp->bio_cmd & BIO_WRITE) == BIO_WRITE) { + err = nand_write_raw(chip, + bp->bio_offset & 0xffffffff, + bp->bio_data, bp->bio_bcount); + } + } else + panic("Unknown access type in bio->bio_driver1\n"); + + if ((bp->bio_cmd & BIO_DELETE) == BIO_DELETE) { + nand_debug(NDBG_GEOM, "Delete on chip%d offset %lld " + "length %ld\n", chip->num, bp->bio_offset, + bp->bio_bcount); + err = nand_erase_blocks(chip, + bp->bio_offset & 0xffffffff, + bp->bio_bcount); + } + + if (err == 0 || err == ECC_CORRECTABLE) + bp->bio_resid = 0; + else { + nand_debug(NDBG_GEOM,"nand_[read|write|erase_blocks] " + "error: %d\n", err); + + bp->bio_error = EIO; + bp->bio_flags |= BIO_ERROR; + bp->bio_resid = bp->bio_bcount; + } + biodone(bp); + } +} + +int +create_geom_disk(struct nand_chip *chip) +{ + struct disk *ndisk, *rdisk; + + /* Create the disk device */ + ndisk = disk_alloc(); + ndisk->d_strategy = nand_strategy; + ndisk->d_ioctl = nand_ioctl; + ndisk->d_getattr = nand_getattr; + ndisk->d_name = "gnand"; + ndisk->d_drv1 = chip; + ndisk->d_maxsize = chip->chip_geom.block_size; + ndisk->d_sectorsize = chip->chip_geom.page_size; + ndisk->d_mediasize = chip->chip_geom.chip_size; + ndisk->d_unit = chip->num + + 10 * device_get_unit(device_get_parent(chip->dev)); + + /* + * When using BBT, make two last blocks of device unavailable + * to user (because those are used to store BBT table). + */ + if (chip->bbt != NULL) + ndisk->d_mediasize -= (2 * chip->chip_geom.block_size); + + ndisk->d_flags = DISKFLAG_CANDELETE; + + snprintf(ndisk->d_ident, sizeof(ndisk->d_ident), + "nand: Man:0x%02x Dev:0x%02x", chip->id.man_id, chip->id.dev_id); + + disk_create(ndisk, DISK_VERSION); + + /* Create the RAW disk device */ + rdisk = disk_alloc(); + rdisk->d_strategy = nand_strategy_raw; + rdisk->d_ioctl = nand_ioctl; + rdisk->d_getattr = nand_getattr; + rdisk->d_name = "gnand.raw"; + rdisk->d_drv1 = chip; + rdisk->d_maxsize = chip->chip_geom.block_size; + rdisk->d_sectorsize = chip->chip_geom.page_size; + rdisk->d_mediasize = chip->chip_geom.chip_size; + rdisk->d_unit = chip->num + + 10 * device_get_unit(device_get_parent(chip->dev)); + + rdisk->d_flags = DISKFLAG_CANDELETE; + + snprintf(rdisk->d_ident, sizeof(rdisk->d_ident), + "nand_raw: Man:0x%02x Dev:0x%02x", chip->id.man_id, + chip->id.dev_id); + + disk_create(rdisk, DISK_VERSION); + + chip->ndisk = ndisk; + chip->rdisk = rdisk; + + mtx_init(&chip->qlock, "NAND I/O lock", NULL, MTX_DEF); + bioq_init(&chip->bioq); + + TASK_INIT(&chip->iotask, 0, nand_io_proc, chip); + chip->tq = taskqueue_create("nand_taskq", M_WAITOK, + taskqueue_thread_enqueue, &chip->tq); + taskqueue_start_threads(&chip->tq, 1, PI_DISK, "nand taskq"); + + if (bootverbose) + device_printf(chip->dev, "Created gnand%d for chip [0x%0x, " + "0x%0x]\n", ndisk->d_unit, chip->id.man_id, + chip->id.dev_id); + + return (0); +} + +void +destroy_geom_disk(struct nand_chip *chip) +{ + struct bio *bp; + + taskqueue_free(chip->tq); + disk_destroy(chip->ndisk); + disk_destroy(chip->rdisk); + + mtx_lock(&chip->qlock); + for (;;) { + bp = bioq_takefirst(&chip->bioq); + if (bp == NULL) + break; + bp->bio_error = EIO; + bp->bio_flags |= BIO_ERROR; + bp->bio_resid = bp->bio_bcount; + + biodone(bp); + } + mtx_unlock(&chip->qlock); + + mtx_destroy(&chip->qlock); +} diff --git a/sys/dev/nand/nand_id.c b/sys/dev/nand/nand_id.c new file mode 100644 index 0000000..75c5834 --- /dev/null +++ b/sys/dev/nand/nand_id.c @@ -0,0 +1,60 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> + +#include <dev/nand/nand.h> + +struct nand_params nand_ids[] = { + { { NAND_MAN_SAMSUNG, 0x75 }, "Samsung K9F5608U0B", + 0x20, 0x200, 0x10, 0x20, 0 }, + { { NAND_MAN_SAMSUNG, 0xd3 }, "Samsung NAND 1GiB 3,3V 8-bit", + 0x400, 0x800, 0x40, 0x40, 0 }, + { { NAND_MAN_HYNIX, 0x76 }, "Hynix NAND 64MiB 3,3V 8-bit", + 0x40, 0x200, 0x10, 0x20, 0 }, + { { NAND_MAN_HYNIX, 0xdc }, "Hynix NAND 512MiB 3,3V 8-bit", + 0x200, 0x800, 0x40, 0x40, 0 }, + { { NAND_MAN_HYNIX, 0x79 }, "NAND 128MB 3,3V 8-bit", + 0x80, 0x200, 0x10, 0x20, 0 }, + { { NAND_MAN_STMICRO, 0xf1 }, "STMicro 128MB 3,3V 8-bit", + 0x80, 2048, 64, 0x40, 0 }, +}; + +struct nand_params *nand_get_params(struct nand_id *id) +{ + int i; + + for (i = 0; i < sizeof(nand_ids) / sizeof(nand_ids[0]); i++) + if (nand_ids[i].id.man_id == id->man_id && + nand_ids[i].id.dev_id == id->dev_id) + return (&nand_ids[i]); + + return (NULL); +} diff --git a/sys/dev/nand/nand_if.m b/sys/dev/nand/nand_if.m new file mode 100644 index 0000000..49c8879 --- /dev/null +++ b/sys/dev/nand/nand_if.m @@ -0,0 +1,168 @@ +#- +# Copyright (C) 2009-2012 Semihalf +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. +# +# $FreeBSD$ + +# NAND chip interface description +# + +#include <sys/bus.h> +#include <dev/nand/nand.h> + +INTERFACE nand; + +CODE { + static int nand_method_not_supported(device_t dev) + { + return (ENOENT); + } +}; + +# Read NAND page +# +# Return values: +# 0: Success +# +METHOD int read_page { + device_t dev; + uint32_t page; + void* buf; + uint32_t len; + uint32_t offset; +}; + +# Program NAND page +# +# Return values: +# 0: Success +# +METHOD int program_page { + device_t dev; + uint32_t page; + void* buf; + uint32_t len; + uint32_t offset; +}; + +# Program NAND page interleaved +# +# Return values: +# 0: Success +# +METHOD int program_page_intlv { + device_t dev; + uint32_t page; + void* buf; + uint32_t len; + uint32_t offset; +} DEFAULT nand_method_not_supported; + +# Read NAND oob +# +# Return values: +# 0: Success +# +METHOD int read_oob { + device_t dev; + uint32_t page; + void* buf; + uint32_t len; + uint32_t offset; +}; + +# Program NAND oob +# +# Return values: +# 0: Success +# +METHOD int program_oob { + device_t dev; + uint32_t page; + void* buf; + uint32_t len; + uint32_t offset; +}; + +# Erase NAND block +# +# Return values: +# 0: Success +# +METHOD int erase_block { + device_t dev; + uint32_t block; +}; + +# Erase NAND block interleaved +# +# Return values: +# 0: Success +# +METHOD int erase_block_intlv { + device_t dev; + uint32_t block; +} DEFAULT nand_method_not_supported; + +# NAND get status +# +# Return values: +# 0: Success +# +METHOD int get_status { + device_t dev; + uint8_t *status; +}; + +# NAND check if block is bad +# +# Return values: +# 0: Success +# +METHOD int is_blk_bad { + device_t dev; + uint32_t block_number; + uint8_t *bad; +}; + +# NAND get ECC +# +# +METHOD int get_ecc { + device_t dev; + void *buf; + void *ecc; + int *needwrite; +}; + +# NAND correct ECC +# +# +METHOD int correct_ecc { + device_t dev; + void *buf; + void *readecc; + void *calcecc; +}; + diff --git a/sys/dev/nand/nandbus.c b/sys/dev/nand/nandbus.c new file mode 100644 index 0000000..322d708 --- /dev/null +++ b/sys/dev/nand/nandbus.c @@ -0,0 +1,530 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/proc.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> + +#include <dev/nand/nand.h> +#include <dev/nand/nandbus.h> +#include "nand_if.h" +#include "nandbus_if.h" +#include "nfc_if.h" + +#define NAND_NCS 4 + +static int nandbus_probe(device_t dev); +static int nandbus_attach(device_t dev); +static int nandbus_detach(device_t dev); + +static int nandbus_child_location_str(device_t, device_t, char *, size_t); +static int nandbus_child_pnpinfo_str(device_t, device_t, char *, size_t); + +static int nandbus_get_status(device_t, uint8_t *); +static void nandbus_read_buffer(device_t, void *, uint32_t); +static int nandbus_select_cs(device_t, uint8_t); +static int nandbus_send_command(device_t, uint8_t); +static int nandbus_send_address(device_t, uint8_t); +static int nandbus_start_command(device_t); +static int nandbus_wait_ready(device_t, uint8_t *); +static void nandbus_write_buffer(device_t, void *, uint32_t); +static int nandbus_get_ecc(device_t, void *, uint32_t, void *, int *); +static int nandbus_correct_ecc(device_t, void *, int, void *, void *); +static void nandbus_lock(device_t); +static void nandbus_unlock(device_t); + +static int nand_readid(device_t, uint8_t *, uint8_t *); +static int nand_probe_onfi(device_t, uint8_t *); +static int nand_reset(device_t); + +struct nandbus_softc { + device_t dev; + struct cv nandbus_cv; + struct mtx nandbus_mtx; + uint8_t busy; +}; + +static device_method_t nandbus_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, nandbus_probe), + DEVMETHOD(device_attach, nandbus_attach), + DEVMETHOD(device_detach, nandbus_detach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + DEVMETHOD(bus_child_pnpinfo_str, nandbus_child_pnpinfo_str), + DEVMETHOD(bus_child_location_str, nandbus_child_location_str), + + /* nandbus interface */ + DEVMETHOD(nandbus_get_status, nandbus_get_status), + DEVMETHOD(nandbus_read_buffer, nandbus_read_buffer), + DEVMETHOD(nandbus_select_cs, nandbus_select_cs), + DEVMETHOD(nandbus_send_command, nandbus_send_command), + DEVMETHOD(nandbus_send_address, nandbus_send_address), + DEVMETHOD(nandbus_start_command,nandbus_start_command), + DEVMETHOD(nandbus_wait_ready, nandbus_wait_ready), + DEVMETHOD(nandbus_write_buffer, nandbus_write_buffer), + DEVMETHOD(nandbus_get_ecc, nandbus_get_ecc), + DEVMETHOD(nandbus_correct_ecc, nandbus_correct_ecc), + DEVMETHOD(nandbus_lock, nandbus_lock), + DEVMETHOD(nandbus_unlock, nandbus_unlock), + { 0, 0 } +}; + +devclass_t nandbus_devclass; + +driver_t nandbus_driver = { + "nandbus", + nandbus_methods, + sizeof(struct nandbus_softc) +}; + +DRIVER_MODULE(nandbus, nand, nandbus_driver, nandbus_devclass, 0, 0); + +int +nandbus_create(device_t nfc) +{ + device_t child; + + child = device_add_child(nfc, "nandbus", -1); + if (!child) + return (ENODEV); + + bus_generic_attach(nfc); + + return(0); +} + +void +nandbus_destroy(device_t nfc) +{ + device_t *children; + int nchildren, i; + + mtx_lock(&Giant); + /* Detach & delete all children */ + if (!device_get_children(nfc, &children, &nchildren)) { + for (i = 0; i < nchildren; i++) + device_delete_child(nfc, children[i]); + + free(children, M_TEMP); + } + mtx_unlock(&Giant); +} + +static int +nandbus_probe(device_t dev) +{ + + device_set_desc(dev, "NAND bus"); + + return (0); +} + +static int +nandbus_attach(device_t dev) +{ + device_t child, nfc; + struct nand_id chip_id; + struct nandbus_softc *sc; + struct nandbus_ivar *ivar; + struct nand_softc *nfc_sc; + struct nand_params *chip_params; + uint8_t cs, onfi; + + sc = device_get_softc(dev); + sc->dev = dev; + + nfc = device_get_parent(dev); + nfc_sc = device_get_softc(nfc); + + mtx_init(&sc->nandbus_mtx, "nandbus lock", MTX_DEF, 0); + cv_init(&sc->nandbus_cv, "nandbus cv"); + + /* Check each possible CS for existing nand devices */ + for (cs = 0; cs < NAND_NCS; cs++) { + nand_debug(NDBG_BUS,"probe chip select %x", cs); + + /* Select & reset chip */ + if (nandbus_select_cs(dev, cs)) + break; + + if (nand_reset(dev)) + continue; + + /* Read manufacturer and device id */ + if (nand_readid(dev, &chip_id.man_id, &chip_id.dev_id)) + continue; + + if (chip_id.man_id == 0xff) + continue; + + /* Check if chip is ONFI compliant */ + if (nand_probe_onfi(dev, &onfi) != 0) { + continue; + } + + ivar = malloc(sizeof(struct nandbus_ivar), + M_NAND, M_WAITOK); + + if (onfi == 1) { + ivar->cs = cs; + ivar->cols = 0; + ivar->rows = 0; + ivar->params = NULL; + ivar->man_id = chip_id.man_id; + ivar->dev_id = chip_id.dev_id; + ivar->is_onfi = onfi; + ivar->chip_cdev_name = nfc_sc->chip_cdev_name; + + child = device_add_child(dev, NULL, -1); + device_set_ivars(child, ivar); + continue; + } + + chip_params = nand_get_params(&chip_id); + if (chip_params == NULL) { + nand_debug(NDBG_BUS,"Chip description not found! " + "(manuf: 0x%0x, chipid: 0x%0x)\n", + chip_id.man_id, chip_id.dev_id); + free(ivar, M_NAND); + continue; + } + + ivar->cs = cs; + ivar->cols = 1; + ivar->rows = 2; + ivar->params = chip_params; + ivar->man_id = chip_id.man_id; + ivar->dev_id = chip_id.dev_id; + ivar->is_onfi = onfi; + ivar->chip_cdev_name = nfc_sc->chip_cdev_name; + + /* + * Check what type of device we have. + * devices bigger than 32MiB have on more row (3) + */ + if (chip_params->chip_size > 32) + ivar->rows++; + /* Large page devices have one more col (2) */ + if (chip_params->chip_size >= 128 && + chip_params->page_size > 512) + ivar->cols++; + + child = device_add_child(dev, NULL, -1); + device_set_ivars(child, ivar); + } + + bus_generic_attach(dev); + return (0); +} + +static int +nandbus_detach(device_t dev) +{ + struct nandbus_softc *sc; + + sc = device_get_softc(dev); + + bus_generic_detach(dev); + + mtx_destroy(&sc->nandbus_mtx); + cv_destroy(&sc->nandbus_cv); + + return (0); +} + +static int +nandbus_child_location_str(device_t bus, device_t child, char *buf, + size_t buflen) +{ + struct nandbus_ivar *ivar = device_get_ivars(child); + + snprintf(buf, buflen, "at cs#%d", ivar->cs); + return (0); +} + +static int +nandbus_child_pnpinfo_str(device_t bus, device_t child, char *buf, + size_t buflen) +{ + // XXX man id, model id ???? + *buf = '\0'; + return (0); +} + +static int +nand_readid(device_t bus, uint8_t *man_id, uint8_t *dev_id) +{ + device_t nfc; + + if (!bus || !man_id || !dev_id) + return (EINVAL); + + nand_debug(NDBG_BUS,"read id"); + + nfc = device_get_parent(bus); + + if (NFC_SEND_COMMAND(nfc, NAND_CMD_READ_ID)) { + nand_debug(NDBG_BUS,"Error : could not send READ ID command"); + return (ENXIO); + } + + if (NFC_SEND_ADDRESS(nfc, 0)) { + nand_debug(NDBG_BUS,"Error : could not sent address to chip"); + return (ENXIO); + } + + if (NFC_START_COMMAND(nfc) != 0) { + nand_debug(NDBG_BUS,"Error : could not start command"); + return (ENXIO); + } + + DELAY(25); + + *man_id = NFC_READ_BYTE(nfc); + *dev_id = NFC_READ_BYTE(nfc); + + nand_debug(NDBG_BUS,"manufacturer id: %x chip id: %x", *man_id, + *dev_id); + + return (0); +} + +static int +nand_probe_onfi(device_t bus, uint8_t *onfi_compliant) +{ + device_t nfc; + char onfi_id[] = {'o', 'n', 'f', 'i', '\0'}; + int i; + + nand_debug(NDBG_BUS,"probing ONFI"); + + nfc = device_get_parent(bus); + + if (NFC_SEND_COMMAND(nfc, NAND_CMD_READ_ID)) { + nand_debug(NDBG_BUS,"Error : could not sent READ ID command"); + return (ENXIO); + } + + if (NFC_SEND_ADDRESS(nfc, ONFI_SIG_ADDR)) { + nand_debug(NDBG_BUS,"Error : could not sent address to chip"); + return (ENXIO); + } + + if (NFC_START_COMMAND(nfc) != 0) { + nand_debug(NDBG_BUS,"Error : could not start command"); + return (ENXIO); + } + for (i = 0; onfi_id[i] != '\0'; i++) + if (NFC_READ_BYTE(nfc) != onfi_id[i]) { + nand_debug(NDBG_BUS,"ONFI non-compliant"); + *onfi_compliant = 0; + return (0); + } + + nand_debug(NDBG_BUS,"ONFI compliant"); + *onfi_compliant = 1; + + return (0); +} + +static int +nand_reset(device_t bus) +{ + device_t nfc; + nand_debug(NDBG_BUS,"resetting..."); + + nfc = device_get_parent(bus); + + if (NFC_SEND_COMMAND(nfc, NAND_CMD_RESET) != 0) { + nand_debug(NDBG_BUS,"Error : could not sent RESET command"); + return (ENXIO); + } + + if (NFC_START_COMMAND(nfc) != 0) { + nand_debug(NDBG_BUS,"Error : could not start RESET command"); + return (ENXIO); + } + + DELAY(1000); + + return (0); +} + +void +nandbus_lock(device_t dev) +{ + struct nandbus_softc *sc; + + sc = device_get_softc(dev); + + mtx_lock(&sc->nandbus_mtx); + if (sc->busy) + cv_wait(&sc->nandbus_cv, &sc->nandbus_mtx); + sc->busy = 1; + mtx_unlock(&sc->nandbus_mtx); +} + +void +nandbus_unlock(device_t dev) +{ + struct nandbus_softc *sc; + + sc = device_get_softc(dev); + + mtx_lock(&sc->nandbus_mtx); + sc->busy = 0; + cv_signal(&sc->nandbus_cv); + mtx_unlock(&sc->nandbus_mtx); +} + +int +nandbus_select_cs(device_t dev, uint8_t cs) +{ + + return (NFC_SELECT_CS(device_get_parent(dev), cs)); +} + +int +nandbus_send_command(device_t dev, uint8_t command) +{ + int err; + + if ((err = NFC_SEND_COMMAND(device_get_parent(dev), command))) + nand_debug(NDBG_BUS,"Err: Could not send command %x, err %x", + command, err); + + return (err); +} + +int +nandbus_send_address(device_t dev, uint8_t address) +{ + int err; + + if ((err = NFC_SEND_ADDRESS(device_get_parent(dev), address))) + nand_debug(NDBG_BUS,"Err: Could not send address %x, err %x", + address, err); + + return (err); +} + +int +nandbus_start_command(device_t dev) +{ + int err; + + if ((err = NFC_START_COMMAND(device_get_parent(dev)))) + nand_debug(NDBG_BUS,"Err: Could not start command, err %x", + err); + + return (err); +} + +void +nandbus_read_buffer(device_t dev, void *buf, uint32_t len) +{ + + NFC_READ_BUF(device_get_parent(dev), buf, len); +} + +void +nandbus_write_buffer(device_t dev, void *buf, uint32_t len) +{ + + NFC_WRITE_BUF(device_get_parent(dev), buf, len); +} + +int +nandbus_get_status(device_t dev, uint8_t *status) +{ + int err; + + if ((err = NANDBUS_SEND_COMMAND(dev, NAND_CMD_STATUS))) + return (err); + if ((err = NANDBUS_START_COMMAND(dev))) + return (err); + + *status = NFC_READ_BYTE(device_get_parent(dev)); + + return (0); +} + +int +nandbus_wait_ready(device_t dev, uint8_t *status) +{ + struct timeval tv, tv2; + + tv2.tv_sec = 0; + tv2.tv_usec = 50 * 5000; /* 10ms */ + + getmicrotime(&tv); + timevaladd(&tv, &tv2); + + do { + if (NANDBUS_GET_STATUS(dev, status)) + return (ENXIO); + + if (*status & NAND_STATUS_RDY) + return (0); + + getmicrotime(&tv2); + } while (timevalcmp(&tv2, &tv, <=)); + + return (EBUSY); +} + +int +nandbus_get_ecc(device_t dev, void *buf, uint32_t pagesize, void *ecc, + int *needwrite) +{ + + return (NFC_GET_ECC(device_get_parent(dev), buf, pagesize, ecc, needwrite)); +} + +int +nandbus_correct_ecc(device_t dev, void *buf, int pagesize, void *readecc, + void *calcecc) +{ + + return (NFC_CORRECT_ECC(device_get_parent(dev), buf, pagesize, + readecc, calcecc)); +} + diff --git a/sys/dev/nand/nandbus.h b/sys/dev/nand/nandbus.h new file mode 100644 index 0000000..417fbf5 --- /dev/null +++ b/sys/dev/nand/nandbus.h @@ -0,0 +1,49 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +#ifndef _NANDBUS_H_ +#define _NANDBUS_H_ + +struct nandbus_ivar { + uint8_t cs; + uint8_t cols; + uint8_t rows; + uint8_t man_id; + uint8_t dev_id; + uint8_t is_onfi; + char *chip_cdev_name; + struct nand_params *params; +}; + +extern devclass_t nandbus_devclass; +extern driver_t nandbus_driver; + +int nandbus_create(device_t nfc); +void nandbus_destroy(device_t nfc); + +#endif /* _NANDBUS_H_ */ diff --git a/sys/dev/nand/nandbus_if.m b/sys/dev/nand/nandbus_if.m new file mode 100644 index 0000000..e914e18 --- /dev/null +++ b/sys/dev/nand/nandbus_if.m @@ -0,0 +1,100 @@ +#- +# Copyright (C) 2009-2012 Semihalf +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. +# +# $FreeBSD$ + +# NAND bus interface description +# + +#include <sys/bus.h> +#include <dev/nand/nand.h> + +INTERFACE nandbus; + +METHOD int get_status { + device_t dev; + uint8_t * status; +}; + +METHOD void read_buffer { + device_t dev; + void * buf; + uint32_t len; +}; + +METHOD int select_cs { + device_t dev; + uint8_t cs; +}; + +METHOD int send_command { + device_t dev; + uint8_t command; +}; + +METHOD int send_address { + device_t dev; + uint8_t address; +}; + +METHOD int start_command { + device_t dev; +}; + +METHOD int wait_ready { + device_t dev; + uint8_t * status; +} + +METHOD void write_buffer { + device_t dev; + void * buf; + uint32_t len; +}; + +METHOD int get_ecc { + device_t dev; + void * buf; + uint32_t pagesize; + void * ecc; + int * needwrite; +}; + +METHOD int correct_ecc { + device_t dev; + void * buf; + int pagesize; + void * readecc; + void * calcecc; +}; + +METHOD void lock { + device_t dev; +}; + +METHOD void unlock { + device_t dev; +}; + diff --git a/sys/dev/nand/nandsim.c b/sys/dev/nand/nandsim.c new file mode 100644 index 0000000..5390c01 --- /dev/null +++ b/sys/dev/nand/nandsim.c @@ -0,0 +1,665 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + */ + +/* Simulated NAND controller driver */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/proc.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/malloc.h> + +#include <dev/nand/nand.h> +#include <dev/nand/nandsim.h> +#include <dev/nand/nandsim_chip.h> +#include <dev/nand/nandsim_log.h> +#include <dev/nand/nandsim_swap.h> + +struct sim_param sim; +struct sim_ctrl_conf ctrls[MAX_SIM_DEV]; + +static struct cdev *nandsim_dev; +static d_ioctl_t nandsim_ioctl; + +static void nandsim_init_sim_param(struct sim_param *); +static int nandsim_create_ctrl(struct sim_ctrl *); +static int nandsim_destroy_ctrl(int); +static int nandsim_ctrl_status(struct sim_ctrl *); +static int nandsim_create_chip(struct sim_chip *); +static int nandsim_destroy_chip(struct sim_ctrl_chip *); +static int nandsim_chip_status(struct sim_chip *); +static int nandsim_start_ctrl(int); +static int nandsim_stop_ctrl(int); +static int nandsim_inject_error(struct sim_error *); +static int nandsim_get_block_state(struct sim_block_state *); +static int nandsim_set_block_state(struct sim_block_state *); +static int nandsim_modify(struct sim_mod *); +static int nandsim_dump(struct sim_dump *); +static int nandsim_restore(struct sim_dump *); +static int nandsim_freeze(struct sim_ctrl_chip *); +static void nandsim_print_log(struct sim_log *); +static struct nandsim_chip *get_nandsim_chip(uint8_t, uint8_t); + +static struct cdevsw nandsim_cdevsw = { + .d_version = D_VERSION, + .d_ioctl = nandsim_ioctl, + .d_name = "nandsim", +}; + +int +nandsim_ioctl(struct cdev *dev, u_long cmd, caddr_t data, + int flags, struct thread *td) +{ + int ret = 0; + + switch (cmd) { + case NANDSIM_SIM_PARAM: + nandsim_init_sim_param((struct sim_param *)data); + break; + case NANDSIM_CREATE_CTRL: + ret = nandsim_create_ctrl((struct sim_ctrl *)data); + break; + case NANDSIM_DESTROY_CTRL: + ret = nandsim_destroy_ctrl(*(int *)data); + break; + case NANDSIM_STATUS_CTRL: + ret = nandsim_ctrl_status((struct sim_ctrl *)data); + break; + case NANDSIM_CREATE_CHIP: + ret = nandsim_create_chip((struct sim_chip *)data); + break; + case NANDSIM_DESTROY_CHIP: + ret = nandsim_destroy_chip((struct sim_ctrl_chip *)data); + break; + case NANDSIM_STATUS_CHIP: + ret = nandsim_chip_status((struct sim_chip *)data); + break; + case NANDSIM_MODIFY: + ret = nandsim_modify((struct sim_mod *)data); + break; + case NANDSIM_START_CTRL: + ret = nandsim_start_ctrl(*(int *)data); + break; + case NANDSIM_STOP_CTRL: + ret = nandsim_stop_ctrl(*(int *)data); + break; + case NANDSIM_INJECT_ERROR: + ret = nandsim_inject_error((struct sim_error *)data); + break; + case NANDSIM_SET_BLOCK_STATE: + ret = nandsim_set_block_state((struct sim_block_state *)data); + break; + case NANDSIM_GET_BLOCK_STATE: + ret = nandsim_get_block_state((struct sim_block_state *)data); + break; + case NANDSIM_PRINT_LOG: + nandsim_print_log((struct sim_log *)data); + break; + case NANDSIM_DUMP: + ret = nandsim_dump((struct sim_dump *)data); + break; + case NANDSIM_RESTORE: + ret = nandsim_restore((struct sim_dump *)data); + break; + case NANDSIM_FREEZE: + ret = nandsim_freeze((struct sim_ctrl_chip *)data); + break; + default: + ret = EINVAL; + break; + } + + return (ret); +} + +static void +nandsim_init_sim_param(struct sim_param *param) +{ + + if (!param) + return; + + nand_debug(NDBG_SIM,"log level:%d output %d", param->log_level, + param->log_output); + nandsim_log_level = param->log_level; + nandsim_log_output = param->log_output; +} + +static int +nandsim_create_ctrl(struct sim_ctrl *ctrl) +{ + struct sim_ctrl_conf *sim_ctrl; + + nand_debug(NDBG_SIM,"create controller num:%d cs:%d",ctrl->num, + ctrl->num_cs); + + if (ctrl->num >= MAX_SIM_DEV) { + return (EINVAL); + } + + sim_ctrl = &ctrls[ctrl->num]; + if(sim_ctrl->created) + return (EEXIST); + + sim_ctrl->num = ctrl->num; + sim_ctrl->num_cs = ctrl->num_cs; + sim_ctrl->ecc = ctrl->ecc; + memcpy(sim_ctrl->ecc_layout, ctrl->ecc_layout, + MAX_ECC_BYTES * sizeof(ctrl->ecc_layout[0])); + strlcpy(sim_ctrl->filename, ctrl->filename, + FILENAME_SIZE); + sim_ctrl->created = 1; + + return (0); +} + +static int +nandsim_destroy_ctrl(int ctrl_num) +{ + + nand_debug(NDBG_SIM,"destroy controller num:%d", ctrl_num); + + if (ctrl_num >= MAX_SIM_DEV) { + return (EINVAL); + } + + if (!ctrls[ctrl_num].created) { + return (ENODEV); + } + + if (ctrls[ctrl_num].running) { + return (EBUSY); + } + + memset(&ctrls[ctrl_num], 0, sizeof(ctrls[ctrl_num])); + + return (0); +} + +static int +nandsim_ctrl_status(struct sim_ctrl *ctrl) +{ + + nand_debug(NDBG_SIM,"status controller num:%d cs:%d",ctrl->num, + ctrl->num_cs); + + if (ctrl->num >= MAX_SIM_DEV) { + return (EINVAL); + } + + ctrl->num_cs = ctrls[ctrl->num].num_cs; + ctrl->ecc = ctrls[ctrl->num].ecc; + memcpy(ctrl->ecc_layout, ctrls[ctrl->num].ecc_layout, + MAX_ECC_BYTES * sizeof(ctrl->ecc_layout[0])); + strlcpy(ctrl->filename, ctrls[ctrl->num].filename, + FILENAME_SIZE); + ctrl->running = ctrls[ctrl->num].running; + ctrl->created = ctrls[ctrl->num].created; + + return (0); +} + +static int +nandsim_create_chip(struct sim_chip *chip) +{ + struct sim_chip *sim_chip; + + nand_debug(NDBG_SIM,"create chip num:%d at ctrl:%d", chip->num, + chip->ctrl_num); + + if (chip->ctrl_num >= MAX_SIM_DEV || + chip->num >= MAX_CTRL_CS) { + return (EINVAL); + } + + if (ctrls[chip->ctrl_num].chips[chip->num]) { + return (EEXIST); + } + + sim_chip = malloc(sizeof(*sim_chip), M_NANDSIM, + M_WAITOK); + if (sim_chip == NULL) { + return (ENOMEM); + } + + memcpy(sim_chip, chip, sizeof(*sim_chip)); + ctrls[chip->ctrl_num].chips[chip->num] = sim_chip; + sim_chip->created = 1; + + return (0); +} + +static int +nandsim_destroy_chip(struct sim_ctrl_chip *chip) +{ + struct sim_ctrl_conf *ctrl_conf; + + nand_debug(NDBG_SIM,"destroy chip num:%d at ctrl:%d", chip->chip_num, + chip->ctrl_num); + + if (chip->ctrl_num >= MAX_SIM_DEV || + chip->chip_num >= MAX_CTRL_CS) + return (EINVAL); + + ctrl_conf = &ctrls[chip->ctrl_num]; + + if (!ctrl_conf->created || !ctrl_conf->chips[chip->chip_num]) + return (ENODEV); + + if (ctrl_conf->running) + return (EBUSY); + + free(ctrl_conf->chips[chip->chip_num], M_NANDSIM); + ctrl_conf->chips[chip->chip_num] = NULL; + + return (0); +} + +static int +nandsim_chip_status(struct sim_chip *chip) +{ + struct sim_ctrl_conf *ctrl_conf; + + nand_debug(NDBG_SIM,"status for chip num:%d at ctrl:%d", chip->num, + chip->ctrl_num); + + if (chip->ctrl_num >= MAX_SIM_DEV && + chip->num >= MAX_CTRL_CS) + return (EINVAL); + + ctrl_conf = &ctrls[chip->ctrl_num]; + if (!ctrl_conf->chips[chip->num]) + chip->created = 0; + else + memcpy(chip, ctrl_conf->chips[chip->num], sizeof(*chip)); + + return (0); +} + +static int +nandsim_start_ctrl(int num) +{ + device_t nexus, ndev; + devclass_t nexus_devclass; + int ret = 0; + + nand_debug(NDBG_SIM,"start ctlr num:%d", num); + + if (num >= MAX_SIM_DEV) + return (EINVAL); + + if (!ctrls[num].created) + return (ENODEV); + + if (ctrls[num].running) + return (EBUSY); + + /* We will add our device as a child of the nexus0 device */ + if (!(nexus_devclass = devclass_find("nexus")) || + !(nexus = devclass_get_device(nexus_devclass, 0))) + return (EFAULT); + + /* + * Create a newbus device representing this frontend instance + * + * XXX powerpc nexus doesn't implement bus_add_child, so child + * must be added by device_add_child(). + */ +#if defined(__powerpc__) + ndev = device_add_child(nexus, "nandsim", num); +#else + ndev = BUS_ADD_CHILD(nexus, 0, "nandsim", num); +#endif + if (!ndev) + return (EFAULT); + + mtx_lock(&Giant); + ret = device_probe_and_attach(ndev); + mtx_unlock(&Giant); + + if (ret == 0) { + ctrls[num].sim_ctrl_dev = ndev; + ctrls[num].running = 1; + } + + return (ret); +} + +static int +nandsim_stop_ctrl(int num) +{ + device_t nexus; + devclass_t nexus_devclass; + int ret = 0; + + nand_debug(NDBG_SIM,"stop controller num:%d", num); + + if (num >= MAX_SIM_DEV) { + return (EINVAL); + } + + if (!ctrls[num].created || !ctrls[num].running) { + return (ENODEV); + } + + /* We will add our device as a child of the nexus0 device */ + if (!(nexus_devclass = devclass_find("nexus")) || + !(nexus = devclass_get_device(nexus_devclass, 0))) { + return (ENODEV); + } + + mtx_lock(&Giant); + if (ctrls[num].sim_ctrl_dev) { + ret = device_delete_child(nexus, ctrls[num].sim_ctrl_dev); + ctrls[num].sim_ctrl_dev = NULL; + } + mtx_unlock(&Giant); + + ctrls[num].running = 0; + + return (ret); +} + +static struct nandsim_chip * +get_nandsim_chip(uint8_t ctrl_num, uint8_t chip_num) +{ + struct nandsim_softc *sc; + + if (!ctrls[ctrl_num].sim_ctrl_dev) + return (NULL); + + sc = device_get_softc(ctrls[ctrl_num].sim_ctrl_dev); + return (sc->chips[chip_num]); +} + +static void +nandsim_print_log(struct sim_log *sim_log) +{ + struct nandsim_softc *sc; + int len1, len2; + + if (!ctrls[sim_log->ctrl_num].sim_ctrl_dev) + return; + + sc = device_get_softc(ctrls[sim_log->ctrl_num].sim_ctrl_dev); + if (sc->log_buff) { + len1 = strlen(&sc->log_buff[sc->log_idx + 1]); + if (len1 >= sim_log->len) + len1 = sim_log->len; + copyout(&sc->log_buff[sc->log_idx + 1], sim_log->log, len1); + len2 = strlen(sc->log_buff); + if (len2 >= (sim_log->len - len1)) + len2 = (sim_log->len - len1); + copyout(sc->log_buff, &sim_log->log[len1], len2); + sim_log->len = len1 + len2; + } +} + +static int +nandsim_inject_error(struct sim_error *error) +{ + struct nandsim_chip *chip; + struct block_space *bs; + struct onfi_params *param; + int page, page_size, block, offset; + + nand_debug(NDBG_SIM,"inject error for chip %d at ctrl %d\n", + error->chip_num, error->ctrl_num); + + if (error->ctrl_num >= MAX_SIM_DEV || + error->chip_num >= MAX_CTRL_CS) + return (EINVAL); + + if (!ctrls[error->ctrl_num].created || !ctrls[error->ctrl_num].running) + return (ENODEV); + + chip = get_nandsim_chip(error->ctrl_num, error->chip_num); + param = &chip->params; + page_size = param->bytes_per_page + param->spare_bytes_per_page; + block = error->page_num / param->pages_per_block; + page = error->page_num % param->pages_per_block; + + bs = get_bs(chip->swap, block, 1); + if (!bs) + return (EINVAL); + + offset = (page * page_size) + error->column; + memset(&bs->blk_ptr[offset], error->pattern, error->len); + + return (0); +} + +static int +nandsim_set_block_state(struct sim_block_state *bs) +{ + struct onfi_params *params; + struct nandsim_chip *chip; + int blocks; + + nand_debug(NDBG_SIM,"set block state for %d:%d block %d\n", + bs->chip_num, bs->ctrl_num, bs->block_num); + + if (bs->ctrl_num >= MAX_SIM_DEV || + bs->chip_num >= MAX_CTRL_CS) + return (EINVAL); + + chip = get_nandsim_chip(bs->ctrl_num, bs->chip_num); + params = &chip->params; + blocks = params->luns * params->blocks_per_lun; + + if (bs->block_num > blocks) + return (EINVAL); + + chip->blk_state[bs->block_num].is_bad = bs->state; + + if (bs->wearout >= 0) + chip->blk_state[bs->block_num].wear_lev = bs->wearout; + + return (0); +} + +static int +nandsim_get_block_state(struct sim_block_state *bs) +{ + struct onfi_params *params; + struct nandsim_chip *chip; + int blocks; + + if (bs->ctrl_num >= MAX_SIM_DEV || + bs->chip_num >= MAX_CTRL_CS) + return (EINVAL); + + nand_debug(NDBG_SIM,"get block state for %d:%d block %d\n", + bs->chip_num, bs->ctrl_num, bs->block_num); + + chip = get_nandsim_chip(bs->ctrl_num, bs->chip_num); + params = &chip->params; + blocks = params->luns * params->blocks_per_lun; + + if (bs->block_num > blocks) + return (EINVAL); + + bs->state = chip->blk_state[bs->block_num].is_bad; + bs->wearout = chip->blk_state[bs->block_num].wear_lev; + + return (0); +} + +static int +nandsim_dump(struct sim_dump *dump) +{ + struct nandsim_chip *chip; + struct block_space *bs; + int blk_size; + + nand_debug(NDBG_SIM,"dump chip %d %d\n", dump->ctrl_num, dump->chip_num); + + if (dump->ctrl_num >= MAX_SIM_DEV || + dump->chip_num >= MAX_CTRL_CS) + return (EINVAL); + + chip = get_nandsim_chip(dump->ctrl_num, dump->chip_num); + blk_size = chip->cg.block_size + + (chip->cg.oob_size * chip->cg.pgs_per_blk); + + bs = get_bs(chip->swap, dump->block_num, 0); + if (!bs) + return (EINVAL); + + if (dump->len > blk_size) + dump->len = blk_size; + + copyout(bs->blk_ptr, dump->data, dump->len); + + return (0); +} + +static int +nandsim_restore(struct sim_dump *dump) +{ + struct nandsim_chip *chip; + struct block_space *bs; + int blk_size; + + nand_debug(NDBG_SIM,"restore chip %d %d\n", dump->ctrl_num, + dump->chip_num); + + if (dump->ctrl_num >= MAX_SIM_DEV || + dump->chip_num >= MAX_CTRL_CS) + return (EINVAL); + + chip = get_nandsim_chip(dump->ctrl_num, dump->chip_num); + blk_size = chip->cg.block_size + + (chip->cg.oob_size * chip->cg.pgs_per_blk); + + bs = get_bs(chip->swap, dump->block_num, 1); + if (!bs) + return (EINVAL); + + if (dump->len > blk_size) + dump->len = blk_size; + + + copyin(dump->data, bs->blk_ptr, dump->len); + + return (0); +} + +static int +nandsim_freeze(struct sim_ctrl_chip *ctrl_chip) +{ + struct nandsim_chip *chip; + + if (ctrl_chip->ctrl_num >= MAX_SIM_DEV || + ctrl_chip->chip_num >= MAX_CTRL_CS) + return (EINVAL); + + chip = get_nandsim_chip(ctrl_chip->ctrl_num, ctrl_chip->chip_num); + nandsim_chip_freeze(chip); + + return (0); +} + +static int +nandsim_modify(struct sim_mod *mod) +{ + struct sim_chip *sim_conf = NULL; + struct nandsim_chip *sim_chip = NULL; + + nand_debug(NDBG_SIM,"modify ctlr %d chip %d", mod->ctrl_num, + mod->chip_num); + + if (mod->field != SIM_MOD_LOG_LEVEL) { + if (mod->ctrl_num >= MAX_SIM_DEV || + mod->chip_num >= MAX_CTRL_CS) + return (EINVAL); + + sim_conf = ctrls[mod->ctrl_num].chips[mod->chip_num]; + sim_chip = get_nandsim_chip(mod->ctrl_num, mod->chip_num); + } + + switch (mod->field) { + case SIM_MOD_LOG_LEVEL: + nandsim_log_level = mod->new_value; + break; + case SIM_MOD_ERASE_TIME: + sim_conf->erase_time = sim_chip->erase_delay = mod->new_value; + break; + case SIM_MOD_PROG_TIME: + sim_conf->prog_time = sim_chip->prog_delay = mod->new_value; + break; + case SIM_MOD_READ_TIME: + sim_conf->read_time = sim_chip->read_delay = mod->new_value; + break; + case SIM_MOD_ERROR_RATIO: + sim_conf->error_ratio = mod->new_value; + sim_chip->error_ratio = mod->new_value; + break; + default: + break; + } + + return (0); +} +static int +nandsim_modevent(module_t mod __unused, int type, void *data __unused) +{ + struct sim_ctrl_chip chip_ctrl; + int i, j; + + switch (type) { + case MOD_LOAD: + nandsim_dev = make_dev(&nandsim_cdevsw, 0, + UID_ROOT, GID_WHEEL, 0666, "nandsim.ioctl"); + break; + case MOD_UNLOAD: + for (i = 0; i < MAX_SIM_DEV; i++) { + nandsim_stop_ctrl(i); + chip_ctrl.ctrl_num = i; + for (j = 0; j < MAX_CTRL_CS; j++) { + chip_ctrl.chip_num = j; + nandsim_destroy_chip(&chip_ctrl); + } + nandsim_destroy_ctrl(i); + } + destroy_dev(nandsim_dev); + break; + case MOD_SHUTDOWN: + break; + default: + return (EOPNOTSUPP); + } + return (0); +} + +DEV_MODULE(nandsim, nandsim_modevent, NULL); +MODULE_VERSION(nandsim, 1); diff --git a/sys/dev/nand/nandsim.h b/sys/dev/nand/nandsim.h new file mode 100644 index 0000000..fbbb6d4 --- /dev/null +++ b/sys/dev/nand/nandsim.h @@ -0,0 +1,175 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +#ifndef _NANDSIM_H_ +#define _NANDSIM_H_ + +#include <sys/ioccom.h> +#include <sys/types.h> + +#define MAX_SIM_DEV 4 +#define MAX_CTRL_CS 4 +#define MAX_ECC_BYTES 512 +#define MAX_BAD_BLOCKS 512 +#define DEV_MODEL_STR_SIZE 21 +#define MAN_STR_SIZE 13 +#define FILENAME_SIZE 20 + +#define MAX_CHIPS (MAX_SIM_DEV*MAX_CTRL_CS) + +#define NANDSIM_OUTPUT_NONE 0x0 +#define NANDSIM_OUTPUT_CONSOLE 0x1 +#define NANDSIM_OUTPUT_RAM 0x2 +#define NANDSIM_OUTPUT_FILE 0x3 + +struct sim_ctrl_chip { + uint8_t ctrl_num; + uint8_t chip_num; +}; + +#define NANDSIM_BASE 'A' + +struct sim_param { + uint8_t log_level; + uint8_t log_output; +}; + +#define NANDSIM_SIM_PARAM _IOW(NANDSIM_BASE, 1, struct sim_param) + +struct sim_ctrl { + uint8_t running; + uint8_t created; + uint8_t num; + uint8_t num_cs; + uint8_t ecc; + char filename[FILENAME_SIZE]; + uint16_t ecc_layout[MAX_ECC_BYTES]; +}; +#define NANDSIM_CREATE_CTRL _IOW(NANDSIM_BASE, 2, struct sim_ctrl) +#define NANDSIM_DESTROY_CTRL _IOW(NANDSIM_BASE, 3, int) + +struct sim_chip { + uint8_t num; + uint8_t ctrl_num; + uint8_t created; + uint8_t device_id; + uint8_t manufact_id; + char device_model[DEV_MODEL_STR_SIZE]; + char manufacturer[MAN_STR_SIZE]; + uint8_t col_addr_cycles; + uint8_t row_addr_cycles; + uint8_t features; + uint8_t width; + uint32_t page_size; + uint32_t oob_size; + uint32_t pgs_per_blk; + uint32_t blks_per_lun; + uint32_t luns; + + uint32_t prog_time; + uint32_t erase_time; + uint32_t read_time; + uint32_t ccs_time; + + uint32_t error_ratio; + uint32_t wear_level; + uint32_t bad_block_map[MAX_BAD_BLOCKS]; + uint8_t is_wp; +}; + +#define NANDSIM_CREATE_CHIP _IOW(NANDSIM_BASE, 3, struct sim_chip) + +struct sim_chip_destroy { + uint8_t ctrl_num; + uint8_t chip_num; +}; +#define NANDSIM_DESTROY_CHIP _IOW(NANDSIM_BASE, 4, struct sim_chip_destroy) + +#define NANDSIM_START_CTRL _IOW(NANDSIM_BASE, 5, int) +#define NANDSIM_STOP_CTRL _IOW(NANDSIM_BASE, 6, int) +#define NANDSIM_RESTART_CTRL _IOW(NANDSIM_BASE, 7, int) + +#define NANDSIM_STATUS_CTRL _IOWR(NANDSIM_BASE, 8, struct sim_ctrl) +#define NANDSIM_STATUS_CHIP _IOWR(NANDSIM_BASE, 9, struct sim_chip) + +struct sim_mod { + uint8_t chip_num; + uint8_t ctrl_num; + uint32_t field; + uint32_t new_value; +}; +#define SIM_MOD_LOG_LEVEL 0 +#define SIM_MOD_ERASE_TIME 1 +#define SIM_MOD_PROG_TIME 2 +#define SIM_MOD_READ_TIME 3 +#define SIM_MOD_CCS_TIME 4 +#define SIM_MOD_ERROR_RATIO 5 + +#define NANDSIM_MODIFY _IOW(NANDSIM_BASE, 10, struct sim_mod) +#define NANDSIM_FREEZE _IOW(NANDSIM_BASE, 11, struct sim_ctrl_chip) + +struct sim_error { + uint8_t ctrl_num; + uint8_t chip_num; + uint32_t page_num; + uint32_t column; + uint32_t len; + uint32_t pattern; +}; +#define NANDSIM_INJECT_ERROR _IOW(NANDSIM_BASE, 20, struct sim_error) + +#define NANDSIM_GOOD_BLOCK 0 +#define NANDSIM_BAD_BLOCK 1 +struct sim_block_state { + uint8_t ctrl_num; + uint8_t chip_num; + uint32_t block_num; + int wearout; + uint8_t state; +}; +#define NANDSIM_SET_BLOCK_STATE _IOW(NANDSIM_BASE, 21, struct sim_block_state) +#define NANDSIM_GET_BLOCK_STATE _IOWR(NANDSIM_BASE, 22, struct sim_block_state) + +struct sim_log { + uint8_t ctrl_num; + char* log; + size_t len; +}; +#define NANDSIM_PRINT_LOG _IOWR(NANDSIM_BASE, 23, struct sim_log) + +struct sim_dump { + uint8_t ctrl_num; + uint8_t chip_num; + uint32_t block_num; + uint32_t len; + void* data; +}; +#define NANDSIM_DUMP _IOWR(NANDSIM_BASE, 24, struct sim_dump) +#define NANDSIM_RESTORE _IOWR(NANDSIM_BASE, 25, struct sim_dump) + +#endif /* _NANDSIM_H_ */ diff --git a/sys/dev/nand/nandsim_chip.c b/sys/dev/nand/nandsim_chip.c new file mode 100644 index 0000000..f04ad80 --- /dev/null +++ b/sys/dev/nand/nandsim_chip.c @@ -0,0 +1,901 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/proc.h> +#include <sys/sched.h> +#include <sys/kthread.h> +#include <sys/unistd.h> + +#include <dev/nand/nand.h> +#include <dev/nand/nandsim_chip.h> +#include <dev/nand/nandsim_log.h> +#include <dev/nand/nandsim_swap.h> + +MALLOC_DEFINE(M_NANDSIM, "NANDsim", "NANDsim dynamic data"); + +#define NANDSIM_CHIP_LOCK(chip) mtx_lock(&(chip)->ns_lock) +#define NANDSIM_CHIP_UNLOCK(chip) mtx_unlock(&(chip)->ns_lock) + +static nandsim_evh_t erase_evh; +static nandsim_evh_t idle_evh; +static nandsim_evh_t poweron_evh; +static nandsim_evh_t reset_evh; +static nandsim_evh_t read_evh; +static nandsim_evh_t readid_evh; +static nandsim_evh_t readparam_evh; +static nandsim_evh_t write_evh; + +static void nandsim_loop(void *); +static void nandsim_undefined(struct nandsim_chip *, uint8_t); +static void nandsim_bad_address(struct nandsim_chip *, uint8_t *); +static void nandsim_ignore_address(struct nandsim_chip *, uint8_t); +static void nandsim_sm_error(struct nandsim_chip *); +static void nandsim_start_handler(struct nandsim_chip *, nandsim_evh_t); + +static void nandsim_callout_eh(void *); +static int nandsim_delay(struct nandsim_chip *, int); + +static int nandsim_bbm_init(struct nandsim_chip *, uint32_t, uint32_t *); +static int nandsim_blk_state_init(struct nandsim_chip *, uint32_t, uint32_t); +static void nandsim_blk_state_destroy(struct nandsim_chip *); +static int nandchip_is_block_valid(struct nandsim_chip *, int); + +static void nandchip_set_status(struct nandsim_chip *, uint8_t); +static void nandchip_clear_status(struct nandsim_chip *, uint8_t); + +struct proc *nandsim_proc; + +struct nandsim_chip * +nandsim_chip_init(struct nandsim_softc* sc, uint8_t chip_num, + struct sim_chip *sim_chip) +{ + struct nandsim_chip *chip; + struct onfi_params *chip_param; + char swapfile[20]; + uint32_t size; + int error; + + chip = malloc(sizeof(*chip), M_NANDSIM, M_WAITOK | M_ZERO); + if (!chip) + return (NULL); + + mtx_init(&chip->ns_lock, "nandsim lock", NULL, MTX_DEF); + callout_init(&chip->ns_callout, CALLOUT_MPSAFE); + STAILQ_INIT(&chip->nandsim_events); + + chip->chip_num = chip_num; + chip->ctrl_num = sim_chip->ctrl_num; + chip->sc = sc; + + if (!sim_chip->is_wp) + nandchip_set_status(chip, NAND_STATUS_WP); + + chip_param = &chip->params; + + chip->id.dev_id = sim_chip->device_id; + chip->id.man_id = sim_chip->manufact_id; + + chip->error_ratio = sim_chip->error_ratio; + chip->wear_level = sim_chip->wear_level; + chip->prog_delay = sim_chip->prog_time; + chip->erase_delay = sim_chip->erase_time; + chip->read_delay = sim_chip->read_time; + + chip_param->t_prog = sim_chip->prog_time; + chip_param->t_bers = sim_chip->erase_time; + chip_param->t_r = sim_chip->read_time; + bcopy("onfi", &chip_param->signature, 4); + + chip_param->manufacturer_id = sim_chip->manufact_id; + strncpy(chip_param->manufacturer_name, sim_chip->manufacturer, 12); + chip_param->manufacturer_name[11] = 0; + strncpy(chip_param->device_model, sim_chip->device_model, 20); + chip_param->device_model[19] = 0; + + chip_param->bytes_per_page = sim_chip->page_size; + chip_param->spare_bytes_per_page = sim_chip->oob_size; + chip_param->pages_per_block = sim_chip->pgs_per_blk; + chip_param->blocks_per_lun = sim_chip->blks_per_lun; + chip_param->luns = sim_chip->luns; + + init_chip_geom(&chip->cg, chip_param->luns, chip_param->blocks_per_lun, + chip_param->pages_per_block, chip_param->bytes_per_page, + chip_param->spare_bytes_per_page); + + chip_param->address_cycles = sim_chip->row_addr_cycles | + (sim_chip->col_addr_cycles << 4); + chip_param->features = sim_chip->features; + if (sim_chip->width == 16) + chip_param->features |= ONFI_FEAT_16BIT; + + size = chip_param->blocks_per_lun * chip_param->luns; + + error = nandsim_blk_state_init(chip, size, sim_chip->wear_level); + if (error) { + mtx_destroy(&chip->ns_lock); + free(chip, M_NANDSIM); + return (NULL); + } + + error = nandsim_bbm_init(chip, size, sim_chip->bad_block_map); + if (error) { + mtx_destroy(&chip->ns_lock); + nandsim_blk_state_destroy(chip); + free(chip, M_NANDSIM); + return (NULL); + } + + nandsim_start_handler(chip, poweron_evh); + + nand_debug(NDBG_SIM,"Create thread for chip%d [%8p]", chip->chip_num, + chip); + /* Create chip thread */ + error = kproc_kthread_add(nandsim_loop, chip, &nandsim_proc, + &chip->nandsim_td, RFSTOPPED | RFHIGHPID, + 0, "nandsim", "chip"); + if (error) { + mtx_destroy(&chip->ns_lock); + nandsim_blk_state_destroy(chip); + free(chip, M_NANDSIM); + return (NULL); + } + + thread_lock(chip->nandsim_td); + sched_class(chip->nandsim_td, PRI_REALTIME); + sched_add(chip->nandsim_td, SRQ_BORING); + thread_unlock(chip->nandsim_td); + + size = (chip_param->bytes_per_page + + chip_param->spare_bytes_per_page) * + chip_param->pages_per_block; + + sprintf(swapfile, "chip%d%d.swp", chip->ctrl_num, chip->chip_num); + chip->swap = nandsim_swap_init(swapfile, chip_param->blocks_per_lun * + chip_param->luns, size); + if (!chip->swap) + nandsim_chip_destroy(chip); + + /* Wait for new thread to enter main loop */ + tsleep(chip->nandsim_td, PWAIT, "ns_chip", 1 * hz); + + return (chip); +} + +static int +nandsim_blk_state_init(struct nandsim_chip *chip, uint32_t size, + uint32_t wear_lev) +{ + int i; + + if (!chip || size == 0) + return (-1); + + chip->blk_state = malloc(size * sizeof(struct nandsim_block_state), + M_NANDSIM, M_WAITOK | M_ZERO); + if (!chip->blk_state) { + return (-1); + } + + for (i = 0; i < size; i++) { + if (wear_lev) + chip->blk_state[i].wear_lev = wear_lev; + else + chip->blk_state[i].wear_lev = -1; + } + + return (0); +} + +static void +nandsim_blk_state_destroy(struct nandsim_chip *chip) +{ + + if (chip && chip->blk_state) + free(chip->blk_state, M_NANDSIM); +} + +static int +nandsim_bbm_init(struct nandsim_chip *chip, uint32_t size, + uint32_t *sim_bbm) +{ + uint32_t index; + int i; + + if ((chip == NULL) || (size == 0)) + return (-1); + + if (chip->blk_state == NULL) + return (-1); + + if (sim_bbm == NULL) + return (0); + + for (i = 0; i < MAX_BAD_BLOCKS; i++) { + index = sim_bbm[i]; + + if (index == 0xffffffff) + break; + else if (index > size) + return (-1); + else + chip->blk_state[index].is_bad = 1; + } + + return (0); +} + +void +nandsim_chip_destroy(struct nandsim_chip *chip) +{ + struct nandsim_ev *ev; + + ev = create_event(chip, NANDSIM_EV_EXIT, 0); + if (ev) + send_event(ev); +} + +void +nandsim_chip_freeze(struct nandsim_chip *chip) +{ + + chip->flags |= NANDSIM_CHIP_FROZEN; +} + +static void +nandsim_loop(void *arg) +{ + struct nandsim_chip *chip = (struct nandsim_chip *)arg; + struct nandsim_ev *ev; + + nand_debug(NDBG_SIM,"Start main loop for chip%d [%8p]", chip->chip_num, + chip); + for(;;) { + NANDSIM_CHIP_LOCK(chip); + if (!(chip->flags & NANDSIM_CHIP_ACTIVE)) { + chip->flags |= NANDSIM_CHIP_ACTIVE; + wakeup(chip->nandsim_td); + } + + if (STAILQ_EMPTY(&chip->nandsim_events)) { + nand_debug(NDBG_SIM,"Chip%d [%8p] going sleep", + chip->chip_num, chip); + msleep(chip, &chip->ns_lock, PRIBIO, "nandev", 0); + } + + ev = STAILQ_FIRST(&chip->nandsim_events); + STAILQ_REMOVE_HEAD(&chip->nandsim_events, links); + NANDSIM_CHIP_UNLOCK(chip); + if (ev->type == NANDSIM_EV_EXIT) { + NANDSIM_CHIP_LOCK(chip); + destroy_event(ev); + wakeup(ev); + while (!STAILQ_EMPTY(&chip->nandsim_events)) { + ev = STAILQ_FIRST(&chip->nandsim_events); + STAILQ_REMOVE_HEAD(&chip->nandsim_events, + links); + destroy_event(ev); + wakeup(ev); + }; + NANDSIM_CHIP_UNLOCK(chip); + nandsim_log(chip, NANDSIM_LOG_SM, "destroyed\n"); + mtx_destroy(&chip->ns_lock); + nandsim_blk_state_destroy(chip); + nandsim_swap_destroy(chip->swap); + free(chip, M_NANDSIM); + nandsim_proc = NULL; + + kthread_exit(); + } + + if (!(chip->flags & NANDSIM_CHIP_FROZEN)) { + nand_debug(NDBG_SIM,"Chip [%x] get event [%x]", + chip->chip_num, ev->type); + chip->ev_handler(chip, ev->type, ev->data); + } + + wakeup(ev); + destroy_event(ev); + } + +} + +struct nandsim_ev * +create_event(struct nandsim_chip *chip, uint8_t type, uint8_t data_size) +{ + struct nandsim_ev *ev; + + ev = malloc(sizeof(*ev), M_NANDSIM, M_NOWAIT | M_ZERO); + if (!ev) { + nand_debug(NDBG_SIM,"Cannot create event"); + return (NULL); + } + + if (data_size > 0) + ev->data = malloc(sizeof(*ev), M_NANDSIM, M_NOWAIT | M_ZERO); + ev->type = type; + ev->chip = chip; + + return (ev); +} + +void +destroy_event(struct nandsim_ev *ev) +{ + + if (ev->data) + free(ev->data, M_NANDSIM); + free(ev, M_NANDSIM); +} + +int +send_event(struct nandsim_ev *ev) +{ + struct nandsim_chip *chip = ev->chip; + + if (!(chip->flags & NANDSIM_CHIP_FROZEN)) { + nand_debug(NDBG_SIM,"Chip%d [%p] send event %x", + chip->chip_num, chip, ev->type); + + NANDSIM_CHIP_LOCK(chip); + STAILQ_INSERT_TAIL(&chip->nandsim_events, ev, links); + NANDSIM_CHIP_UNLOCK(chip); + + wakeup(chip); + if ((ev->type != NANDSIM_EV_TIMEOUT) && chip->nandsim_td && + (curthread != chip->nandsim_td)) + tsleep(ev, PWAIT, "ns_ev", 5 * hz); + } + + return (0); +} + +static void +nandsim_callout_eh(void *arg) +{ + struct nandsim_ev *ev = (struct nandsim_ev *)arg; + + send_event(ev); +} + +static int +nandsim_delay(struct nandsim_chip *chip, int timeout) +{ + struct nandsim_ev *ev; + struct timeval delay; + int tm; + + nand_debug(NDBG_SIM,"Chip[%d] Set delay: %d", chip->chip_num, timeout); + + ev = create_event(chip, NANDSIM_EV_TIMEOUT, 0); + if (!ev) + return (-1); + + chip->sm_state = NANDSIM_STATE_TIMEOUT; + tm = (timeout/10000) * (hz / 100); + if (callout_reset(&chip->ns_callout, tm, nandsim_callout_eh, ev)) + return (-1); + + delay.tv_sec = chip->read_delay / 1000000; + delay.tv_usec = chip->read_delay % 1000000; + timevaladd(&chip->delay_tv, &delay); + + return (0); +} + +static void +nandsim_start_handler(struct nandsim_chip *chip, nandsim_evh_t evh) +{ + struct nandsim_ev *ev; + + chip->ev_handler = evh; + + nand_debug(NDBG_SIM,"Start handler %p for chip%d [%p]", evh, + chip->chip_num, chip); + ev = create_event(chip, NANDSIM_EV_START, 0); + if (!ev) + nandsim_sm_error(chip); + + send_event(ev); +} + +static void +nandchip_set_data(struct nandsim_chip *chip, uint8_t *data, uint32_t len, + uint32_t idx) +{ + + nand_debug(NDBG_SIM,"Chip [%x] data %p [%x] at %x", chip->chip_num, + data, len, idx); + chip->data.data_ptr = data; + chip->data.size = len; + chip->data.index = idx; +} + +static int +nandchip_chip_space(struct nandsim_chip *chip, int32_t row, int32_t column, + size_t size, uint8_t writing) +{ + struct block_space *blk_space; + uint32_t lun, block, page, offset, block_size; + int err; + + block_size = chip->cg.block_size + + (chip->cg.oob_size * chip->cg.pgs_per_blk); + + err = nand_row_to_blkpg(&chip->cg, row, &lun, &block, &page); + if (err) { + nand_debug(NDBG_SIM,"cannot get address\n"); + return (-1); + } + + if (!nandchip_is_block_valid(chip, block)) { + nandchip_set_data(chip, NULL, 0, 0); + return (-1); + } + + blk_space = get_bs(chip->swap, block, writing); + if (!blk_space) { + nandchip_set_data(chip, NULL, 0, 0); + return (-1); + } + + if (size > block_size) + size = block_size; + + if (size == block_size) { + offset = 0; + column = 0; + } else + offset = page * (chip->cg.page_size + chip->cg.oob_size); + + nandchip_set_data(chip, &blk_space->blk_ptr[offset], size, column); + + return (0); +} + +static int +nandchip_get_addr_byte(struct nandsim_chip *chip, void *data, uint32_t *value) +{ + int ncycles = 0; + uint8_t byte; + uint8_t *buffer; + + buffer = (uint8_t *)value; + byte = *((uint8_t *)data); + + KASSERT((chip->sm_state == NANDSIM_STATE_WAIT_ADDR_ROW || + chip->sm_state == NANDSIM_STATE_WAIT_ADDR_COL), + ("unexpected state")); + + if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_ROW) { + ncycles = chip->params.address_cycles & 0xf; + buffer[chip->sm_addr_cycle++] = byte; + } else if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_COL) { + ncycles = (chip->params.address_cycles >> 4) & 0xf; + buffer[chip->sm_addr_cycle++] = byte; + } + + nand_debug(NDBG_SIM, "Chip [%x] read addr byte: %02x (%d of %d)\n", + chip->chip_num, byte, chip->sm_addr_cycle, ncycles); + + if (chip->sm_addr_cycle == ncycles) { + chip->sm_addr_cycle = 0; + return (0); + } + + return (1); +} + +static int +nandchip_is_block_valid(struct nandsim_chip *chip, int block_num) +{ + + if (!chip || !chip->blk_state) + return (0); + + if (chip->blk_state[block_num].wear_lev == 0 || + chip->blk_state[block_num].is_bad) + return (0); + + return (1); +} + +static void +nandchip_set_status(struct nandsim_chip *chip, uint8_t flags) +{ + + chip->chip_status |= flags; +} + +static void +nandchip_clear_status(struct nandsim_chip *chip, uint8_t flags) +{ + + chip->chip_status &= ~flags; +} + +uint8_t +nandchip_get_status(struct nandsim_chip *chip) +{ + return (chip->chip_status); +} + +void +nandsim_chip_timeout(struct nandsim_chip *chip) +{ + struct timeval tv; + + getmicrotime(&tv); + + if (chip->sm_state == NANDSIM_STATE_TIMEOUT && + timevalcmp(&tv, &chip->delay_tv, >=)) { + nandchip_set_status(chip, NAND_STATUS_RDY); + } +} +void +poweron_evh(struct nandsim_chip *chip, uint32_t type, void *data) +{ + uint8_t cmd; + + if (type == NANDSIM_EV_START) + chip->sm_state = NANDSIM_STATE_IDLE; + else if (type == NANDSIM_EV_CMD) { + cmd = *(uint8_t *)data; + switch(cmd) { + case NAND_CMD_RESET: + nandsim_log(chip, NANDSIM_LOG_SM, "in RESET state\n"); + nandsim_start_handler(chip, reset_evh); + break; + default: + nandsim_undefined(chip, type); + break; + } + } else + nandsim_undefined(chip, type); +} + +void +idle_evh(struct nandsim_chip *chip, uint32_t type, void *data) +{ + uint8_t cmd; + + if (type == NANDSIM_EV_START) { + nandsim_log(chip, NANDSIM_LOG_SM, "in IDLE state\n"); + chip->sm_state = NANDSIM_STATE_WAIT_CMD; + } else if (type == NANDSIM_EV_CMD) { + nandchip_clear_status(chip, NAND_STATUS_FAIL); + getmicrotime(&chip->delay_tv); + cmd = *(uint8_t *)data; + switch(cmd) { + case NAND_CMD_READ_ID: + nandsim_start_handler(chip, readid_evh); + break; + case NAND_CMD_READ_PARAMETER: + nandsim_start_handler(chip, readparam_evh); + break; + case NAND_CMD_READ: + nandsim_start_handler(chip, read_evh); + break; + case NAND_CMD_PROG: + nandsim_start_handler(chip, write_evh); + break; + case NAND_CMD_ERASE: + nandsim_start_handler(chip, erase_evh); + break; + default: + nandsim_undefined(chip, type); + break; + } + } else + nandsim_undefined(chip, type); +} + +void +readid_evh(struct nandsim_chip *chip, uint32_t type, void *data) +{ + struct onfi_params *params; + uint8_t addr; + + params = &chip->params; + + if (type == NANDSIM_EV_START) { + nandsim_log(chip, NANDSIM_LOG_SM, "in READID state\n"); + chip->sm_state = NANDSIM_STATE_WAIT_ADDR_BYTE; + } else if (type == NANDSIM_EV_ADDR) { + + addr = *((uint8_t *)data); + + if (addr == 0x0) + nandchip_set_data(chip, (uint8_t *)&chip->id, 2, 0); + else if (addr == ONFI_SIG_ADDR) + nandchip_set_data(chip, (uint8_t *)¶ms->signature, + 4, 0); + else + nandsim_bad_address(chip, &addr); + + nandsim_start_handler(chip, idle_evh); + } else + nandsim_undefined(chip, type); +} + +void +readparam_evh(struct nandsim_chip *chip, uint32_t type, void *data) +{ + struct onfi_params *params; + uint8_t addr; + + params = &chip->params; + + if (type == NANDSIM_EV_START) { + nandsim_log(chip, NANDSIM_LOG_SM, "in READPARAM state\n"); + chip->sm_state = NANDSIM_STATE_WAIT_ADDR_BYTE; + } else if (type == NANDSIM_EV_ADDR) { + addr = *((uint8_t *)data); + + if (addr == 0) { + nandchip_set_data(chip, (uint8_t *)params, + sizeof(*params), 0); + } else + nandsim_bad_address(chip, &addr); + + nandsim_start_handler(chip, idle_evh); + } else + nandsim_undefined(chip, type); +} + +void +read_evh(struct nandsim_chip *chip, uint32_t type, void *data) +{ + static uint32_t column = 0, row = 0; + uint32_t size; + uint8_t cmd; + + size = chip->cg.page_size + chip->cg.oob_size; + + switch (type) { + case NANDSIM_EV_START: + nandsim_log(chip, NANDSIM_LOG_SM, "in READ state\n"); + chip->sm_state = NANDSIM_STATE_WAIT_ADDR_COL; + break; + case NANDSIM_EV_ADDR: + if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_COL) { + if (nandchip_get_addr_byte(chip, data, &column)) + break; + + chip->sm_state = NANDSIM_STATE_WAIT_ADDR_ROW; + } else if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_ROW) { + if (nandchip_get_addr_byte(chip, data, &row)) + break; + + chip->sm_state = NANDSIM_STATE_WAIT_CMD; + } else + nandsim_ignore_address(chip, *((uint8_t *)data)); + break; + case NANDSIM_EV_CMD: + cmd = *(uint8_t *)data; + if (chip->sm_state == NANDSIM_STATE_WAIT_CMD && + cmd == NAND_CMD_READ_END) { + if (chip->read_delay != 0 && + nandsim_delay(chip, chip->read_delay) == 0) + nandchip_clear_status(chip, NAND_STATUS_RDY); + else { + nandchip_chip_space(chip, row, column, size, 0); + nandchip_set_status(chip, NAND_STATUS_RDY); + nandsim_start_handler(chip, idle_evh); + } + } else + nandsim_undefined(chip, type); + break; + case NANDSIM_EV_TIMEOUT: + if (chip->sm_state == NANDSIM_STATE_TIMEOUT) { + nandchip_chip_space(chip, row, column, size, 0); + nandchip_set_status(chip, NAND_STATUS_RDY); + nandsim_start_handler(chip, idle_evh); + } else + nandsim_undefined(chip, type); + break; + } +} +void +write_evh(struct nandsim_chip *chip, uint32_t type, void *data) +{ + static uint32_t column, row; + uint32_t size; + uint8_t cmd; + int err; + + size = chip->cg.page_size + chip->cg.oob_size; + + switch(type) { + case NANDSIM_EV_START: + nandsim_log(chip, NANDSIM_LOG_SM, "in WRITE state\n"); + chip->sm_state = NANDSIM_STATE_WAIT_ADDR_COL; + break; + case NANDSIM_EV_ADDR: + if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_COL) { + if (nandchip_get_addr_byte(chip, data, &column)) + break; + + chip->sm_state = NANDSIM_STATE_WAIT_ADDR_ROW; + } else if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_ROW) { + if (nandchip_get_addr_byte(chip, data, &row)) + break; + + err = nandchip_chip_space(chip, row, column, size, 1); + if (err == -1) + nandchip_set_status(chip, NAND_STATUS_FAIL); + + chip->sm_state = NANDSIM_STATE_WAIT_CMD; + } else + nandsim_ignore_address(chip, *((uint8_t *)data)); + break; + case NANDSIM_EV_CMD: + cmd = *(uint8_t *)data; + if (chip->sm_state == NANDSIM_STATE_WAIT_CMD && + cmd == NAND_CMD_PROG_END) { + if (chip->prog_delay != 0 && + nandsim_delay(chip, chip->prog_delay) == 0) + nandchip_clear_status(chip, NAND_STATUS_RDY); + else { + nandchip_set_status(chip, NAND_STATUS_RDY); + nandsim_start_handler(chip, idle_evh); + } + } else + nandsim_undefined(chip, type); + break; + case NANDSIM_EV_TIMEOUT: + if (chip->sm_state == NANDSIM_STATE_TIMEOUT) { + nandsim_start_handler(chip, idle_evh); + nandchip_set_status(chip, NAND_STATUS_RDY); + } else + nandsim_undefined(chip, type); + break; + } +} + +void +erase_evh(struct nandsim_chip *chip, uint32_t type, void *data) +{ + static uint32_t row, block_size; + uint32_t lun, block, page; + int err; + uint8_t cmd; + + block_size = chip->cg.block_size + + (chip->cg.oob_size * chip->cg.pgs_per_blk); + + switch (type) { + case NANDSIM_EV_START: + nandsim_log(chip, NANDSIM_LOG_SM, "in ERASE state\n"); + chip->sm_state = NANDSIM_STATE_WAIT_ADDR_ROW; + break; + case NANDSIM_EV_CMD: + cmd = *(uint8_t *)data; + if (chip->sm_state == NANDSIM_STATE_WAIT_CMD && + cmd == NAND_CMD_ERASE_END) { + if (chip->data.data_ptr != NULL && + chip->data.size == block_size) + memset(chip->data.data_ptr, 0xff, block_size); + else + nand_debug(NDBG_SIM,"Bad block erase data\n"); + + err = nand_row_to_blkpg(&chip->cg, row, &lun, + &block, &page); + if (!err) { + if (chip->blk_state[block].wear_lev > 0) + chip->blk_state[block].wear_lev--; + } + + if (chip->erase_delay != 0 && + nandsim_delay(chip, chip->erase_delay) == 0) + nandchip_clear_status(chip, NAND_STATUS_RDY); + else { + nandchip_set_status(chip, NAND_STATUS_RDY); + nandsim_start_handler(chip, idle_evh); + } + } else + nandsim_undefined(chip, type); + break; + case NANDSIM_EV_ADDR: + if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_ROW) { + if (nandchip_get_addr_byte(chip, data, &row)) + break; + + err = nandchip_chip_space(chip, row, 0, block_size, 1); + if (err == -1) { + nandchip_set_status(chip, NAND_STATUS_FAIL); + } + chip->sm_state = NANDSIM_STATE_WAIT_CMD; + } else + nandsim_ignore_address(chip, *((uint8_t *)data)); + break; + case NANDSIM_EV_TIMEOUT: + if (chip->sm_state == NANDSIM_STATE_TIMEOUT) { + nandchip_set_status(chip, NAND_STATUS_RDY); + nandsim_start_handler(chip, idle_evh); + } else + nandsim_undefined(chip, type); + break; + } +} + +void +reset_evh(struct nandsim_chip *chip, uint32_t type, void *data) +{ + + if (type == NANDSIM_EV_START) { + nandsim_log(chip, NANDSIM_LOG_SM, "in RESET state\n"); + chip->sm_state = NANDSIM_STATE_TIMEOUT; + nandchip_set_data(chip, NULL, 0, 0); + DELAY(500); + nandsim_start_handler(chip, idle_evh); + } else + nandsim_undefined(chip, type); +} + +static void +nandsim_undefined(struct nandsim_chip *chip, uint8_t type) +{ + + nandsim_log(chip, NANDSIM_LOG_ERR, + "ERR: Chip received ev %x in state %x\n", + type, chip->sm_state); + nandsim_start_handler(chip, idle_evh); +} + +static void +nandsim_bad_address(struct nandsim_chip *chip, uint8_t *addr) +{ + + nandsim_log(chip, NANDSIM_LOG_ERR, + "ERR: Chip received out of range address" + "%02x%02x - %02x%02x%02x\n", addr[0], addr[1], addr[2], + addr[3], addr[4]); +} + +static void +nandsim_ignore_address(struct nandsim_chip *chip, uint8_t byte) +{ + nandsim_log(chip, NANDSIM_LOG_SM, "ignored address byte: %d\n", byte); +} + +static void +nandsim_sm_error(struct nandsim_chip *chip) +{ + + nandsim_log(chip, NANDSIM_LOG_ERR, "ERR: State machine error." + "Restart required.\n"); +} diff --git a/sys/dev/nand/nandsim_chip.h b/sys/dev/nand/nandsim_chip.h new file mode 100644 index 0000000..a6fb234 --- /dev/null +++ b/sys/dev/nand/nandsim_chip.h @@ -0,0 +1,159 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +#ifndef _NANDSIM_CHIP_H +#define _NANDSIM_CHIP_H + +#include <sys/malloc.h> +#include <sys/callout.h> +#include <dev/nand/nand.h> +#include <dev/nand/nandsim.h> +#include <dev/nand/nandsim_swap.h> + +MALLOC_DECLARE(M_NANDSIM); + +#define MAX_CS_NUM 4 +struct nandsim_chip; + +typedef void nandsim_evh_t(struct nandsim_chip *chip, uint32_t ev, void *data); + +enum addr_type { + ADDR_NONE, + ADDR_ID, + ADDR_ROW, + ADDR_ROWCOL +}; + +struct nandsim_softc { + struct nand_softc nand_dev; + device_t dev; + + struct nandsim_chip *chips[MAX_CS_NUM]; + struct nandsim_chip *active_chip; + + uint8_t address_cycle; + enum addr_type address_type; + int log_idx; + char *log_buff; + struct alq *alq; +}; + +struct nandsim_ev { + STAILQ_ENTRY(nandsim_ev) links; + struct nandsim_chip *chip; + uint8_t type; + void *data; +}; + +struct nandsim_data { + uint8_t *data_ptr; + uint32_t index; + uint32_t size; +}; + +struct nandsim_block_state { + int32_t wear_lev; + uint8_t is_bad; +}; + +#define NANDSIM_CHIP_ACTIVE 0x1 +#define NANDSIM_CHIP_FROZEN 0x2 +#define NANDSIM_CHIP_GET_STATUS 0x4 + +struct nandsim_chip { + struct nandsim_softc *sc; + struct thread *nandsim_td; + + STAILQ_HEAD(, nandsim_ev) nandsim_events; + nandsim_evh_t *ev_handler; + struct mtx ns_lock; + struct callout ns_callout; + + struct chip_geom cg; + struct nand_id id; + struct onfi_params params; + struct nandsim_data data; + struct nandsim_block_state *blk_state; + + struct chip_swap *swap; + + uint32_t error_ratio; + uint32_t wear_level; + uint32_t sm_state; + uint32_t sm_addr_cycle; + + uint32_t erase_delay; + uint32_t prog_delay; + uint32_t read_delay; + struct timeval delay_tv; + + uint8_t flags; + uint8_t chip_status; + uint8_t ctrl_num; + uint8_t chip_num; +}; + +struct sim_ctrl_conf { + uint8_t num; + uint8_t num_cs; + uint8_t ecc; + uint8_t running; + uint8_t created; + device_t sim_ctrl_dev; + struct sim_chip *chips[MAX_CTRL_CS]; + uint16_t ecc_layout[MAX_ECC_BYTES]; + char filename[FILENAME_SIZE]; +}; + +#define NANDSIM_STATE_IDLE 0x0 +#define NANDSIM_STATE_WAIT_ADDR_BYTE 0x1 +#define NANDSIM_STATE_WAIT_CMD 0x2 +#define NANDSIM_STATE_TIMEOUT 0x3 +#define NANDSIM_STATE_WAIT_ADDR_ROW 0x4 +#define NANDSIM_STATE_WAIT_ADDR_COL 0x5 + +#define NANDSIM_EV_START 0x1 +#define NANDSIM_EV_CMD 0x2 +#define NANDSIM_EV_ADDR 0x3 +#define NANDSIM_EV_TIMEOUT 0x4 +#define NANDSIM_EV_EXIT 0xff + +struct nandsim_chip *nandsim_chip_init(struct nandsim_softc *, + uint8_t, struct sim_chip *); +void nandsim_chip_destroy(struct nandsim_chip *); +void nandsim_chip_freeze(struct nandsim_chip *); +void nandsim_chip_timeout(struct nandsim_chip *); +int nandsim_chip_check_bad_block(struct nandsim_chip *, int); + +uint8_t nandchip_get_status(struct nandsim_chip *); + +void destroy_event(struct nandsim_ev *); +int send_event(struct nandsim_ev *); +struct nandsim_ev *create_event(struct nandsim_chip *, uint8_t, uint8_t); + +#endif /* _NANDSIM_CHIP_H */ diff --git a/sys/dev/nand/nandsim_ctrl.c b/sys/dev/nand/nandsim_ctrl.c new file mode 100644 index 0000000..5f7b019 --- /dev/null +++ b/sys/dev/nand/nandsim_ctrl.c @@ -0,0 +1,396 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + */ + +/* Simulated NAND controller driver */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/proc.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/rman.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/time.h> + +#include <dev/nand/nand.h> +#include <dev/nand/nandbus.h> +#include <dev/nand/nandsim.h> +#include <dev/nand/nandsim_log.h> +#include <dev/nand/nandsim_chip.h> +#include "nfc_if.h" + +#define ADDRESS_SIZE 5 + +extern struct sim_ctrl_conf ctrls[MAX_SIM_DEV]; + +static void byte_corrupt(struct nandsim_chip *, uint8_t *); + +static int nandsim_attach(device_t); +static int nandsim_detach(device_t); +static int nandsim_probe(device_t); + +static uint8_t nandsim_read_byte(device_t); +static uint16_t nandsim_read_word(device_t); +static int nandsim_select_cs(device_t, uint8_t); +static void nandsim_write_byte(device_t, uint8_t); +static void nandsim_write_word(device_t, uint16_t); +static void nandsim_read_buf(device_t, void *, uint32_t); +static void nandsim_write_buf(device_t, void *, uint32_t); +static int nandsim_send_command(device_t, uint8_t); +static int nandsim_send_address(device_t, uint8_t); + +static device_method_t nandsim_methods[] = { + DEVMETHOD(device_probe, nandsim_probe), + DEVMETHOD(device_attach, nandsim_attach), + DEVMETHOD(device_detach, nandsim_detach), + + DEVMETHOD(nfc_select_cs, nandsim_select_cs), + DEVMETHOD(nfc_send_command, nandsim_send_command), + DEVMETHOD(nfc_send_address, nandsim_send_address), + DEVMETHOD(nfc_read_byte, nandsim_read_byte), + DEVMETHOD(nfc_read_word, nandsim_read_word), + DEVMETHOD(nfc_write_byte, nandsim_write_byte), + DEVMETHOD(nfc_read_buf, nandsim_read_buf), + DEVMETHOD(nfc_write_buf, nandsim_write_buf), + + { 0, 0 }, +}; + +static driver_t nandsim_driver = { + "nandsim", + nandsim_methods, + sizeof(struct nandsim_softc), +}; + +static devclass_t nandsim_devclass; +DRIVER_MODULE(nandsim, nexus, nandsim_driver, nandsim_devclass, 0, 0); +DRIVER_MODULE(nandbus, nandsim, nandbus_driver, nandbus_devclass, 0, 0); + +static int +nandsim_probe(device_t dev) +{ + + device_set_desc(dev, "NAND controller simulator"); + return (BUS_PROBE_DEFAULT); +} + +static int +nandsim_attach(device_t dev) +{ + struct nandsim_softc *sc; + struct sim_ctrl_conf *params; + struct sim_chip *chip; + uint16_t *eccpos; + int i, err; + + sc = device_get_softc(dev); + params = &ctrls[device_get_unit(dev)]; + + if (strlen(params->filename) == 0) + snprintf(params->filename, FILENAME_SIZE, "ctrl%d.log", + params->num); + + nandsim_log_init(sc, params->filename); + for (i = 0; i < params->num_cs; i++) { + chip = params->chips[i]; + if (chip && chip->device_id != 0) { + sc->chips[i] = nandsim_chip_init(sc, i, chip); + if (chip->features & ONFI_FEAT_16BIT) + sc->nand_dev.flags |= NAND_16_BIT; + } + } + + if (params->ecc_layout[0] != 0xffff) + eccpos = params->ecc_layout; + else + eccpos = NULL; + + nand_init(&sc->nand_dev, dev, params->ecc, 0, 0, eccpos, "nandsim"); + + err = nandbus_create(dev); + + return (err); +} + +static int +nandsim_detach(device_t dev) +{ + struct nandsim_softc *sc; + struct sim_ctrl_conf *params; + int i; + + sc = device_get_softc(dev); + params = &ctrls[device_get_unit(dev)]; + + for (i = 0; i < params->num_cs; i++) + if (sc->chips[i] != NULL) + nandsim_chip_destroy(sc->chips[i]); + + nandsim_log_close(sc); + + return (0); +} + +static int +nandsim_select_cs(device_t dev, uint8_t cs) +{ + struct nandsim_softc *sc; + + sc = device_get_softc(dev); + + if (cs >= MAX_CS_NUM) + return (EINVAL); + + sc->active_chip = sc->chips[cs]; + + if (sc->active_chip) + nandsim_log(sc->active_chip, NANDSIM_LOG_EV, + "Select cs %d\n", cs); + + return (0); +} + +static int +nandsim_send_command(device_t dev, uint8_t command) +{ + struct nandsim_softc *sc; + struct nandsim_chip *chip; + struct nandsim_ev *ev; + + sc = device_get_softc(dev); + chip = sc->active_chip; + + if (chip == NULL) + return (0); + + nandsim_log(chip, NANDSIM_LOG_EV, "Send command %x\n", command); + + switch (command) { + case NAND_CMD_READ_ID: + case NAND_CMD_READ_PARAMETER: + sc->address_type = ADDR_ID; + break; + case NAND_CMD_ERASE: + sc->address_type = ADDR_ROW; + break; + case NAND_CMD_READ: + case NAND_CMD_PROG: + sc->address_type = ADDR_ROWCOL; + break; + default: + sc->address_type = ADDR_NONE; + break; + } + + if (command == NAND_CMD_STATUS) + chip->flags |= NANDSIM_CHIP_GET_STATUS; + else { + ev = create_event(chip, NANDSIM_EV_CMD, 1); + *(uint8_t *)ev->data = command; + send_event(ev); + } + + return (0); +} + +static int +nandsim_send_address(device_t dev, uint8_t addr) +{ + struct nandsim_ev *ev; + struct nandsim_softc *sc; + struct nandsim_chip *chip; + + sc = device_get_softc(dev); + chip = sc->active_chip; + + if (chip == NULL) + return (0); + + KASSERT((sc->address_type != ADDR_NONE), ("unexpected address")); + nandsim_log(chip, NANDSIM_LOG_EV, "Send addr %x\n", addr); + + ev = create_event(chip, NANDSIM_EV_ADDR, 1); + + *((uint8_t *)(ev->data)) = addr; + + send_event(ev); + return (0); +} + +static uint8_t +nandsim_read_byte(device_t dev) +{ + struct nandsim_softc *sc; + struct nandsim_chip *chip; + uint8_t ret = 0xff; + + sc = device_get_softc(dev); + chip = sc->active_chip; + + if (chip && !(chip->flags & NANDSIM_CHIP_FROZEN)) { + if (chip->flags & NANDSIM_CHIP_GET_STATUS) { + nandsim_chip_timeout(chip); + ret = nandchip_get_status(chip); + chip->flags &= ~NANDSIM_CHIP_GET_STATUS; + } else if (chip->data.index < chip->data.size) { + ret = chip->data.data_ptr[chip->data.index++]; + byte_corrupt(chip, &ret); + } + nandsim_log(chip, NANDSIM_LOG_DATA, "read %02x\n", ret); + } + + return (ret); +} + +static uint16_t +nandsim_read_word(device_t dev) +{ + struct nandsim_softc *sc; + struct nandsim_chip *chip; + uint16_t *data_ptr; + uint16_t ret = 0xffff; + uint8_t *byte_ret = (uint8_t *)&ret; + + sc = device_get_softc(dev); + chip = sc->active_chip; + + if (chip && !(chip->flags & NANDSIM_CHIP_FROZEN)) { + if (chip->data.index < chip->data.size - 1) { + data_ptr = + (uint16_t *)&(chip->data.data_ptr[chip->data.index]); + ret = *data_ptr; + chip->data.index += 2; + byte_corrupt(chip, byte_ret); + byte_corrupt(chip, byte_ret + 1); + } + nandsim_log(chip, NANDSIM_LOG_DATA, "read %04x\n", ret); + } + + return (ret); +} + +static void +nandsim_write_byte(device_t dev, uint8_t byte) +{ + struct nandsim_softc *sc; + struct nandsim_chip *chip; + + sc = device_get_softc(dev); + chip = sc->active_chip; + + if (chip && !(chip->flags & NANDSIM_CHIP_FROZEN) && + (chip->data.index < chip->data.size)) { + byte_corrupt(chip, &byte); + chip->data.data_ptr[chip->data.index] &= byte; + chip->data.index++; + nandsim_log(chip, NANDSIM_LOG_DATA, "write %02x\n", byte); + } +} + +static void +nandsim_write_word(device_t dev, uint16_t word) +{ + struct nandsim_softc *sc; + struct nandsim_chip *chip; + uint16_t *data_ptr; + uint8_t *byte_ptr = (uint8_t *)&word; + + sc = device_get_softc(dev); + chip = sc->active_chip; + + if (chip && !(chip->flags & NANDSIM_CHIP_FROZEN)) { + if ((chip->data.index + 1) < chip->data.size) { + byte_corrupt(chip, byte_ptr); + byte_corrupt(chip, byte_ptr + 1); + data_ptr = + (uint16_t *)&(chip->data.data_ptr[chip->data.index]); + *data_ptr &= word; + chip->data.index += 2; + } + + nandsim_log(chip, NANDSIM_LOG_DATA, "write %04x\n", word); + } +} + +static void +nandsim_read_buf(device_t dev, void *buf, uint32_t len) +{ + struct nandsim_softc *sc; + uint16_t *buf16 = (uint16_t *)buf; + uint8_t *buf8 = (uint8_t *)buf; + int i; + + sc = device_get_softc(dev); + + if (sc->nand_dev.flags & NAND_16_BIT) { + for (i = 0; i < len / 2; i++) + buf16[i] = nandsim_read_word(dev); + } else { + for (i = 0; i < len; i++) + buf8[i] = nandsim_read_byte(dev); + } +} + +static void +nandsim_write_buf(device_t dev, void *buf, uint32_t len) +{ + struct nandsim_softc *sc; + uint16_t *buf16 = (uint16_t *)buf; + uint8_t *buf8 = (uint8_t *)buf; + int i; + + sc = device_get_softc(dev); + + if (sc->nand_dev.flags & NAND_16_BIT) { + for (i = 0; i < len / 2; i++) + nandsim_write_word(dev, buf16[i]); + } else { + for (i = 0; i < len; i++) + nandsim_write_byte(dev, buf8[i]); + } +} + +static void +byte_corrupt(struct nandsim_chip *chip, uint8_t *byte) +{ + uint32_t rand; + uint8_t bit; + + rand = random(); + if ((rand % 1000000) < chip->error_ratio) { + bit = rand % 8; + if (*byte & (1 << bit)) + *byte &= ~(1 << bit); + else + *byte |= (1 << bit); + } +} diff --git a/sys/dev/nand/nandsim_log.c b/sys/dev/nand/nandsim_log.c new file mode 100644 index 0000000..c51118f --- /dev/null +++ b/sys/dev/nand/nandsim_log.c @@ -0,0 +1,186 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/malloc.h> +#include <sys/proc.h> +#include <sys/alq.h> +#include <sys/time.h> + +#include <machine/stdarg.h> + +#include <dev/nand/nandsim_log.h> + +int nandsim_log_level; +int nandsim_log_output; +int log_size = NANDSIM_RAM_LOG_SIZE; + +static int nandsim_entry_size = NANDSIM_ENTRY_SIZE; +static int nandsim_entry_count = NANDSIM_ENTRY_COUNT; +static int str_index = 0; +static char string[NANDSIM_ENTRY_SIZE + 1] = {0}; + +int +nandsim_log_init(struct nandsim_softc *sc, char *filename) +{ + int error = 0; + + if (nandsim_log_output == NANDSIM_OUTPUT_FILE) { + error = alq_open(&sc->alq, filename, + curthread->td_ucred, 0644, + nandsim_entry_size, nandsim_entry_count); + } else if (nandsim_log_output == NANDSIM_OUTPUT_RAM) { + sc->log_buff = malloc(log_size, M_NANDSIM, M_WAITOK | M_ZERO); + if (!sc->log_buff) + error = ENOMEM; + } + + return (error); +} + +void +nandsim_log_close(struct nandsim_softc *sc) +{ + + if (nandsim_log_output == NANDSIM_OUTPUT_FILE) { + memset(&string[str_index], 0, NANDSIM_ENTRY_SIZE - str_index); + alq_write(sc->alq, (void *) string, ALQ_NOWAIT); + str_index = 0; + string[0] = '\0'; + alq_close(sc->alq); + } else if (nandsim_log_output == NANDSIM_OUTPUT_RAM) { + free(sc->log_buff, M_NANDSIM); + sc->log_buff = NULL; + } +} + +void +nandsim_log(struct nandsim_chip *chip, int level, const char *fmt, ...) +{ + char hdr[TIME_STR_SIZE]; + char tmp[NANDSIM_ENTRY_SIZE]; + struct nandsim_softc *sc; + struct timeval currtime; + va_list ap; + int hdr_len, len, rest; + + if (nandsim_log_output == NANDSIM_OUTPUT_NONE) + return; + + if (chip == NULL) + return; + + sc = chip->sc; + if (!sc->alq && nandsim_log_output == NANDSIM_OUTPUT_FILE) + return; + + if (level <= nandsim_log_level) { + microtime(&currtime); + hdr_len = sprintf(hdr, "%08jd.%08li [chip:%d, ctrl:%d]: ", + (intmax_t)currtime.tv_sec, currtime.tv_usec, + chip->chip_num, chip->ctrl_num); + + switch(nandsim_log_output) { + case NANDSIM_OUTPUT_CONSOLE: + printf("%s", hdr); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + break; + case NANDSIM_OUTPUT_RAM: + va_start(ap, fmt); + len = vsnprintf(tmp, NANDSIM_ENTRY_SIZE - 1, fmt, ap); + tmp[NANDSIM_ENTRY_SIZE - 1] = 0; + va_end(ap); + + rest = log_size - sc->log_idx - 1; + if (rest >= hdr_len) { + bcopy(hdr, &sc->log_buff[sc->log_idx], + hdr_len); + sc->log_idx += hdr_len; + sc->log_buff[sc->log_idx] = 0; + } else { + bcopy(hdr, &sc->log_buff[sc->log_idx], rest); + bcopy(&hdr[rest], sc->log_buff, + hdr_len - rest); + sc->log_idx = hdr_len - rest; + sc->log_buff[sc->log_idx] = 0; + } + + rest = log_size - sc->log_idx - 1; + if (rest >= len) { + bcopy(tmp, &sc->log_buff[sc->log_idx], len); + sc->log_idx += len; + sc->log_buff[sc->log_idx] = 0; + } else { + bcopy(tmp, &sc->log_buff[sc->log_idx], rest); + bcopy(&tmp[rest], sc->log_buff, len - rest); + sc->log_idx = len - rest; + sc->log_buff[sc->log_idx] = 0; + } + + break; + + case NANDSIM_OUTPUT_FILE: + va_start(ap, fmt); + len = vsnprintf(tmp, NANDSIM_ENTRY_SIZE - 1, fmt, ap); + tmp[NANDSIM_ENTRY_SIZE - 1] = 0; + va_end(ap); + + rest = NANDSIM_ENTRY_SIZE - str_index; + if (rest >= hdr_len) { + strcat(string, hdr); + str_index += hdr_len; + } else { + strlcat(string, hdr, NANDSIM_ENTRY_SIZE + 1); + alq_write(sc->alq, (void *) string, + ALQ_NOWAIT); + strcpy(string, &hdr[rest]); + str_index = hdr_len - rest; + } + rest = NANDSIM_ENTRY_SIZE - str_index; + if (rest >= len) { + strcat(string, tmp); + str_index += len; + } else { + strlcat(string, tmp, NANDSIM_ENTRY_SIZE + 1); + alq_write(sc->alq, (void *) string, + ALQ_NOWAIT); + strcpy(string, &tmp[rest]); + str_index = len - rest; + } + break; + default: + break; + } + } +} diff --git a/sys/dev/nand/nandsim_log.h b/sys/dev/nand/nandsim_log.h new file mode 100644 index 0000000..8cf1082 --- /dev/null +++ b/sys/dev/nand/nandsim_log.h @@ -0,0 +1,52 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +#ifndef _NANDSIM_LOG_H +#define _NANDSIM_LOG_H + +#include <dev/nand/nandsim_chip.h> + +#define NANDSIM_ENTRY_SIZE 128 +#define NANDSIM_ENTRY_COUNT 1024 +#define NANDSIM_RAM_LOG_SIZE 16384 +#define TIME_STR_SIZE 40 + +#define NANDSIM_LOG_ERR 1 +#define NANDSIM_LOG_SM 5 +#define NANDSIM_LOG_EV 10 +#define NANDSIM_LOG_DATA 15 + +extern int nandsim_log_level; +extern int nandsim_log_output; + +int nandsim_log_init(struct nandsim_softc *, char *); +void nandsim_log_close(struct nandsim_softc *); +void nandsim_log(struct nandsim_chip *, int, const char *, ...); + +#endif /* _NANDSIM_LOG_H */ + diff --git a/sys/dev/nand/nandsim_swap.c b/sys/dev/nand/nandsim_swap.c new file mode 100644 index 0000000..cc4201d --- /dev/null +++ b/sys/dev/nand/nandsim_swap.c @@ -0,0 +1,389 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/queue.h> +#include <sys/fcntl.h> +#include <sys/proc.h> +#include <sys/namei.h> +#include <sys/lock.h> +#include <sys/vnode.h> +#include <sys/mount.h> + +#include <dev/nand/nandsim_chip.h> +#include <dev/nand/nandsim_swap.h> + +static int init_block_state(struct chip_swap *); +static void destroy_block_state(struct chip_swap *); + +static int create_buffers(struct chip_swap *); +static void destroy_buffers(struct chip_swap *); + +static int swap_file_open(struct chip_swap *, const char *); +static void swap_file_close(struct chip_swap *); +static int swap_file_write(struct chip_swap *, struct block_state *); +static int swap_file_read(struct chip_swap *, struct block_state *); + +#define CHIP_SWAP_CMODE 0600 +#define CHIP_SWAP_BLOCKSPACES 2 + +static int +init_block_state(struct chip_swap *swap) +{ + struct block_state *blk_state; + int i; + + if (swap == NULL) + return (-1); + + blk_state = malloc(swap->nof_blks * sizeof(struct block_state), + M_NANDSIM, M_WAITOK | M_ZERO); + + for (i = 0; i < swap->nof_blks; i++) + blk_state[i].offset = 0xffffffff; + + swap->blk_state = blk_state; + + return (0); +} + +static void +destroy_block_state(struct chip_swap *swap) +{ + + if (swap == NULL) + return; + + if (swap->blk_state != NULL) + free(swap->blk_state, M_NANDSIM); +} + +static int +create_buffers(struct chip_swap *swap) +{ + struct block_space *block_space; + void *block; + int i; + + for (i = 0; i < CHIP_SWAP_BLOCKSPACES; i++) { + block_space = malloc(sizeof(*block_space), M_NANDSIM, M_WAITOK); + block = malloc(swap->blk_size, M_NANDSIM, M_WAITOK); + block_space->blk_ptr = block; + SLIST_INSERT_HEAD(&swap->free_bs, block_space, free_link); + nand_debug(NDBG_SIM,"created blk_space %p[%p]\n", block_space, + block); + } + + if (i == 0) + return (-1); + + return (0); +} + +static void +destroy_buffers(struct chip_swap *swap) +{ + struct block_space *blk_space; + + if (swap == NULL) + return; + + blk_space = SLIST_FIRST(&swap->free_bs); + while (blk_space) { + SLIST_REMOVE_HEAD(&swap->free_bs, free_link); + nand_debug(NDBG_SIM,"destroyed blk_space %p[%p]\n", + blk_space, blk_space->blk_ptr); + free(blk_space->blk_ptr, M_NANDSIM); + free(blk_space, M_NANDSIM); + blk_space = SLIST_FIRST(&swap->free_bs); + } + + blk_space = STAILQ_FIRST(&swap->used_bs); + while (blk_space) { + STAILQ_REMOVE_HEAD(&swap->used_bs, used_link); + nand_debug(NDBG_SIM,"destroyed blk_space %p[%p]\n", + blk_space, blk_space->blk_ptr); + free(blk_space->blk_ptr, M_NANDSIM); + free(blk_space, M_NANDSIM); + blk_space = STAILQ_FIRST(&swap->used_bs); + } +} + +static int +swap_file_open(struct chip_swap *swap, const char *swap_file) +{ + struct nameidata nd; + int vfslocked, flags, error; + + NDINIT(&nd, LOOKUP, NOFOLLOW | MPSAFE, UIO_SYSSPACE, swap_file, + curthread); + + flags = FWRITE | FREAD | O_NOFOLLOW | O_CREAT | O_TRUNC; + + error = vn_open(&nd, &flags, CHIP_SWAP_CMODE, NULL); + if (error) { + nand_debug(NDBG_SIM,"Cannot create swap file %s", swap_file); + NDFREE(&nd, NDF_ONLY_PNBUF); + return (error); + } + + swap->swap_cred = crhold(curthread->td_ucred); + vfslocked = NDHASGIANT(&nd); + NDFREE(&nd, NDF_ONLY_PNBUF); + + /* We just unlock so we hold a reference */ + VOP_UNLOCK(nd.ni_vp, 0); + VFS_UNLOCK_GIANT(vfslocked); + + swap->swap_vp = nd.ni_vp; + + return (0); +} + +static void +swap_file_close(struct chip_swap *swap) +{ + + if (swap == NULL) + return; + + if (swap->swap_vp == NULL) + return; + + vn_close(swap->swap_vp, FWRITE, swap->swap_cred, curthread); + crfree(swap->swap_cred); +} + +static int +swap_file_write(struct chip_swap *swap, struct block_state *blk_state) +{ + struct block_space *blk_space; + struct thread *td; + struct mount *mp; + struct vnode *vp; + struct uio auio; + struct iovec aiov; + int vfslocked; + + if (swap == NULL || blk_state == NULL) + return (-1); + + blk_space = blk_state->blk_sp; + if (blk_state->offset == -1) { + blk_state->offset = swap->swap_offset; + swap->swap_offset += swap->blk_size; + } + + nand_debug(NDBG_SIM,"saving %p[%p] at %x\n", + blk_space, blk_space->blk_ptr, blk_state->offset); + + bzero(&aiov, sizeof(aiov)); + bzero(&auio, sizeof(auio)); + + aiov.iov_base = blk_space->blk_ptr; + aiov.iov_len = swap->blk_size; + td = curthread; + vp = swap->swap_vp; + + auio.uio_iov = &aiov; + auio.uio_offset = blk_state->offset; + auio.uio_segflg = UIO_SYSSPACE; + auio.uio_rw = UIO_WRITE; + auio.uio_iovcnt = 1; + auio.uio_resid = swap->blk_size; + auio.uio_td = td; + + vfslocked = VFS_LOCK_GIANT(vp->v_mount); + vn_start_write(vp, &mp, V_WAIT); + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + VOP_WRITE(vp, &auio, IO_UNIT, swap->swap_cred); + VOP_UNLOCK(vp, 0); + vn_finished_write(mp); + VFS_UNLOCK_GIANT(vfslocked); + + return (0); +} + +static int +swap_file_read(struct chip_swap *swap, struct block_state *blk_state) +{ + struct block_space *blk_space; + struct thread *td; + struct vnode *vp; + struct uio auio; + struct iovec aiov; + int vfslocked; + + if (swap == NULL || blk_state == NULL) + return (-1); + + blk_space = blk_state->blk_sp; + + nand_debug(NDBG_SIM,"restore %p[%p] at %x\n", + blk_space, blk_space->blk_ptr, blk_state->offset); + + bzero(&aiov, sizeof(aiov)); + bzero(&auio, sizeof(auio)); + + aiov.iov_base = blk_space->blk_ptr; + aiov.iov_len = swap->blk_size; + td = curthread; + vp = swap->swap_vp; + + auio.uio_iov = &aiov; + auio.uio_offset = blk_state->offset; + auio.uio_segflg = UIO_SYSSPACE; + auio.uio_rw = UIO_READ; + auio.uio_iovcnt = 1; + auio.uio_resid = swap->blk_size; + auio.uio_td = td; + + vfslocked = VFS_LOCK_GIANT(vp->v_mount); + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + VOP_READ(vp, &auio, 0, swap->swap_cred); + VOP_UNLOCK(vp, 0); + VFS_UNLOCK_GIANT(vfslocked); + + return (0); +} + +struct chip_swap * +nandsim_swap_init(const char *swap_file, uint32_t nof_blks, uint32_t blk_size) +{ + struct chip_swap *swap; + int err = 0; + + if ((swap_file == NULL) || (nof_blks == 0) || (blk_size == 0)) + return (NULL); + + swap = malloc(sizeof(*swap), M_NANDSIM, M_WAITOK | M_ZERO); + + SLIST_INIT(&swap->free_bs); + STAILQ_INIT(&swap->used_bs); + swap->blk_size = blk_size; + swap->nof_blks = nof_blks; + + err = init_block_state(swap); + if (err) { + nandsim_swap_destroy(swap); + return (NULL); + } + + err = create_buffers(swap); + if (err) { + nandsim_swap_destroy(swap); + return (NULL); + } + + err = swap_file_open(swap, swap_file); + if (err) { + nandsim_swap_destroy(swap); + return (NULL); + } + + return (swap); +} + +void +nandsim_swap_destroy(struct chip_swap *swap) +{ + + if (swap == NULL) + return; + + destroy_block_state(swap); + destroy_buffers(swap); + swap_file_close(swap); + free(swap, M_NANDSIM); +} + +struct block_space * +get_bs(struct chip_swap *swap, uint32_t block, uint8_t writing) +{ + struct block_state *blk_state, *old_blk_state = NULL; + struct block_space *blk_space; + + if (swap == NULL || (block >= swap->nof_blks)) + return (NULL); + + blk_state = &swap->blk_state[block]; + nand_debug(NDBG_SIM,"blk_state %x\n", blk_state->status); + + if (blk_state->status & BLOCK_ALLOCATED) { + blk_space = blk_state->blk_sp; + } else { + blk_space = SLIST_FIRST(&swap->free_bs); + if (blk_space) { + SLIST_REMOVE_HEAD(&swap->free_bs, free_link); + STAILQ_INSERT_TAIL(&swap->used_bs, blk_space, + used_link); + } else { + blk_space = STAILQ_FIRST(&swap->used_bs); + old_blk_state = blk_space->blk_state; + STAILQ_REMOVE_HEAD(&swap->used_bs, used_link); + STAILQ_INSERT_TAIL(&swap->used_bs, blk_space, + used_link); + if (old_blk_state->status & BLOCK_DIRTY) { + swap_file_write(swap, old_blk_state); + old_blk_state->status &= ~BLOCK_DIRTY; + old_blk_state->status |= BLOCK_SWAPPED; + } + } + } + + if (blk_space == NULL) + return (NULL); + + if (old_blk_state != NULL) { + old_blk_state->status &= ~BLOCK_ALLOCATED; + old_blk_state->blk_sp = NULL; + } + + blk_state->blk_sp = blk_space; + blk_space->blk_state = blk_state; + + if (!(blk_state->status & BLOCK_ALLOCATED)) { + if (blk_state->status & BLOCK_SWAPPED) + swap_file_read(swap, blk_state); + else + memset(blk_space->blk_ptr, 0xff, swap->blk_size); + blk_state->status |= BLOCK_ALLOCATED; + } + + if (writing) + blk_state->status |= BLOCK_DIRTY; + + nand_debug(NDBG_SIM,"get_bs returned %p[%p] state %x\n", blk_space, + blk_space->blk_ptr, blk_state->status); + + return (blk_space); +} diff --git a/sys/dev/nand/nandsim_swap.h b/sys/dev/nand/nandsim_swap.h new file mode 100644 index 0000000..4fbae2a --- /dev/null +++ b/sys/dev/nand/nandsim_swap.h @@ -0,0 +1,64 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +#ifndef _NANDSIM_SWAP_CHIP_H_ +#define _NANDSIM_SWAP_CHIP_H_ + +struct block_space { + SLIST_ENTRY(block_space) free_link; + STAILQ_ENTRY(block_space) used_link; + struct block_state *blk_state; + uint8_t *blk_ptr; +}; + +#define BLOCK_ALLOCATED 0x1 +#define BLOCK_SWAPPED 0x2 +#define BLOCK_DIRTY 0x4 + +struct block_state { + struct block_space *blk_sp; + uint32_t offset; + uint8_t status; +}; + +struct chip_swap { + struct block_state *blk_state; + SLIST_HEAD(,block_space) free_bs; + STAILQ_HEAD(,block_space) used_bs; + struct ucred *swap_cred; + struct vnode *swap_vp; + uint32_t swap_offset; + uint32_t blk_size; + uint32_t nof_blks; +}; + +struct chip_swap *nandsim_swap_init(const char *, uint32_t, uint32_t); +void nandsim_swap_destroy(struct chip_swap *); +struct block_space *get_bs(struct chip_swap *, uint32_t, uint8_t); + +#endif /* _NANDSIM_SWAP_CHIP_H_ */ diff --git a/sys/dev/nand/nfc_if.m b/sys/dev/nand/nfc_if.m new file mode 100644 index 0000000..a4e1099 --- /dev/null +++ b/sys/dev/nand/nfc_if.m @@ -0,0 +1,165 @@ +#- +# Copyright (C) 2009-2012 Semihalf +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. +# +# $FreeBSD$ + +# NAND controller interface description +# + +#include <sys/bus.h> +#include <dev/nand/nand.h> + +INTERFACE nfc; + +CODE { + static int nfc_default_method(device_t dev) + { + return (0); + } + + static int nfc_softecc_get(device_t dev, void *buf, int pagesize, + void *ecc, int *needwrite) + { + *needwrite = 1; + return (nand_softecc_get(dev, buf, pagesize, ecc)); + } + + static int nfc_softecc_correct(device_t dev, void *buf, int pagesize, + void *readecc, void *calcecc) + { + return (nand_softecc_correct(dev, buf, pagesize, readecc, + calcecc)); + } +}; + +# Send command to a NAND chip +# +# Return values: +# 0: Success +# +METHOD int send_command { + device_t dev; + uint8_t command; +}; + +# Send address to a NAND chip +# +# Return values: +# 0: Success +# +METHOD int send_address { + device_t dev; + uint8_t address; +}; + +# Read byte +# +# Return values: +# byte read +# +METHOD uint8_t read_byte { + device_t dev; +}; + +# Write byte +# +METHOD void write_byte { + device_t dev; + uint8_t byte; +}; + +# Read word +# +# Return values: +# word read +# +METHOD uint16_t read_word { + device_t dev; +}; + +# Write word +# +METHOD void write_word { + device_t dev; + uint16_t word; +}; + +# Read buf +# +METHOD void read_buf { + device_t dev; + void *buf; + uint32_t len; +}; + +# Write buf +# +METHOD void write_buf { + device_t dev; + void *buf; + uint32_t len; +}; + +# Select CS +# +METHOD int select_cs { + device_t dev; + uint8_t cs; +}; + +# Read ready/busy signal +# +METHOD int read_rnb { + device_t dev; +}; + +# Start command +# +# Return values: +# 0: Success +# +METHOD int start_command { + device_t dev; +} DEFAULT nfc_default_method; + +# Generate ECC or get it from H/W +# +METHOD int get_ecc { + device_t dev; + void *buf; + int pagesize; + void *ecc; + int *needwrite; +} DEFAULT nfc_softecc_get; + +# Correct ECC +# +METHOD int correct_ecc { + device_t dev; + void *buf; + int pagesize; + void *readecc; + void *calcecc; +} DEFAULT nfc_softecc_correct; diff --git a/sys/dev/nand/nfc_mv.c b/sys/dev/nand/nfc_mv.c new file mode 100644 index 0000000..7f68e82 --- /dev/null +++ b/sys/dev/nand/nfc_mv.c @@ -0,0 +1,236 @@ +/*- + * Copyright (C) 2009-2012 Semihalf + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + */ + +/* Integrated NAND controller driver */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/proc.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/malloc.h> +#include <sys/rman.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/time.h> + +#include <machine/bus.h> +#include <machine/fdt.h> +#include <arm/mv/mvvar.h> +#include <arm/mv/mvwin.h> + +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <dev/nand/nand.h> +#include <dev/nand/nandbus.h> +#include "nfc_if.h" + +#define MV_NAND_DATA (0x00) +#define MV_NAND_COMMAND (0x01) +#define MV_NAND_ADDRESS (0x02) + +struct mv_nand_softc { + struct nand_softc nand_dev; + bus_space_handle_t sc_handle; + bus_space_tag_t sc_tag; + struct resource *res; + int rid; +}; + +static int mv_nand_attach(device_t); +static int mv_nand_probe(device_t); +static int mv_nand_send_command(device_t, uint8_t); +static int mv_nand_send_address(device_t, uint8_t); +static uint8_t mv_nand_read_byte(device_t); +static void mv_nand_read_buf(device_t, void *, uint32_t); +static void mv_nand_write_buf(device_t, void *, uint32_t); +static int mv_nand_select_cs(device_t, uint8_t); +static int mv_nand_read_rnb(device_t); + +static device_method_t mv_nand_methods[] = { + DEVMETHOD(device_probe, mv_nand_probe), + DEVMETHOD(device_attach, mv_nand_attach), + + DEVMETHOD(nfc_send_command, mv_nand_send_command), + DEVMETHOD(nfc_send_address, mv_nand_send_address), + DEVMETHOD(nfc_read_byte, mv_nand_read_byte), + DEVMETHOD(nfc_read_buf, mv_nand_read_buf), + DEVMETHOD(nfc_write_buf, mv_nand_write_buf), + DEVMETHOD(nfc_select_cs, mv_nand_select_cs), + DEVMETHOD(nfc_read_rnb, mv_nand_read_rnb), + + { 0, 0 }, +}; + +static driver_t mv_nand_driver = { + "nand", + mv_nand_methods, + sizeof(struct mv_nand_softc), +}; + +static devclass_t mv_nand_devclass; +DRIVER_MODULE(mv_nand, localbus, mv_nand_driver, mv_nand_devclass, 0, 0); + +static int +mv_nand_probe(device_t dev) +{ + + if (!ofw_bus_is_compatible(dev, "mrvl,nfc")) + return (ENXIO); + + device_set_desc(dev, "Marvell NAND controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +mv_nand_attach(device_t dev) +{ + struct mv_nand_softc *sc; + int err; + + sc = device_get_softc(dev); + sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->rid, + RF_ACTIVE); + if (sc->res == NULL) { + device_printf(dev, "could not allocate resources!\n"); + return (ENXIO); + } + + sc->sc_tag = rman_get_bustag(sc->res); + sc->sc_handle = rman_get_bushandle(sc->res); + + nand_init(&sc->nand_dev, dev, NAND_ECC_SOFT, 0, 0, NULL, NULL); + + err = nandbus_create(dev); + + return (err); +} + +static int +mv_nand_send_command(device_t dev, uint8_t command) +{ + struct mv_nand_softc *sc; + + nand_debug(NDBG_DRV,"mv_nand: send command %x", command); + + sc = device_get_softc(dev); + bus_space_write_1(sc->sc_tag, sc->sc_handle, MV_NAND_COMMAND, command); + return (0); +} + +static int +mv_nand_send_address(device_t dev, uint8_t addr) +{ + struct mv_nand_softc *sc; + + nand_debug(NDBG_DRV,"mv_nand: send address %x", addr); + + sc = device_get_softc(dev); + bus_space_write_1(sc->sc_tag, sc->sc_handle, MV_NAND_ADDRESS, addr); + return (0); +} + +static uint8_t +mv_nand_read_byte(device_t dev) +{ + struct mv_nand_softc *sc; + uint8_t data; + + sc = device_get_softc(dev); + data = bus_space_read_1(sc->sc_tag, sc->sc_handle, MV_NAND_DATA); + + nand_debug(NDBG_DRV,"mv_nand: read %x", data); + + return (data); +} + +static void +mv_nand_read_buf(device_t dev, void* buf, uint32_t len) +{ + struct mv_nand_softc *sc; + int i; + uint8_t *b = (uint8_t*)buf; + + sc = device_get_softc(dev); + + for (i = 0; i < len; i++) { + b[i] = bus_space_read_1(sc->sc_tag, sc->sc_handle, + MV_NAND_DATA); +#ifdef NAND_DEBUG + if (!(i % 16)) + printf("%s", i == 0 ? "mv_nand:\n" : "\n"); + printf(" %x", b[i]); + if (i == len - 1) + printf("\n"); +#endif + } +} + +static void +mv_nand_write_buf(device_t dev, void* buf, uint32_t len) +{ + struct mv_nand_softc *sc; + int i; + uint8_t *b = (uint8_t*)buf; + + sc = device_get_softc(dev); + + for (i = 0; i < len; i++) { +#ifdef NAND_DEBUG + if (!(i % 16)) + printf("%s", i == 0 ? "mv_nand:\n" : "\n"); + printf(" %x", b[i]); + if (i == len - 1) + printf("\n"); +#endif + bus_space_write_1(sc->sc_tag, sc->sc_handle, MV_NAND_DATA, + b[i]); + } +} + +static int +mv_nand_select_cs(device_t dev, uint8_t cs) +{ + + if (cs > 0) + return (ENODEV); + + return (0); +} + +static int +mv_nand_read_rnb(device_t dev) +{ + + /* no-op */ + return (0); /* ready */ +} |