From 88271cb3ae9c68dc200d627653df96fc557c2a64 Mon Sep 17 00:00:00 2001 From: Weijie Gao Date: Mon, 25 Jul 2022 10:55:35 +0800 Subject: [PATCH 47/71] cmd: add a new command for NAND flash debugging Add a command 'nand-ext' for NAND flash debugging: - Dump a page with oob, with optional raw read support - Display all bad blocks - Mark a block as bad block - Set a bitflip on a page - Erase - Read / write data from/to any offset with any size - Read / write pages with oob - Erase, read and write support skip bad block or forced mode, support raw mode, supporot auto-oob mode - Supports operating on a specific partition - No need to specify NAND device name Signed-off-by: Weijie Gao --- cmd/Kconfig | 8 + cmd/Makefile | 1 + cmd/nand-ext.c | 1062 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1071 insertions(+) create mode 100644 cmd/nand-ext.c --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1465,6 +1465,14 @@ config CMD_NAND_TORTURE endif # CMD_NAND +config CMD_NAND_EXT + bool "nand - extended nand utility for debugging" + depends on !CMD_NAND + default y if MTD_RAW_NAND || MTD_SPI_NAND || MTK_SPI_NAND + select MTD_PARTITIONS + help + NAND flash R/W and debugging support. + config CMD_NMBM depends on NMBM_MTD bool "nmbm" --- a/cmd/Makefile +++ b/cmd/Makefile @@ -127,6 +127,7 @@ obj-y += legacy-mtd-utils.o endif obj-$(CONFIG_CMD_MUX) += mux.o obj-$(CONFIG_CMD_NAND) += nand.o +obj-$(CONFIG_CMD_NAND_EXT) += nand-ext.o obj-$(CONFIG_CMD_NMBM) += nmbm.o obj-$(CONFIG_CMD_NET) += net.o obj-$(CONFIG_ENV_SUPPORT) += nvedit.o --- /dev/null +++ b/cmd/nand-ext.c @@ -0,0 +1,1062 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 MediaTek Inc. All Rights Reserved. + * + * Author: Weijie Gao + */ + +#include +#include +#include +#include +#include +#include +#include + +static struct mtd_info *curr_dev; + +static void mtd_show_parts(struct mtd_info *mtd, int level) +{ + struct mtd_info *part; + int i; + + list_for_each_entry(part, &mtd->partitions, node) { + for (i = 0; i < level; i++) + printf("\t"); + printf(" - 0x%012llx-0x%012llx : \"%s\"\n", + part->offset, part->offset + part->size, part->name); + + mtd_show_parts(part, level + 1); + } +} + +static void mtd_show_device(struct mtd_info *mtd) +{ + /* Device */ + printf("* %s\n", mtd->name); +#if defined(CONFIG_DM) + if (mtd->dev) { + printf(" - device: %s\n", mtd->dev->name); + printf(" - parent: %s\n", mtd->dev->parent->name); + printf(" - driver: %s\n", mtd->dev->driver->name); + } +#endif + + /* MTD device information */ + printf(" - type: "); + switch (mtd->type) { + case MTD_NANDFLASH: + printf("NAND flash\n"); + break; + case MTD_MLCNANDFLASH: + printf("MLC NAND flash\n"); + break; + case MTD_ABSENT: + default: + printf("Not supported\n"); + break; + } + + printf(" - block size: 0x%x bytes\n", mtd->erasesize); + printf(" - page size: 0x%x bytes\n", mtd->writesize); + printf(" - OOB size: %u bytes\n", mtd->oobsize); + printf(" - OOB available: %u bytes\n", mtd->oobavail); + + if (mtd->ecc_strength) { + printf(" - ECC strength: %u bits\n", mtd->ecc_strength); + printf(" - ECC step size: %u bytes\n", mtd->ecc_step_size); + printf(" - bitflip threshold: %u bits\n", + mtd->bitflip_threshold); + } + + printf(" - 0x%012llx-0x%012llx : \"%s\"\n", + mtd->offset, mtd->offset + mtd->size, mtd->name); + + /* MTD partitions, if any */ + mtd_show_parts(mtd, 1); +} + +static int do_nand_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd; + int dev_nb = 0; + + /* Ensure all devices (and their partitions) are probed */ + mtd_probe_devices(); + + printf("List of NAND devices:\n"); + mtd_for_each_device(mtd) { + if (mtd->type != MTD_NANDFLASH && mtd->type != MTD_MLCNANDFLASH) + continue; + + if (!mtd_is_partition(mtd)) + mtd_show_device(mtd); + + dev_nb++; + } + + if (!dev_nb) + printf("No NAND MTD device found\n"); + + return CMD_RET_SUCCESS; +} + +static struct mtd_info *nand_get_curr_dev(void) +{ + struct mtd_info *mtd, *first_dev = NULL; + int err, dev_nb = 0; + + if (curr_dev) { + mtd = get_mtd_device(curr_dev, -1); + if (!IS_ERR_OR_NULL(mtd)) { + __put_mtd_device(mtd); + return mtd; + } + + curr_dev = NULL; + } + + /* Ensure all devices (and their partitions) are probed */ + mtd_probe_devices(); + + mtd_for_each_device(mtd) { + if (mtd->type != MTD_NANDFLASH && mtd->type != MTD_MLCNANDFLASH) + continue; + + if (!mtd_is_partition(mtd)) { + if (!first_dev) + first_dev = mtd; + dev_nb++; + } + } + + if (!dev_nb) { + printf("No NAND MTD device found\n"); + return NULL; + } + + if (dev_nb > 1) { + printf("No active NAND MTD device specified\n"); + return NULL; + } + + err = __get_mtd_device(first_dev); + if (err) { + printf("Failed to get MTD device '%s': err %d\n", + first_dev->name, err); + return NULL; + } + + curr_dev = first_dev; + + printf("'%s' is now active device\n", first_dev->name); + + return curr_dev; +} + +static struct mtd_info *nand_get_part(struct mtd_info *master, + const char *name) +{ + struct mtd_info *slave; + + list_for_each_entry(slave, &master->partitions, node) { + if (!strcmp(slave->name, name)) + return slave; + } + + return NULL; +} + +static int do_nand_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd = nand_get_curr_dev(); + + if (!mtd) + return CMD_RET_FAILURE; + + mtd_show_device(mtd); + + return 0; +} + +static int do_nand_select(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd, *old; + + if (argc < 2) { + printf("MTD device name must be specified\n"); + return CMD_RET_USAGE; + } + + mtd = get_mtd_device_nm(argv[1]); + if (!mtd) { + printf("MTD device '%s' not found\n", argv[1]); + return CMD_RET_FAILURE; + } + + if (mtd_is_partition(mtd)) { + printf("Error: '%s' is a MTD partition\n", argv[1]); + __put_mtd_device(mtd); + return CMD_RET_FAILURE; + } + + if (mtd->type != MTD_NANDFLASH && mtd->type != MTD_MLCNANDFLASH) { + printf("Error: '%s' is not a NAND device\n", argv[1]); + __put_mtd_device(mtd); + return CMD_RET_FAILURE; + } + + if (mtd == curr_dev) { + __put_mtd_device(mtd); + return CMD_RET_SUCCESS; + } + + if (curr_dev) { + old = get_mtd_device(curr_dev, -1); + if (!IS_ERR_OR_NULL(old)) { + __put_mtd_device(old); + __put_mtd_device(curr_dev); + } + + curr_dev = NULL; + } + + curr_dev = mtd; + + printf("'%s' is now active device\n", curr_dev->name); + + return CMD_RET_SUCCESS; +} + +static void dump_buf(const u8 *data, size_t size, u64 addr) +{ + const u8 *p = data; + u32 i, chklen; + + while (size) { + chklen = 16; + if (chklen > size) + chklen = (u32)size; + + printf("%08llx: ", addr); + + for (i = 0; i < chklen; i++) { + if (i && (i % 4 == 0)) + printf(" "); + + printf("%02x ", p[i]); + } + + for (i = chklen; i < 16; i++) { + if (i && (i % 4 == 0)) + printf(" "); + + printf(" "); + } + printf(" "); + + for (i = 0; i < chklen; i++) { + if (p[i] < 32 || p[i] >= 0x7f) + printf("."); + else + printf("%c", p[i]); + } + printf("\n"); + + p += chklen; + size -= chklen; + addr += chklen; + } +} + +static int do_nand_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd = nand_get_curr_dev(); + struct mtd_oob_ops io_op = {}; + bool raw = false; + int ret; + u64 off; + u8 *buf; + + if (!mtd) + return CMD_RET_FAILURE; + + if (strstr(argv[0], ".raw")) + raw = true; + + if (argc < 2) { + printf("Dump offset must be specified\n"); + return CMD_RET_USAGE; + } + + off = simple_strtoull(argv[1], NULL, 0); + if (off >= mtd->size) { + printf("Offset 0x%llx is larger than flash size\n", off); + return CMD_RET_FAILURE; + } + + off &= ~(u64)mtd->writesize_mask; + + buf = malloc(mtd->writesize + mtd->oobsize); + if (!buf) { + printf("Failed to allocate buffer\n"); + return CMD_RET_FAILURE; + } + + io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB; + io_op.len = mtd->writesize; + io_op.datbuf = buf; + io_op.ooblen = mtd->oobsize; + io_op.oobbuf = buf + mtd->writesize; + + ret = mtd_read_oob(mtd, off, &io_op); + if (ret < 0 && ret != -EUCLEAN && ret != -EBADMSG) { + printf("Failed to read page at 0x%llx, err %d\n", off, ret); + free(buf); + return CMD_RET_FAILURE; + } + + printf("Dump of %spage at 0x%llx:\n", raw ? "raw " : "", off); + dump_buf(buf, mtd->writesize, off); + + printf("\n"); + printf("OOB:\n"); + dump_buf(buf + mtd->writesize, mtd->oobsize, 0); + + free(buf); + + return CMD_RET_SUCCESS; +} + +static int do_nand_bad(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd = nand_get_curr_dev(); + u64 off = 0; + + if (!mtd) + return CMD_RET_FAILURE; + + while (off < mtd->size) { + if (mtd_block_isbad(mtd, off)) + printf("\t%08llx\n", off); + + off += mtd->erasesize; + } + + return 0; +} + +static int do_nand_markbad(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd = nand_get_curr_dev(); + u64 off; + int ret; + + if (!mtd) + return CMD_RET_FAILURE; + + if (argc < 2) { + printf("Missing address within a block to be marked bad\n"); + return CMD_RET_USAGE; + } + + off = simple_strtoull(argv[1], NULL, 0); + if (off >= mtd->size) { + printf("Offset 0x%llx is larger than flash size\n", off); + return CMD_RET_FAILURE; + } + + off &= ~(u64)mtd->erasesize_mask; + + ret = mtd_block_markbad(mtd, off); + + if (!ret) + printf("Block at 0x%08llx has been marked bad\n", off); + else + printf("Failed to mark bad block at 0x%08llx\n", off); + + return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + +static int do_nand_bitflip(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd = nand_get_curr_dev(); + struct mtd_oob_ops io_op = {}; + u32 col, bit; + bool res; + u64 off; + u8 *buf; + int ret; + + if (!mtd) + return CMD_RET_FAILURE; + + if (argc < 2) { + printf("Missing address to generate bitflip\n"); + return CMD_RET_USAGE; + } + + off = simple_strtoull(argv[1], NULL, 0); + if (off >= mtd->size) { + printf("Offset 0x%llx is larger than flash size\n", off); + return CMD_RET_FAILURE; + } + + if (argc < 3) { + printf("Missing column address\n"); + return CMD_RET_USAGE; + } + + col = simple_strtoul(argv[2], NULL, 0); + if (col >= mtd->writesize + mtd->oobsize) { + printf("Column address must be less than %u\n", + mtd->writesize + mtd->oobsize); + return CMD_RET_FAILURE; + } + + if (argc < 4) { + printf("Missing bit position\n"); + return CMD_RET_USAGE; + } + + bit = simple_strtoul(argv[3], NULL, 0); + if (bit > 7) { + printf("Bit position must be less than 8\n"); + return CMD_RET_FAILURE; + } + + off &= ~(u64)mtd->writesize_mask; + + buf = malloc(mtd->writesize + mtd->oobsize); + if (!buf) { + printf("Failed to allocate buffer\n"); + return CMD_RET_FAILURE; + } + + io_op.mode = MTD_OPS_RAW; + io_op.len = mtd->writesize; + io_op.datbuf = buf; + io_op.ooblen = mtd->oobsize; + io_op.oobbuf = buf + mtd->writesize; + + ret = mtd_read_oob(mtd, off, &io_op); + if (ret < 0) { + printf("Failed to read page at 0x%llx, err %d\n", off, ret); + free(buf); + return CMD_RET_FAILURE; + } + + if (!(buf[col] & (1 << bit))) { + printf("Bit %u at byte %u is already zero\n", bit, col); + free(buf); + return CMD_RET_FAILURE; + } + + buf[col] &= ~(1 << bit); + + memset(&io_op, 0, sizeof(io_op)); + io_op.mode = MTD_OPS_RAW; + io_op.len = mtd->writesize; + io_op.datbuf = buf; + io_op.ooblen = mtd->oobsize; + io_op.oobbuf = buf + mtd->writesize; + + ret = mtd_write_oob(mtd, off, &io_op); + + if (ret < 0) { + printf("Failed to write page at 0x%llx, err %d\n", off, ret); + return CMD_RET_FAILURE; + } + + memset(&io_op, 0, sizeof(io_op)); + io_op.mode = MTD_OPS_RAW; + io_op.len = mtd->writesize; + io_op.datbuf = buf; + io_op.ooblen = mtd->oobsize; + io_op.oobbuf = buf + mtd->writesize; + + ret = mtd_read_oob(mtd, off, &io_op); + if (ret < 0) { + printf("Failed to read page at 0x%llx, err %d\n", off, ret); + free(buf); + return CMD_RET_FAILURE; + } + + res = (buf[col] & (1 << bit)) == 0; + free(buf); + + if (res) { + printf("Bit %u at byte %u has been changed to 0\n", bit, col); + return CMD_RET_SUCCESS; + } + + printf("Failed to change bit %u at byte %u to 0\n", bit, col); + return CMD_RET_FAILURE; +} + +static int do_nand_erase(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd = nand_get_curr_dev(), *part; + bool spread = false, force = false; + u64 off, size, end, limit; + struct erase_info ei; + char *ends; + int ret; + + if (!mtd) + return CMD_RET_FAILURE; + + if (strstr(argv[0], ".spread")) + spread = true; + + if (strstr(argv[0], ".force")) + force = true; + + if (spread && force) { + printf("spread and force must not be set at the same time\n"); + return CMD_RET_FAILURE; + } + + if (argc < 2) { + printf("Erase start offset/partition must be specified\n"); + return CMD_RET_USAGE; + } + + part = nand_get_part(mtd, argv[1]); + if (part) { + off = part->offset; + + if (argc < 3) + size = part->size; + else + size = simple_strtoull(argv[2], NULL, 0); + + if (size > part->size) { + printf("Erase end offset is larger than partition size\n"); + printf("Erase size reduced to 0x%llx\n", part->size); + + size = part->size; + } + + limit = off + part->size; + } else { + off = simple_strtoull(argv[1], &ends, 0); + + if (ends == argv[1] || *ends) { + printf("Partition '%s' not found\n", argv[1]); + return CMD_RET_FAILURE; + } + + if (off >= mtd->size) { + printf("Offset 0x%llx is larger than flash size\n", off); + return CMD_RET_FAILURE; + } + + if (argc < 3) { + printf("Erase size offset must be specified\n"); + return CMD_RET_USAGE; + } + + size = simple_strtoull(argv[2], NULL, 0); + + if (off + size > mtd->size) { + printf("Erase end offset is larger than flash size\n"); + + size = mtd->size - off; + printf("Erase size reduced to 0x%llx\n", size); + } + + limit = mtd->size; + } + + end = off + size; + off &= ~(u64)mtd->erasesize_mask; + end = (end + mtd->erasesize_mask) & (~(u64)mtd->erasesize_mask); + size = end - off; + + printf("Erasing from 0x%llx to 0x%llx, size 0x%llx ...\n", + off, end - 1, end - off); + + while (size && off < limit) { + if (mtd_block_isbad(mtd, off)) { + printf("Bad block at 0x%llx", off); + + if (spread) { + printf(" ... skipped\n"); + off += mtd->erasesize; + continue; + } + + if (!force) { + printf(" ... aborted\n"); + return CMD_RET_FAILURE; + } + + printf(" ... will be force erased\n"); + } + + memset(&ei, 0, sizeof(ei)); + + ei.mtd = mtd; + ei.addr = off; + ei.len = mtd->erasesize; + ei.scrub = force; + + ret = mtd_erase(mtd, &ei); + if (ret) { + printf("Erase failed at 0x%llx\n", off); + return CMD_RET_FAILURE; + } + + off += mtd->erasesize; + size -= mtd->erasesize; + } + + printf("Succeeded\n"); + + return CMD_RET_SUCCESS; +} + +static bool is_empty_page(const u8 *buf, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) { + if (buf[i] != 0xff) + return false; + } + + return true; +} + +static int do_nand_io_normal(int argc, char *const argv[]) +{ + struct mtd_info *mtd = nand_get_curr_dev(), *part; + bool spread = false, force = false, raw = false, writeff = false; + bool read = false, checkbad = true; + struct mtd_oob_ops io_op = {}; + size_t size, padding, chksz; + uintptr_t addr; + u64 off, offp; + char *ends; + u8 *buf; + int ret; + + if (!mtd) + return CMD_RET_FAILURE; + + if (!strncmp(argv[0], "read", 4)) + read = true; + + if (strstr(argv[0], ".spread")) + spread = true; + + if (strstr(argv[0], ".force")) + force = true; + + if (strstr(argv[0], ".raw")) + raw = true; + + if (strstr(argv[0], ".ff")) + writeff = true; + + if (spread && force) { + printf("spread and force must not be set at the same time\n"); + return CMD_RET_FAILURE; + } + + if (argc < 2) { + printf("Data address must be specified\n"); + return CMD_RET_USAGE; + } + + addr = simple_strtoul(argv[1], NULL, 0); + + if (argc < 3) { + printf("Flash address/partition must be specified\n"); + return CMD_RET_USAGE; + } + + part = nand_get_part(mtd, argv[2]); + if (part) { + if (argc < 4) { + off = 0; + } else { + off = simple_strtoull(argv[3], NULL, 0); + if (off + part->offset >= part->size) { + printf("Offset is larger than partition size\n"); + return CMD_RET_FAILURE; + } + } + + if (argc < 5) { + size = part->size - off; + } else { + size = simple_strtoul(argv[4], NULL, 0); + if (off + size > part->size) { + printf("Data size is too large\n"); + return CMD_RET_FAILURE; + } + } + + off += part->offset; + } else { + off = simple_strtoull(argv[2], &ends, 0); + + if (ends == argv[1] || *ends) { + printf("Partition '%s' not found\n", argv[2]); + return CMD_RET_FAILURE; + } + + if (off >= mtd->size) { + printf("Offset 0x%llx is larger than flash size\n", off); + return CMD_RET_FAILURE; + } + + if (argc < 4) { + printf("Data size must be specified\n"); + return CMD_RET_USAGE; + } + + size = simple_strtoul(argv[3], NULL, 0); + if (off + size > mtd->size) { + printf("Data size is too large\n"); + return CMD_RET_FAILURE; + } + } + + buf = malloc(mtd->writesize); + if (!buf) { + printf("Failed to allocate buffer\n"); + return CMD_RET_FAILURE; + } + + printf("%s from 0x%llx to 0x%llx, size 0x%zx ...\n", + read ? "Reading" : "Writing", off, off + size - 1, size); + + while (size && off < mtd->size) { + if (checkbad || !(off & mtd->erasesize_mask)) { + offp = off & ~(u64)mtd->erasesize_mask; + + if (mtd_block_isbad(mtd, offp)) { + printf("Bad block at 0x%llx", offp); + + if (spread) { + printf(" ... skipped\n"); + off += mtd->erasesize; + checkbad = true; + continue; + } + + if (!force) { + printf(" ... aborted\n"); + goto err_out; + } + + printf(" ... continue\n"); + } + + checkbad = false; + } + + padding = off & mtd->writesize_mask; + chksz = mtd->writesize - padding; + chksz = min_t(size_t, chksz, size); + + offp = off & ~(u64)mtd->writesize_mask; + + memset(&io_op, 0, sizeof(io_op)); + io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB; + io_op.len = mtd->writesize; + + if (chksz < mtd->writesize) + io_op.datbuf = buf; + else + io_op.datbuf = (void *)addr; + + if (read) { + ret = mtd_read_oob(mtd, offp, &io_op); + if (ret && ret != -EUCLEAN && ret != -EBADMSG) + goto io_err; + + if (chksz < mtd->writesize) + memcpy((void *)addr, buf + padding, chksz); + } else { + if (chksz < mtd->writesize) { + memset(buf, 0xff, mtd->writesize); + memcpy(buf + padding, (void *)addr, chksz); + } + + if (is_empty_page(io_op.datbuf, io_op.len) && !writeff) + ret = 0; + else + ret = mtd_write_oob(mtd, offp, &io_op); + + if (ret) + goto io_err; + } + + size -= chksz; + addr += chksz; + off += chksz; + } + + if (!size) { + printf("Succeeded\n"); + ret = CMD_RET_SUCCESS; + goto out; + } + + printf("0x%zx byte%s remained for %s\n", size, size > 1 ? "s" : "", + read ? "read" : "write"); + goto err_out; + +io_err: + printf("%s error %d at 0x%llx\n", read ? "Read" : "Write", ret, offp); + +err_out: + ret = CMD_RET_FAILURE; + +out: + free(buf); + return ret; +} + +static int do_nand_io_page(int argc, char *const argv[]) +{ + struct mtd_info *mtd = nand_get_curr_dev(), *part; + bool spread = false, force = false, raw = false, autooob = false; + bool read = false, checkbad = true, writeff = false; + struct mtd_oob_ops io_op = {}; + uintptr_t addr; + u64 off, offp; + char *ends; + u32 count; + int ret; + + if (!mtd) + return CMD_RET_FAILURE; + + if (!strncmp(argv[0], "read", 4)) + read = true; + + if (strstr(argv[0], ".spread")) + spread = true; + + if (strstr(argv[0], ".force")) + force = true; + + if (strstr(argv[0], ".raw")) + raw = true; + + if (strstr(argv[0], ".auto")) + autooob = true; + + if (spread && force) { + printf("spread and force must not be set at the same time\n"); + return CMD_RET_FAILURE; + } + + if (raw && autooob) { + printf("raw and auto must not be set at the same time\n"); + return CMD_RET_FAILURE; + } + + if (argc < 2) { + printf("Data address must be specified\n"); + return CMD_RET_USAGE; + } + + addr = simple_strtoul(argv[1], NULL, 0); + + if (argc < 3) { + printf("Flash address/partition must be specified\n"); + return CMD_RET_USAGE; + } + + part = nand_get_part(mtd, argv[2]); + if (part) { + if (argc < 4) { + printf("Partition offset / page count must be specified\n"); + return CMD_RET_USAGE; + } + + if (argc < 5) { + off = 0; + + count = simple_strtoul(argv[3], NULL, 0); + if (part->offset + count * mtd->writesize > part->size) { + printf("Page count exceeds partition size\n"); + return CMD_RET_FAILURE; + } + } else { + off = simple_strtoull(argv[3], NULL, 0); + if (off >= part->size) { + printf("Offset 0x%llx is larger than partition size\n", off); + return CMD_RET_FAILURE; + } + + off &= ~(u64)mtd->writesize_mask; + + count = simple_strtoul(argv[4], NULL, 0); + if (part->offset + off + count * mtd->writesize > part->size) { + printf("Page count exceeds partition size\n"); + return CMD_RET_FAILURE; + } + } + + off += part->offset; + } else { + off = simple_strtoull(argv[2], &ends, 0); + + if (ends == argv[1] || *ends) { + printf("Partition '%s' not found\n", argv[2]); + return CMD_RET_FAILURE; + } + + if (off >= mtd->size) { + printf("Offset 0x%llx is larger than flash size\n", off); + return CMD_RET_FAILURE; + } + + off &= ~(u64)mtd->writesize_mask; + + if (argc < 4) { + printf("Page count must be specified\n"); + return CMD_RET_USAGE; + } + + count = simple_strtoul(argv[3], NULL, 0); + if (off + count * mtd->writesize > mtd->size) { + printf("Page count exceeds flash size\n"); + return CMD_RET_FAILURE; + } + } + + printf("%s from 0x%llx to 0x%llx (+%u), count %u ...\n", + read ? "Reading" : "Writing", off, + off + count * mtd->writesize - 1, mtd->oobsize, count); + + while (count && off < mtd->size) { + if (checkbad || !(off & mtd->erasesize_mask)) { + offp = off & ~(u64)mtd->erasesize_mask; + + if (mtd_block_isbad(mtd, offp)) { + printf("Bad block at 0x%llx", offp); + + if (spread) { + printf(" ... skipped\n"); + off += mtd->erasesize; + checkbad = true; + continue; + } + + if (!force) { + printf(" ... aborted\n"); + return CMD_RET_FAILURE; + } + + printf(" ... continue\n"); + } + + checkbad = false; + } + + memset(&io_op, 0, sizeof(io_op)); + + if (raw) + io_op.mode = MTD_OPS_RAW; + else if (autooob) + io_op.mode = MTD_OPS_AUTO_OOB; + else + io_op.mode = MTD_OPS_PLACE_OOB; + + io_op.len = mtd->writesize; + io_op.ooblen = mtd->oobsize; + io_op.datbuf = (void *)addr; + io_op.oobbuf = io_op.datbuf + mtd->writesize; + + if (read) { + ret = mtd_read_oob(mtd, off, &io_op); + if (ret && ret != -EUCLEAN && ret != -EBADMSG) + goto io_err; + } else { + if (is_empty_page((void *)addr, mtd->writesize + mtd->oobsize) && !writeff) + ret = 0; + else + ret = mtd_write_oob(mtd, off, &io_op); + + if (ret) + goto io_err; + } + + count--; + addr += mtd->writesize + mtd->oobsize; + off += mtd->writesize; + } + + if (!count) { + printf("Succeeded\n"); + return CMD_RET_SUCCESS; + } + + printf("%u page%s remained for %s\n", count, count > 1 ? "s" : "", + read ? "read" : "write"); + return CMD_RET_FAILURE; + +io_err: + printf("%s error %d at 0x%llx\n", read ? "Read" : "Write", ret, off); + return CMD_RET_FAILURE; +} + +static int do_nand_io(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (strstr(argv[0], ".oob")) + return do_nand_io_page(argc, argv); + + return do_nand_io_normal(argc, argv); +} + +#ifdef CONFIG_SYS_LONGHELP +static char nand_help_text[] = + "- NAND flash R/W and debugging utility\n" + "nand list\n" + "nand info - Show active NAND devices\n" + "nand select - Select active NAND devices\n" + "nand dump[.raw] \n" + "nand bad\n" + "nand markbad \n" + "nand bitflip \n" + "nand erase[.spread|.force] [ | []]\n" + "nand read[.spread|.force][.raw] \n" + " [ []]\n" + "nand write[.spread|.force][.raw][.ff] \n" + " [ []]\n" + "nand read.oob[.spread|.force][.raw|.auto] \n" + " [] \n" + "nand write.oob[.spread|.force][.raw|.auto][.ff] \n" + " [] \n"; +#endif + +U_BOOT_CMD_WITH_SUBCMDS(nand, "NAND utility", + nand_help_text, + U_BOOT_SUBCMD_MKENT(list, 1, 0, do_nand_list), + U_BOOT_SUBCMD_MKENT(info, 1, 0, do_nand_info), + U_BOOT_SUBCMD_MKENT(select, 2, 0, do_nand_select), + U_BOOT_SUBCMD_MKENT(dump, 2, 0, do_nand_dump), + U_BOOT_SUBCMD_MKENT(bad, 1, 0, do_nand_bad), + U_BOOT_SUBCMD_MKENT(markbad, 2, 0, do_nand_markbad), + U_BOOT_SUBCMD_MKENT(bitflip, 4, 0, do_nand_bitflip), + U_BOOT_SUBCMD_MKENT(erase, 3, 0, do_nand_erase), + U_BOOT_SUBCMD_MKENT(read, 5, 0, do_nand_io), + U_BOOT_SUBCMD_MKENT(write, 5, 0, do_nand_io) +);