X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=drivers%2Fmtd%2Fnand%2Fnand_util.c;h=71285b6669416b1402ebd8b03f7b9957b47a37a4;hb=57dc53a72460e8e301fa1cc7951b41db8e731485;hp=2ba0c5ef95604a32859e959822168a46f310c524;hpb=79f38777947ac7685e2cef8bd977f954ab198c0e;p=u-boot diff --git a/drivers/mtd/nand/nand_util.c b/drivers/mtd/nand/nand_util.c index 2ba0c5ef95..71285b6669 100644 --- a/drivers/mtd/nand/nand_util.c +++ b/drivers/mtd/nand/nand_util.c @@ -14,35 +14,16 @@ * 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. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License version - * 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * 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. + * + * SPDX-License-Identifier: GPL-2.0 */ #include #include #include #include +#include #include #include @@ -73,7 +54,6 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts) struct jffs2_unknown_node cleanmarker; erase_info_t erase; unsigned long erase_length, erased_length; /* in blocks */ - int bbtest = 1; int result; int percent_complete = -1; const char *mtd_device = meminfo->name; @@ -112,6 +92,7 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts) kfree(chip->bbt); } chip->bbt = NULL; + chip->options &= ~NAND_BBT_SCANNED; } for (erased_length = 0; @@ -120,8 +101,12 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts) WATCHDOG_RESET(); - if (!opts->scrub && bbtest) { - int ret = meminfo->block_isbad(meminfo, erase.addr); + if (opts->lim && (erase.addr >= (opts->offset + opts->lim))) { + puts("Size of erase exceeds limit\n"); + return -EFBIG; + } + if (!opts->scrub) { + int ret = mtd_block_isbad(meminfo, erase.addr); if (ret > 0) { if (!opts->quiet) printf("\rSkipping bad block at " @@ -144,7 +129,7 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts) erased_length++; - result = meminfo->erase(meminfo, &erase); + result = mtd_erase(meminfo, &erase); if (result != 0) { printf("\n%s: MTD Erase failure: %d\n", mtd_device, result); @@ -153,15 +138,16 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts) /* format for JFFS2 ? */ if (opts->jffs2 && chip->ecc.layout->oobavail >= 8) { - chip->ops.ooblen = 8; - chip->ops.datbuf = NULL; - chip->ops.oobbuf = (uint8_t *)&cleanmarker; - chip->ops.ooboffs = 0; - chip->ops.mode = MTD_OOB_AUTO; - - result = meminfo->write_oob(meminfo, - erase.addr, - &chip->ops); + struct mtd_oob_ops ops; + ops.ooblen = 8; + ops.datbuf = NULL; + ops.oobbuf = (uint8_t *)&cleanmarker; + ops.ooboffs = 0; + ops.mode = MTD_OPS_AUTO_OOB; + + result = mtd_write_oob(meminfo, + erase.addr, + &ops); if (result != 0) { printf("\n%s: MTD writeoob failure: %d\n", mtd_device, result); @@ -195,14 +181,14 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts) if (!opts->quiet) printf("\n"); - if (opts->scrub) - chip->scan_bbt(meminfo); - return 0; } #ifdef CONFIG_CMD_NAND_LOCK_UNLOCK +#define NAND_CMD_LOCK_TIGHT 0x2c +#define NAND_CMD_LOCK_STATUS 0x7a + /****************************************************************************** * Support for locking / unlocking operations of some NAND devices *****************************************************************************/ @@ -237,6 +223,14 @@ int nand_lock(struct mtd_info *mtd, int tight) /* select the NAND device */ chip->select_chip(mtd, 0); + /* 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); @@ -249,6 +243,7 @@ int nand_lock(struct mtd_info *mtd, int tight) ret = -1; } + out: /* de-select the NAND device */ chip->select_chip(mtd, -1); return ret; @@ -322,7 +317,7 @@ int nand_unlock(struct mtd_info *mtd, loff_t start, size_t length, int page; struct nand_chip *chip = mtd->priv; - debug("nand_unlock%s: start: %08llx, length: %d!\n", + debug("nand_unlock%s: start: %08llx, length: %zd!\n", allexcept ? " (allexcept)" : "", start, length); /* select the NAND device */ @@ -337,6 +332,15 @@ int nand_unlock(struct mtd_info *mtd, loff_t start, size_t length, goto out; } + /* 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"); @@ -358,7 +362,6 @@ int nand_unlock(struct mtd_info *mtd, loff_t start, size_t length, length -= mtd->erasesize; /* submit address of first page to unlock */ - page = (int)(start >> chip->page_shift); chip->cmdfunc(mtd, NAND_CMD_UNLOCK1, -1, page & chip->pagemask); /* submit ADDRESS of LAST page to unlock */ @@ -399,11 +402,13 @@ int nand_unlock(struct mtd_info *mtd, loff_t start, size_t length, * @param nand NAND device * @param offset offset in flash * @param length image length + * @param used length of flash needed for the requested length * @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 int check_skip_len(nand_info_t *nand, loff_t offset, size_t length) +static int check_skip_len(nand_info_t *nand, loff_t offset, size_t length, + size_t *used) { size_t len_excl_bad = 0; int ret = 0; @@ -425,8 +430,13 @@ static int check_skip_len(nand_info_t *nand, loff_t offset, size_t length) ret = 1; offset += block_len; + *used += block_len; } + /* If the length is not a multiple of block_len, adjust. */ + if (len_excl_bad > length) + *used -= (len_excl_bad - length); + return ret; } @@ -434,7 +444,8 @@ static int check_skip_len(nand_info_t *nand, loff_t offset, size_t length) static size_t drop_ffs(const nand_info_t *nand, const u_char *buf, const size_t *len) { - size_t i, l = *len; + size_t l = *len; + ssize_t i; for (i = l - 1; i >= 0; i--) if (buf[i] != 0xFF) @@ -453,47 +464,124 @@ static size_t drop_ffs(const nand_info_t *nand, const u_char *buf, } #endif +/** + * nand_verify_page_oob: + * + * Verify a page of NAND flash, including the OOB. + * Reads page of NAND and verifies the contents and OOB against the + * values in ops. + * + * @param nand NAND device + * @param ops MTD operations, including data to verify + * @param ofs offset in flash + * @return 0 in case of success + */ +int nand_verify_page_oob(nand_info_t *nand, struct mtd_oob_ops *ops, loff_t ofs) +{ + int rval; + struct mtd_oob_ops vops; + size_t verlen = nand->writesize + nand->oobsize; + + memcpy(&vops, ops, sizeof(vops)); + + vops.datbuf = memalign(ARCH_DMA_MINALIGN, verlen); + + if (!vops.datbuf) + return -ENOMEM; + + vops.oobbuf = vops.datbuf + nand->writesize; + + rval = mtd_read_oob(nand, ofs, &vops); + if (!rval) + rval = memcmp(ops->datbuf, vops.datbuf, vops.len); + if (!rval) + rval = memcmp(ops->oobbuf, vops.oobbuf, vops.ooblen); + + free(vops.datbuf); + + return rval ? -EIO : 0; +} + +/** + * nand_verify: + * + * Verify a region of NAND flash. + * Reads NAND in page-sized chunks and verifies the contents against + * the contents of a buffer. The offset into the NAND must be + * page-aligned, and the function doesn't handle skipping bad blocks. + * + * @param nand NAND device + * @param ofs offset in flash + * @param len buffer length + * @param buf buffer to read from + * @return 0 in case of success + */ +int nand_verify(nand_info_t *nand, loff_t ofs, size_t len, u_char *buf) +{ + int rval = 0; + size_t verofs; + size_t verlen = nand->writesize; + uint8_t *verbuf = memalign(ARCH_DMA_MINALIGN, verlen); + + if (!verbuf) + return -ENOMEM; + + /* Read the NAND back in page-size groups to limit malloc size */ + for (verofs = ofs; verofs < ofs + len; + verofs += verlen, buf += verlen) { + verlen = min(nand->writesize, (uint32_t)(ofs + len - verofs)); + rval = nand_read(nand, verofs, &verlen, verbuf); + if (!rval || (rval == -EUCLEAN)) + rval = memcmp(buf, verbuf, verlen); + + if (rval) + break; + } + + free(verbuf); + + return rval ? -EIO : 0; +} + + + /** * nand_write_skip_bad: * * Write image to NAND flash. * Blocks that are marked bad are skipped and the is written to the next * block instead as long as the image is short enough to fit even after - * skipping the bad blocks. + * skipping the bad blocks. Due to bad blocks we may not be able to + * perform the requested write. In the case where the write would + * extend beyond the end of the NAND device, both length and actual (if + * not NULL) are set to 0. In the case where the write would extend + * beyond the limit we are passed, length is set to 0 and actual is set + * to the required length. * * @param nand NAND device * @param offset offset in flash * @param length buffer length + * @param actual set to size required to write length worth of + * buffer or 0 on error, if not NULL + * @param lim maximum size that actual may be in order to not + * exceed the buffer * @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, loff_t offset, size_t *length, - u_char *buffer, int flags) + size_t *actual, loff_t lim, u_char *buffer, int flags) { int rval = 0, blocksize; size_t left_to_write = *length; + size_t used_for_write = 0; 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; - } + if (actual) + *actual = 0; + + blocksize = nand->erasesize; /* * nand_write() handles unaligned, partial page writes. @@ -512,15 +600,29 @@ int nand_write_skip_bad(nand_info_t *nand, loff_t offset, size_t *length, return -EINVAL; } - need_skip = check_skip_len(nand, offset, *length); + need_skip = check_skip_len(nand, offset, *length, &used_for_write); + + if (actual) + *actual = used_for_write; + if (need_skip < 0) { printf("Attempt to write outside the flash area\n"); *length = 0; return -EINVAL; } + if (used_for_write > lim) { + puts("Size of write exceeds partition or device limit\n"); + *length = 0; + return -EFBIG; + } + if (!need_skip && !(flags & WITH_DROP_FFS)) { rval = nand_write(nand, offset, length, buffer); + + if ((flags & WITH_WR_VERIFY) && !rval) + rval = nand_verify(nand, offset, *length, buffer); + if (rval == 0) return 0; @@ -548,48 +650,22 @@ int nand_write_skip_bad(nand_info_t *nand, loff_t offset, size_t *length, else 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; + 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); + 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, &truncated_write_size, + p_buffer); + + if ((flags & WITH_WR_VERIFY) && !rval) + rval = nand_verify(nand, offset, + truncated_write_size, p_buffer); + + offset += write_size; + p_buffer += write_size; if (rval != 0) { printf("NAND write to offset %llx failed %d\n", @@ -609,36 +685,58 @@ int nand_write_skip_bad(nand_info_t *nand, loff_t offset, size_t *length, * * Read image from NAND flash. * 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. + * instead as long as the image is short enough to fit even after + * skipping the bad blocks. Due to bad blocks we may not be able to + * perform the requested read. In the case where the read would extend + * beyond the end of the NAND device, both length and actual (if not + * NULL) are set to 0. In the case where the read would extend beyond + * the limit we are passed, length is set to 0 and actual is set to the + * required length. * * @param nand NAND device * @param offset offset in flash * @param length buffer length, on return holds number of read bytes + * @param actual set to size required to read length worth of buffer or 0 + * on error, if not NULL + * @param lim maximum size that actual may be in order to not exceed the + * buffer * @param buffer buffer to write to * @return 0 in case of success */ int nand_read_skip_bad(nand_info_t *nand, loff_t offset, size_t *length, - u_char *buffer) + size_t *actual, loff_t lim, u_char *buffer) { int rval; size_t left_to_read = *length; + size_t used_for_read = 0; u_char *p_buffer = buffer; int need_skip; if ((offset & (nand->writesize - 1)) != 0) { printf("Attempt to read non page-aligned data\n"); *length = 0; + if (actual) + *actual = 0; return -EINVAL; } - need_skip = check_skip_len(nand, offset, *length); + need_skip = check_skip_len(nand, offset, *length, &used_for_read); + + if (actual) + *actual = used_for_read; + if (need_skip < 0) { printf("Attempt to read outside the flash area\n"); *length = 0; return -EINVAL; } + if (used_for_read > lim) { + puts("Size of read exceeds partition or device limit\n"); + *length = 0; + return -EFBIG; + } + if (!need_skip) { rval = nand_read(nand, offset, length, buffer); if (!rval || rval == -EUCLEAN) @@ -742,7 +840,7 @@ int nand_torture(nand_info_t *nand, loff_t offset) patt_count = ARRAY_SIZE(patterns); - buf = malloc(nand->erasesize); + buf = malloc_cache_aligned(nand->erasesize); if (buf == NULL) { puts("Out of memory for erase block buffer\n"); return -ENOMEM;