X-Git-Url: https://git.kernelconcepts.de/?a=blobdiff_plain;f=drivers%2Fmtd%2Fnand%2Fnand_util.c;h=ff2d3483076b1a5d92868270d136d8b599ea5b08;hb=412665b46134f93464c09405e02f08ac9c62526d;hp=22820d55d02c64b93ac6b17b62c6b93744bca870;hpb=cc4a0ceeac5462106172d0cc9d9d542233aa3ab2;p=karo-tx-uboot.git diff --git a/drivers/mtd/nand/nand_util.c b/drivers/mtd/nand/nand_util.c index 22820d55d0..ff2d348307 100644 --- a/drivers/mtd/nand/nand_util.c +++ b/drivers/mtd/nand/nand_util.c @@ -11,6 +11,9 @@ * nandwrite.c by Steven J. Hill (sjhill@realitydiluted.com) * and Thomas Gleixner (tglx@linutronix.de) * + * Copyright (C) 2008 Nokia Corporation: drop_ffs() function by + * Artem Bityutskiy from mtd-utils + * * See file CREDITS for list of people who contributed to this * project. * @@ -28,6 +31,12 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * + * Copyright 2010 Freescale Semiconductor + * The portions of this file whose copyright is held by Freescale and which + * are not considered a derived work of GPL v2-only code may be distributed + * and/or modified under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. */ #include @@ -36,28 +45,21 @@ #include #include - #include #include #include #include -typedef struct erase_info erase_info_t; -typedef struct mtd_info mtd_info_t; +typedef struct erase_info erase_info_t; +typedef struct mtd_info mtd_info_t; /* support only for native endian JFFS2 */ #define cpu_to_je16(x) (x) #define cpu_to_je32(x) (x) -/*****************************************************************************/ -static int nand_block_bad_scrub(struct mtd_info *mtd, loff_t ofs, int getchip) -{ - return 0; -} - /** * nand_erase_opts: - erase NAND flash with support for various options - * (jffs2 formating) + * (jffs2 formatting) * * @param meminfo NAND device to erase * @param opts options, @see struct nand_erase_options @@ -70,72 +72,66 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts) { struct jffs2_unknown_node cleanmarker; erase_info_t erase; - ulong erase_length; + unsigned long erase_length, erased_length; /* in blocks */ int bbtest = 1; int result; int percent_complete = -1; - int (*nand_block_bad_old)(struct mtd_info *, loff_t, int) = NULL; const char *mtd_device = meminfo->name; struct mtd_oob_ops oob_opts; struct nand_chip *chip = meminfo->priv; - uint8_t buf[64]; - memset(buf, 0, sizeof(buf)); + if ((opts->offset & (meminfo->erasesize - 1)) != 0) { + printf("Attempt to erase non block-aligned data\n"); + return -1; + } + memset(&erase, 0, sizeof(erase)); memset(&oob_opts, 0, sizeof(oob_opts)); erase.mtd = meminfo; erase.len = meminfo->erasesize; erase.addr = opts->offset; - erase_length = opts->length; - + erase_length = lldiv(opts->length + meminfo->erasesize - 1, + meminfo->erasesize); - cleanmarker.magic = cpu_to_je16 (JFFS2_MAGIC_BITMASK); - cleanmarker.nodetype = cpu_to_je16 (JFFS2_NODETYPE_CLEANMARKER); + cleanmarker.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); + cleanmarker.nodetype = cpu_to_je16(JFFS2_NODETYPE_CLEANMARKER); cleanmarker.totlen = cpu_to_je32(8); - cleanmarker.hdr_crc = cpu_to_je32( - crc32_no_comp(0, (unsigned char *) &cleanmarker, - sizeof(struct jffs2_unknown_node) - 4)); /* scrub option allows to erase badblock. To prevent internal * check from erase() method, set block check method to dummy * and disable bad block table while erasing. */ if (opts->scrub) { - struct nand_chip *priv_nand = meminfo->priv; - - nand_block_bad_old = priv_nand->block_bad; - priv_nand->block_bad = nand_block_bad_scrub; - /* we don't need the bad block table anymore... + erase.scrub = opts->scrub; + /* + * We don't need the bad block table anymore... * after scrub, there are no bad blocks left! */ - if (priv_nand->bbt) { - kfree(priv_nand->bbt); + if (chip->bbt) { + kfree(chip->bbt); } - priv_nand->bbt = NULL; - } - - if (erase_length < meminfo->erasesize) { - printf("Warning: Erase size 0x%08lx smaller than one " \ - "erase block 0x%08x\n",erase_length, meminfo->erasesize); - printf(" Erasing 0x%08x instead\n", meminfo->erasesize); - erase_length = meminfo->erasesize; + chip->bbt = NULL; } - for (; - erase.addr < opts->offset + erase_length; + for (erased_length = 0; + erased_length < erase_length; erase.addr += meminfo->erasesize) { - WATCHDOG_RESET (); + WATCHDOG_RESET(); if (!opts->scrub && bbtest) { int ret = meminfo->block_isbad(meminfo, erase.addr); if (ret > 0) { if (!opts->quiet) printf("\rSkipping bad block at " - "0x%08x " + "0x%08llx " " \n", erase.addr); + + if (!opts->spread) + erased_length++; + continue; } else if (ret < 0) { @@ -146,6 +142,8 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts) } } + erased_length++; + result = meminfo->erase(meminfo, &erase); if (result != 0) { printf("\n%s: MTD Erase failure: %d\n", @@ -154,29 +152,25 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts) } /* format for JFFS2 ? */ - if (opts->jffs2) { - - chip->ops.len = chip->ops.ooblen = 64; + if (opts->jffs2 && chip->ecc.layout->oobavail >= 8) { + chip->ops.ooblen = 8; chip->ops.datbuf = NULL; - chip->ops.oobbuf = buf; - chip->ops.ooboffs = chip->badblockpos & ~0x01; + chip->ops.oobbuf = (uint8_t *)&cleanmarker; + chip->ops.ooboffs = 0; + chip->ops.mode = MTD_OOB_AUTO; result = meminfo->write_oob(meminfo, - erase.addr + meminfo->oobsize, - &chip->ops); + erase.addr, + &chip->ops); if (result != 0) { printf("\n%s: MTD writeoob failure: %d\n", - mtd_device, result); + mtd_device, result); continue; } - else - printf("%s: MTD writeoob at 0x%08x\n",mtd_device, erase.addr + meminfo->oobsize ); } if (!opts->quiet) { - unsigned long long n =(unsigned long long) - (erase.addr + meminfo->erasesize - opts->offset) - * 100; + unsigned long long n = erased_length * 100ULL; int percent; do_div(n, erase_length); @@ -189,79 +183,35 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts) if (percent != percent_complete) { percent_complete = percent; - printf("\rErasing at 0x%x -- %3d%% complete.", - erase.addr, percent); + printf("\rErasing at 0x%llx -- %3d%% complete.", + erase.addr, percent); if (opts->jffs2 && result == 0) - printf(" Cleanmarker written at 0x%x.", - erase.addr); + printf(" Cleanmarker written at 0x%llx.", + erase.addr); } } } if (!opts->quiet) printf("\n"); - if (nand_block_bad_old) { - struct nand_chip *priv_nand = meminfo->priv; - - priv_nand->block_bad = nand_block_bad_old; - priv_nand->scan_bbt(meminfo); - } + if (opts->scrub) + chip->scan_bbt(meminfo); return 0; } -/* XXX U-BOOT XXX */ -#if 0 - -#define MAX_PAGE_SIZE 2048 -#define MAX_OOB_SIZE 64 - -/* - * buffer array used for writing data - */ -static unsigned char data_buf[MAX_PAGE_SIZE]; -static unsigned char oob_buf[MAX_OOB_SIZE]; - -/* OOB layouts to pass into the kernel as default */ -static struct nand_ecclayout none_ecclayout = { - .useecc = MTD_NANDECC_OFF, -}; - -static struct nand_ecclayout jffs2_ecclayout = { - .useecc = MTD_NANDECC_PLACE, - .eccbytes = 6, - .eccpos = { 0, 1, 2, 3, 6, 7 } -}; - -static struct nand_ecclayout yaffs_ecclayout = { - .useecc = MTD_NANDECC_PLACE, - .eccbytes = 6, - .eccpos = { 8, 9, 10, 13, 14, 15} -}; - -static struct nand_ecclayout autoplace_ecclayout = { - .useecc = MTD_NANDECC_AUTOPLACE -}; -#endif +#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK -/* XXX U-BOOT XXX */ -#if 0 /****************************************************************************** * Support for locking / unlocking operations of some NAND devices *****************************************************************************/ -#define NAND_CMD_LOCK 0x2a -#define NAND_CMD_LOCK_TIGHT 0x2c -#define NAND_CMD_UNLOCK1 0x23 -#define NAND_CMD_UNLOCK2 0x24 -#define NAND_CMD_LOCK_STATUS 0x7a - /** * nand_lock: Set all pages of NAND flash chip to the LOCK or LOCK-TIGHT * state * - * @param meminfo nand mtd instance + * @param mtd nand mtd instance * @param tight bring device in lock tight mode * * @return 0 on success, -1 in case of error @@ -278,29 +228,38 @@ static struct nand_ecclayout autoplace_ecclayout = { * calls will fail. It is only posible to leave lock-tight state by * an hardware signal (low pulse on _WP pin) or by power down. */ -int nand_lock(nand_info_t *meminfo, int tight) +int nand_lock(struct mtd_info *mtd, int tight) { int ret = 0; int status; - struct nand_chip *this = meminfo->priv; + struct nand_chip *chip = mtd->priv; /* select the NAND device */ - this->select_chip(meminfo, 0); + chip->select_chip(mtd, 0); - this->cmdfunc(meminfo, + /* check the Lock Tight Status */ + chip->cmdfunc(mtd, NAND_CMD_LOCK_STATUS, -1, 0); + if (chip->read_byte(mtd) & NAND_LOCK_STATUS_TIGHT) { + printf("nand_lock: Device is locked tight!\n"); + ret = -1; + goto out; + } + + chip->cmdfunc(mtd, (tight ? NAND_CMD_LOCK_TIGHT : NAND_CMD_LOCK), -1, -1); /* call wait ready function */ - status = this->waitfunc(meminfo, this, FL_WRITING); + status = chip->waitfunc(mtd, chip); /* see if device thinks it succeeded */ if (status & 0x01) { ret = -1; } + out: /* de-select the NAND device */ - this->select_chip(meminfo, -1); + chip->select_chip(mtd, -1); return ret; } @@ -308,31 +267,30 @@ int nand_lock(nand_info_t *meminfo, int tight) * nand_get_lock_status: - query current lock state from one page of NAND * flash * - * @param meminfo nand mtd instance - * @param offset page address to query (muss be page aligned!) + * @param mtd nand mtd instance + * @param offset page address to query (must be page-aligned!) * * @return -1 in case of error * >0 lock status: * bitfield with the following combinations: * NAND_LOCK_STATUS_TIGHT: page in tight state - * NAND_LOCK_STATUS_LOCK: page locked * NAND_LOCK_STATUS_UNLOCK: page unlocked * */ -int nand_get_lock_status(nand_info_t *meminfo, ulong offset) +int nand_get_lock_status(struct mtd_info *mtd, loff_t offset) { int ret = 0; int chipnr; int page; - struct nand_chip *this = meminfo->priv; + struct nand_chip *chip = mtd->priv; /* select the NAND device */ - chipnr = (int)(offset >> this->chip_shift); - this->select_chip(meminfo, chipnr); + chipnr = (int)(offset >> chip->chip_shift); + chip->select_chip(mtd, chipnr); - if ((offset & (meminfo->writesize - 1)) != 0) { - printf ("nand_get_lock_status: " + if ((offset & (mtd->writesize - 1)) != 0) { + printf("nand_get_lock_status: " "Start address must be beginning of " "nand page!\n"); ret = -1; @@ -340,16 +298,15 @@ int nand_get_lock_status(nand_info_t *meminfo, ulong offset) } /* check the Lock Status */ - page = (int)(offset >> this->page_shift); - this->cmdfunc(meminfo, NAND_CMD_LOCK_STATUS, -1, page & this->pagemask); + page = (int)(offset >> chip->page_shift); + chip->cmdfunc(mtd, NAND_CMD_LOCK_STATUS, -1, page & chip->pagemask); - ret = this->read_byte(meminfo) & (NAND_LOCK_STATUS_TIGHT - | NAND_LOCK_STATUS_LOCK + ret = chip->read_byte(mtd) & (NAND_LOCK_STATUS_TIGHT | NAND_LOCK_STATUS_UNLOCK); out: /* de-select the NAND device */ - this->select_chip(meminfo, -1); + chip->select_chip(mtd, -1); return ret; } @@ -357,59 +314,85 @@ int nand_get_lock_status(nand_info_t *meminfo, ulong offset) * nand_unlock: - Unlock area of NAND pages * only one consecutive area can be unlocked at one time! * - * @param meminfo nand mtd instance + * @param mtd nand mtd instance * @param start start byte address * @param length number of bytes to unlock (must be a multiple of * page size nand->writesize) + * @param allexcept if set, unlock everything not selected * * @return 0 on success, -1 in case of error */ -int nand_unlock(nand_info_t *meminfo, ulong start, ulong length) +int nand_unlock(struct mtd_info *mtd, loff_t start, size_t length, + int allexcept) { int ret = 0; int chipnr; int status; int page; - struct nand_chip *this = meminfo->priv; - printf ("nand_unlock: start: %08x, length: %d!\n", - (int)start, (int)length); + struct nand_chip *chip = mtd->priv; + + debug("nand_unlock%s: start: %08llx, length: %d!\n", + allexcept ? " (allexcept)" : "", start, length); /* select the NAND device */ - chipnr = (int)(start >> this->chip_shift); - this->select_chip(meminfo, chipnr); + chipnr = (int)(start >> chip->chip_shift); + chip->select_chip(mtd, chipnr); /* check the WP bit */ - this->cmdfunc(meminfo, NAND_CMD_STATUS, -1, -1); - if ((this->read_byte(meminfo) & 0x80) == 0) { - printf ("nand_unlock: Device is write protected!\n"); + chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); + if (!(chip->read_byte(mtd) & NAND_STATUS_WP)) { + printf("nand_unlock: Device is write protected!\n"); ret = -1; goto out; } - if ((start & (meminfo->writesize - 1)) != 0) { - printf ("nand_unlock: Start address must be beginning of " - "nand page!\n"); + /* check the Lock Tight Status */ + page = (int)(start >> chip->page_shift); + chip->cmdfunc(mtd, NAND_CMD_LOCK_STATUS, -1, page & chip->pagemask); + if (chip->read_byte(mtd) & NAND_LOCK_STATUS_TIGHT) { + printf("nand_unlock: Device is locked tight!\n"); + ret = -1; + goto out; + } + + if ((start & (mtd->erasesize - 1)) != 0) { + printf("nand_unlock: Start address must be beginning of " + "nand block!\n"); ret = -1; goto out; } - if (length == 0 || (length & (meminfo->writesize - 1)) != 0) { - printf ("nand_unlock: Length must be a multiple of nand page " - "size!\n"); + if (length == 0 || (length & (mtd->erasesize - 1)) != 0) { + printf("nand_unlock: Length must be a multiple of nand block " + "size %08x!\n", mtd->erasesize); ret = -1; goto out; } + /* + * Set length so that the last address is set to the + * starting address of the last block + */ + length -= mtd->erasesize; + /* submit address of first page to unlock */ - page = (int)(start >> this->page_shift); - this->cmdfunc(meminfo, NAND_CMD_UNLOCK1, -1, page & this->pagemask); + chip->cmdfunc(mtd, NAND_CMD_UNLOCK1, -1, page & chip->pagemask); /* submit ADDRESS of LAST page to unlock */ - page += (int)(length >> this->page_shift) - 1; - this->cmdfunc(meminfo, NAND_CMD_UNLOCK2, -1, page & this->pagemask); + page += (int)(length >> chip->page_shift); + + /* + * Page addresses for unlocking are supposed to be block-aligned. + * At least some NAND chips use the low bit to indicate that the + * page range should be inverted. + */ + if (allexcept) + page |= 1; + + chip->cmdfunc(mtd, NAND_CMD_UNLOCK2, -1, page & chip->pagemask); /* call wait ready function */ - status = this->waitfunc(meminfo, this, FL_WRITING); + status = chip->waitfunc(mtd, chip); /* see if device thinks it succeeded */ if (status & 0x01) { /* there was an error */ @@ -419,43 +402,73 @@ int nand_unlock(nand_info_t *meminfo, ulong start, ulong length) out: /* de-select the NAND device */ - this->select_chip(meminfo, -1); + chip->select_chip(mtd, -1); return ret; } #endif /** - * get_len_incl_bad + * check_skip_len * - * Check if length including bad blocks fits into device. + * Check if there are any bad blocks, and whether length including bad + * blocks fits into device * * @param nand NAND device * @param offset offset in flash * @param length image length - * @return image length including bad blocks + * @return 0 if the image fits and there are no bad blocks + * 1 if the image fits, but there are bad blocks + * -1 if the image does not fit */ -static size_t get_len_incl_bad (nand_info_t *nand, size_t offset, - const size_t length) +static int check_skip_len(nand_info_t *nand, loff_t offset, size_t length) { - size_t len_incl_bad = 0; size_t len_excl_bad = 0; - size_t block_len; + int ret = 0; while (len_excl_bad < length) { - block_len = nand->erasesize - (offset & (nand->erasesize - 1)); + size_t block_len, block_off; + loff_t block_start; - if (!nand_block_isbad (nand, offset & ~(nand->erasesize - 1))) + if (offset >= nand->size) + return -1; + + block_start = offset & ~(loff_t)(nand->erasesize - 1); + block_off = offset & (nand->erasesize - 1); + block_len = nand->erasesize - block_off; + + if (!nand_block_isbad(nand, block_start)) len_excl_bad += block_len; + else + ret = 1; + + offset += block_len; + } - len_incl_bad += block_len; - offset += block_len; + return ret; +} - if ((offset + len_incl_bad) >= nand->size) +#ifdef CONFIG_CMD_NAND_TRIMFFS +static size_t drop_ffs(const nand_info_t *nand, const u_char *buf, + const size_t *len) +{ + size_t i, l = *len; + + for (i = l - 1; i >= 0; i--) + if (buf[i] != 0xFF) break; - } - return len_incl_bad; + /* The resulting length must be aligned to the minimum flash I/O size */ + l = i + 1; + l = (l + nand->writesize - 1) / nand->writesize; + l *= nand->writesize; + + /* + * since the input length may be unaligned, prevent access past the end + * of the buffer + */ + return min(l, *len); } +#endif /** * nand_write_skip_bad: @@ -468,67 +481,141 @@ static size_t get_len_incl_bad (nand_info_t *nand, size_t offset, * @param nand NAND device * @param offset offset in flash * @param length buffer length - * @param buf buffer to read from + * @param buffer buffer to read from + * @param flags flags modifying the behaviour of the write to NAND * @return 0 in case of success */ -int nand_write_skip_bad(nand_info_t *nand, size_t offset, size_t *length, - u_char *buffer) +int nand_write_skip_bad(nand_info_t *nand, loff_t offset, size_t *length, + u_char *buffer, int flags) { - int rval; + int rval = 0, blocksize; size_t left_to_write = *length; - size_t len_incl_bad; u_char *p_buffer = buffer; + int need_skip; + +#ifdef CONFIG_CMD_NAND_YAFFS + if (flags & WITH_YAFFS_OOB) { + if (flags & ~WITH_YAFFS_OOB) + return -EINVAL; + + int pages; + pages = nand->erasesize / nand->writesize; + blocksize = (pages * nand->oobsize) + nand->erasesize; + if (*length % (nand->writesize + nand->oobsize)) { + printf("Attempt to write incomplete page" + " in yaffs mode\n"); + return -EINVAL; + } + } else +#endif + { + blocksize = nand->erasesize; + } - /* Reject writes, which are not page aligned */ - if ((offset & (nand->writesize - 1)) != 0 || - (*length & (nand->writesize - 1)) != 0) { - printf ("Attempt to write non page aligned data\n"); + /* + * nand_write() handles unaligned, partial page writes. + * + * We allow length to be unaligned, for convenience in + * using the $filesize variable. + * + * However, starting at an unaligned offset makes the + * semantics of bad block skipping ambiguous (really, + * you should only start a block skipping access at a + * partition boundary). So don't try to handle that. + */ + if ((offset & (nand->writesize - 1)) != 0) { + printf("Attempt to write non page-aligned data\n"); + *length = 0; return -EINVAL; } - len_incl_bad = get_len_incl_bad (nand, offset, *length); - - if ((offset + len_incl_bad) >= nand->size) { - printf ("Attempt to write outside the flash area\n"); + need_skip = check_skip_len(nand, offset, *length); + if (need_skip < 0) { + printf("Attempt to write outside the flash area\n"); + *length = 0; return -EINVAL; } - if (len_incl_bad == *length) { - rval = nand_write (nand, offset, length, buffer); - if (rval != 0) { - printf ("NAND write to offset %x failed %d\n", - offset, rval); - return rval; - } + if (!need_skip && !(flags & WITH_DROP_FFS)) { + rval = nand_write(nand, offset, length, buffer); + if (rval == 0) + return 0; + + *length = 0; + printf("NAND write to offset %llx failed %d\n", + offset, rval); + return rval; } while (left_to_write > 0) { size_t block_offset = offset & (nand->erasesize - 1); - size_t write_size; + size_t write_size, truncated_write_size; + + WATCHDOG_RESET(); - if (nand_block_isbad (nand, offset & ~(nand->erasesize - 1))) { - printf ("Skip bad block 0x%08x\n", + if (nand_block_isbad(nand, offset & ~(nand->erasesize - 1))) { + printf("Skip bad block 0x%08llx\n", offset & ~(nand->erasesize - 1)); offset += nand->erasesize - block_offset; continue; } - if (left_to_write < (nand->erasesize - block_offset)) + if (left_to_write < (blocksize - block_offset)) write_size = left_to_write; else - write_size = nand->erasesize - block_offset; + write_size = blocksize - block_offset; + +#ifdef CONFIG_CMD_NAND_YAFFS + if (flags & WITH_YAFFS_OOB) { + int page, pages; + size_t pagesize = nand->writesize; + size_t pagesize_oob = pagesize + nand->oobsize; + struct mtd_oob_ops ops; + + ops.len = pagesize; + ops.ooblen = nand->oobsize; + ops.mode = MTD_OOB_AUTO; + ops.ooboffs = 0; + + pages = write_size / pagesize_oob; + for (page = 0; page < pages; page++) { + WATCHDOG_RESET(); + + ops.datbuf = p_buffer; + ops.oobbuf = ops.datbuf + pagesize; + + rval = nand->write_oob(nand, offset, &ops); + if (rval != 0) + break; + + offset += pagesize; + p_buffer += pagesize_oob; + } + } + else +#endif + { + truncated_write_size = write_size; +#ifdef CONFIG_CMD_NAND_TRIMFFS + if (flags & WITH_DROP_FFS) + truncated_write_size = drop_ffs(nand, p_buffer, + &write_size); +#endif + + rval = nand_write(nand, offset, &truncated_write_size, + p_buffer); + offset += write_size; + p_buffer += write_size; + } - rval = nand_write (nand, offset, &write_size, p_buffer); if (rval != 0) { - printf ("NAND write to offset %x failed %d\n", - offset, rval); + printf("NAND write to offset %llx failed %d\n", + offset, rval); *length -= left_to_write; return rval; } left_to_write -= write_size; - offset += write_size; - p_buffer += write_size; } return 0; @@ -538,46 +625,56 @@ int nand_write_skip_bad(nand_info_t *nand, size_t offset, size_t *length, * nand_read_skip_bad: * * Read image from NAND flash. - * Blocks that are marked bad are skipped and the next block is readen + * Blocks that are marked bad are skipped and the next block is read * instead as long as the image is short enough to fit even after skipping the * bad blocks. * * @param nand NAND device * @param offset offset in flash - * @param length buffer length, on return holds remaining bytes to read + * @param length buffer length, on return holds number of read bytes * @param buffer buffer to write to * @return 0 in case of success */ -int nand_read_skip_bad(nand_info_t *nand, size_t offset, size_t *length, +int nand_read_skip_bad(nand_info_t *nand, loff_t offset, size_t *length, u_char *buffer) { int rval; size_t left_to_read = *length; - size_t len_incl_bad; u_char *p_buffer = buffer; + int need_skip; - len_incl_bad = get_len_incl_bad (nand, offset, *length); + if ((offset & (nand->writesize - 1)) != 0) { + printf("Attempt to read non page-aligned data\n"); + *length = 0; + return -EINVAL; + } - if ((offset + len_incl_bad) >= nand->size) { - printf ("Attempt to read outside the flash area\n"); + need_skip = check_skip_len(nand, offset, *length); + if (need_skip < 0) { + printf("Attempt to read outside the flash area\n"); + *length = 0; return -EINVAL; } - if (len_incl_bad == *length) { - rval = nand_read (nand, offset, length, buffer); - if (rval != 0) { - printf ("NAND read from offset %x failed %d\n", - offset, rval); - return rval; - } + if (!need_skip) { + rval = nand_read(nand, offset, length, buffer); + if (!rval || rval == -EUCLEAN) + return 0; + + *length = 0; + printf("NAND read from offset %llx failed %d\n", + offset, rval); + return rval; } while (left_to_read > 0) { size_t block_offset = offset & (nand->erasesize - 1); size_t read_length; - if (nand_block_isbad (nand, offset & ~(nand->erasesize - 1))) { - printf ("Skipping bad block 0x%08x\n", + WATCHDOG_RESET(); + + if (nand_block_isbad(nand, offset & ~(nand->erasesize - 1))) { + printf("Skipping bad block 0x%08llx\n", offset & ~(nand->erasesize - 1)); offset += nand->erasesize - block_offset; continue; @@ -588,10 +685,10 @@ int nand_read_skip_bad(nand_info_t *nand, size_t offset, size_t *length, else read_length = nand->erasesize - block_offset; - rval = nand_read (nand, offset, &read_length, p_buffer); - if (rval != 0) { - printf ("NAND read from offset %x failed %d\n", - offset, rval); + rval = nand_read(nand, offset, &read_length, p_buffer); + if (rval && rval != -EUCLEAN) { + printf("NAND read from offset %llx failed %d\n", + offset, rval); *length -= left_to_read; return rval; } @@ -603,3 +700,125 @@ int nand_read_skip_bad(nand_info_t *nand, size_t offset, size_t *length, return 0; } + +#ifdef CONFIG_CMD_NAND_TORTURE + +/** + * check_pattern: + * + * Check if buffer contains only a certain byte pattern. + * + * @param buf buffer to check + * @param patt the pattern to check + * @param size buffer size in bytes + * @return 1 if there are only patt bytes in buf + * 0 if something else was found + */ +static int check_pattern(const u_char *buf, u_char patt, int size) +{ + int i; + + for (i = 0; i < size; i++) + if (buf[i] != patt) + return 0; + return 1; +} + +/** + * nand_torture: + * + * Torture a block of NAND flash. + * This is useful to determine if a block that caused a write error is still + * good or should be marked as bad. + * + * @param nand NAND device + * @param offset offset in flash + * @return 0 if the block is still good + */ +int nand_torture(nand_info_t *nand, loff_t offset) +{ + u_char patterns[] = {0xa5, 0x5a, 0x00}; + struct erase_info instr = { + .mtd = nand, + .addr = offset, + .len = nand->erasesize, + }; + size_t retlen; + int err, ret = -1, i, patt_count; + u_char *buf; + + if ((offset & (nand->erasesize - 1)) != 0) { + puts("Attempt to torture a block at a non block-aligned offset\n"); + return -EINVAL; + } + + if (offset + nand->erasesize > nand->size) { + puts("Attempt to torture a block outside the flash area\n"); + return -EINVAL; + } + + patt_count = ARRAY_SIZE(patterns); + + buf = malloc(nand->erasesize); + if (buf == NULL) { + puts("Out of memory for erase block buffer\n"); + return -ENOMEM; + } + + for (i = 0; i < patt_count; i++) { + err = nand->erase(nand, &instr); + if (err) { + printf("%s: erase() failed for block at 0x%llx: %d\n", + nand->name, instr.addr, err); + goto out; + } + + /* Make sure the block contains only 0xff bytes */ + err = nand->read(nand, offset, nand->erasesize, &retlen, buf); + if ((err && err != -EUCLEAN) || retlen != nand->erasesize) { + printf("%s: read() failed for block at 0x%llx: %d\n", + nand->name, instr.addr, err); + goto out; + } + + err = check_pattern(buf, 0xff, nand->erasesize); + if (!err) { + printf("Erased block at 0x%llx, but a non-0xff byte was found\n", + offset); + ret = -EIO; + goto out; + } + + /* Write a pattern and check it */ + memset(buf, patterns[i], nand->erasesize); + err = nand->write(nand, offset, nand->erasesize, &retlen, buf); + if (err || retlen != nand->erasesize) { + printf("%s: write() failed for block at 0x%llx: %d\n", + nand->name, instr.addr, err); + goto out; + } + + err = nand->read(nand, offset, nand->erasesize, &retlen, buf); + if ((err && err != -EUCLEAN) || retlen != nand->erasesize) { + printf("%s: read() failed for block at 0x%llx: %d\n", + nand->name, instr.addr, err); + goto out; + } + + err = check_pattern(buf, patterns[i], nand->erasesize); + if (!err) { + printf("Pattern 0x%.2x checking failed for block at " + "0x%llx\n", patterns[i], offset); + ret = -EIO; + goto out; + } + } + + ret = 0; + +out: + free(buf); + return ret; +} + +#endif