diff options
Diffstat (limited to 'cmd/mtd.c')
-rw-r--r-- | cmd/mtd.c | 480 |
1 files changed, 469 insertions, 11 deletions
diff --git a/cmd/mtd.c b/cmd/mtd.c index 2520b89eed2..acd886da6da 100644 --- a/cmd/mtd.c +++ b/cmd/mtd.c @@ -20,6 +20,7 @@ #include <time.h> #include <dm/devres.h> #include <linux/err.h> +#include <memalign.h> #include <linux/ctype.h> @@ -468,7 +469,7 @@ static int do_mtd_io(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { bool dump, read, raw, woob, benchmark, write_empty_pages, has_pages = false; - u64 start_off, off, len, remaining, default_len; + u64 start_off, off, len, remaining, default_len, speed; unsigned long bench_start, bench_end; struct mtd_oob_ops io_op = {}; uint user_addr = 0, npages; @@ -594,9 +595,10 @@ static int do_mtd_io(struct cmd_tbl *cmdtp, int flag, int argc, if (benchmark && bench_start) { bench_end = timer_get_us(); + speed = (len * 1000000) / (bench_end - bench_start); printf("%s speed: %lukiB/s\n", read ? "Read" : "Write", - ((io_op.len * 1000000) / (bench_end - bench_start)) / 1024); + (unsigned long)(speed / 1024)); } led_activity_off(); @@ -711,6 +713,439 @@ out_put_mtd: return ret; } +#ifdef CONFIG_CMD_MTD_MARKBAD +static int do_mtd_markbad(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd; + loff_t off; + int ret = 0; + + if (argc < 3) + return CMD_RET_USAGE; + + mtd = get_mtd_by_name(argv[1]); + if (IS_ERR_OR_NULL(mtd)) + return CMD_RET_FAILURE; + + if (!mtd_can_have_bb(mtd)) { + printf("Only NAND-based devices can have bad blocks\n"); + goto out_put_mtd; + } + + argc -= 2; + argv += 2; + while (argc > 0) { + off = hextoul(argv[0], NULL); + if (!mtd_is_aligned_with_block_size(mtd, off)) { + printf("Offset not aligned with a block (0x%x)\n", + mtd->erasesize); + ret = CMD_RET_FAILURE; + goto out_put_mtd; + } + + ret = mtd_block_markbad(mtd, off); + if (ret) { + printf("block 0x%08llx NOT marked as bad! ERROR %d\n", + off, ret); + ret = CMD_RET_FAILURE; + } else { + printf("block 0x%08llx successfully marked as bad\n", + off); + } + --argc; + ++argv; + } + +out_put_mtd: + put_mtd_device(mtd); + + return ret; +} +#endif + +#ifdef CONFIG_CMD_MTD_NAND_WRITE_TEST +/** + * nand_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 nand_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_write_test: + * + * Writes a block of NAND flash with different patterns. + * This is useful to determine if a block that caused a write error is still + * good or should be marked as bad. + * + * @param mtd nand mtd instance + * @param offset offset in flash + * Return: 0 if the block is still good + */ +static int nand_write_test(struct mtd_info *mtd, loff_t offset) +{ + u_char patterns[] = {0xa5, 0x5a, 0x00}; + struct erase_info instr = { + .mtd = mtd, + .addr = offset, + .len = mtd->erasesize, + }; + size_t retlen; + int err, ret = -1, i, patt_count; + u_char *buf; + + if ((offset & (mtd->erasesize - 1)) != 0) { + puts("Attempt to torture a block at a non block-aligned offset\n"); + return -EINVAL; + } + + if (offset + mtd->erasesize > mtd->size) { + puts("Attempt to torture a block outside the flash area\n"); + return -EINVAL; + } + + patt_count = ARRAY_SIZE(patterns); + + buf = malloc_cache_aligned(mtd->erasesize); + if (buf == NULL) { + puts("Out of memory for erase block buffer\n"); + return -ENOMEM; + } + + for (i = 0; i < patt_count; i++) { + err = mtd_erase(mtd, &instr); + if (err) { + printf("%s: erase() failed for block at 0x%llx: %d\n", + mtd->name, instr.addr, err); + goto out; + } + + /* Make sure the block contains only 0xff bytes */ + err = mtd_read(mtd, offset, mtd->erasesize, &retlen, buf); + if ((err && err != -EUCLEAN) || retlen != mtd->erasesize) { + printf("%s: read() failed for block at 0x%llx: %d\n", + mtd->name, instr.addr, err); + goto out; + } + + err = nand_check_pattern(buf, 0xff, mtd->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], mtd->erasesize); + err = mtd_write(mtd, offset, mtd->erasesize, &retlen, buf); + if (err || retlen != mtd->erasesize) { + printf("%s: write() failed for block at 0x%llx: %d\n", + mtd->name, instr.addr, err); + goto out; + } + + err = mtd_read(mtd, offset, mtd->erasesize, &retlen, buf); + if ((err && err != -EUCLEAN) || retlen != mtd->erasesize) { + printf("%s: read() failed for block at 0x%llx: %d\n", + mtd->name, instr.addr, err); + goto out; + } + + err = nand_check_pattern(buf, patterns[i], mtd->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; +} + +static int do_nand_write_test(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd; + loff_t off, len; + int ret = 0; + unsigned int failed = 0, passed = 0; + + if (argc < 2) + return CMD_RET_USAGE; + + mtd = get_mtd_by_name(argv[1]); + if (IS_ERR_OR_NULL(mtd)) + return CMD_RET_FAILURE; + + if (!mtd_can_have_bb(mtd)) { + printf("Only NAND-based devices can be tortured\n"); + goto out_put_mtd; + } + + argc -= 2; + argv += 2; + + off = argc > 0 ? hextoul(argv[0], NULL) : 0; + len = argc > 1 ? hextoul(argv[1], NULL) : mtd->size; + + if (!mtd_is_aligned_with_block_size(mtd, off)) { + printf("Offset not aligned with a block (0x%x)\n", + mtd->erasesize); + ret = CMD_RET_FAILURE; + goto out_put_mtd; + } + + if (!mtd_is_aligned_with_block_size(mtd, len)) { + printf("Size not a multiple of a block (0x%x)\n", + mtd->erasesize); + ret = CMD_RET_FAILURE; + goto out_put_mtd; + } + + printf("\nNAND write test: device '%s' offset 0x%llx size 0x%llx (block size 0x%x)\n", + mtd->name, off, len, mtd->erasesize); + while (len > 0) { + printf("\r block at %llx ", off); + if (mtd_block_isbad(mtd, off)) { + printf("marked bad, skipping\n"); + } else { + ret = nand_write_test(mtd, off); + if (ret) { + failed++; + printf("failed\n"); + } else { + passed++; + } + } + off += mtd->erasesize; + len -= mtd->erasesize; + } + printf("\n Passed: %u, failed: %u\n", passed, failed); + if (failed != 0) + ret = CMD_RET_FAILURE; + +out_put_mtd: + put_mtd_device(mtd); + + return ret; +} +#endif + +#ifdef CONFIG_CMD_MTD_NAND_READ_TEST +enum nand_read_status { + NAND_READ_STATUS_UNKNOWN = 0, + NAND_READ_STATUS_NONECC_READ_FAIL, + NAND_READ_STATUS_ECC_READ_FAIL, + NAND_READ_STATUS_BAD_BLOCK, + NAND_READ_STATUS_BITFLIP_ABOVE_MAX, + NAND_READ_STATUS_BITFLIP_MISMATCH, + NAND_READ_STATUS_BITFLIP_MAX, + NAND_READ_STATUS_UNRELIABLE, + NAND_READ_STATUS_OK, +}; + +/* test_buf MUST be not smaller than 2 * blocksize bytes */ +static enum nand_read_status nand_read_block_check(struct mtd_info *mtd, + loff_t off, + size_t blocksize, + u_char *test_buf) +{ + struct mtd_oob_ops ops = { + .mode = MTD_OPS_RAW, + .len = blocksize, + .datbuf = test_buf, + }; + int i, d, ret, len, pos, cnt, max; + + if (blocksize % mtd->writesize != 0) { + printf("\r block at 0x%llx: bad block size\n", off); + return NAND_READ_STATUS_UNKNOWN; + } + + ret = mtd->_read_oob(mtd, off, &ops); + if (ret < 0) { + printf("\r block at 0x%llx: non-ecc reading error %d\n", + off, ret); + return NAND_READ_STATUS_NONECC_READ_FAIL; + } + + ops.mode = MTD_OPS_PLACE_OOB; + ops.datbuf = test_buf + blocksize; + + ret = mtd->_read_oob(mtd, off, &ops); + if (ret == -EBADMSG) { + printf("\r block at 0x%llx: bad block\n", off); + return NAND_READ_STATUS_BAD_BLOCK; + } + + if (ret < 0) { + printf("\r block at 0x%llx: ecc reading error %d\n", off, ret); + return NAND_READ_STATUS_ECC_READ_FAIL; + } + + if (mtd->ecc_strength == 0) + return NAND_READ_STATUS_OK; + + if (ret > mtd->ecc_strength) { + printf("\r block at 0x%llx: returned bit-flips value %d " + "is above maximum value %d\n", + off, ret, mtd->ecc_strength); + return NAND_READ_STATUS_BITFLIP_ABOVE_MAX; + } + + max = 0; + pos = 0; + len = blocksize; + while (len > 0) { + cnt = 0; + for (i = 0; i < mtd->ecc_step_size; i++) { + d = test_buf[pos + i] ^ test_buf[blocksize + pos + i]; + if (d == 0) + continue; + + while (d > 0) { + d &= (d - 1); + cnt++; + } + } + if (cnt > max) + max = cnt; + + len -= mtd->ecc_step_size; + pos += mtd->ecc_step_size; + } + + if (max > ret) { + printf("\r block at 0x%llx: bitflip mismatch, " + "read %d but actual %d\n", off, ret, max); + return NAND_READ_STATUS_BITFLIP_MISMATCH; + } + + if (ret == mtd->ecc_strength) { + printf("\r block at 0x%llx: max bitflip reached, " + "block is unreliable\n", off); + return NAND_READ_STATUS_BITFLIP_MAX; + } + + if (ret >= mtd->bitflip_threshold) { + printf("\r block at 0x%llx: bitflip threshold reached, " + "block is unreliable\n", off); + return NAND_READ_STATUS_UNRELIABLE; + } + + return NAND_READ_STATUS_OK; +} + +static int do_mtd_nand_read_test(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd; + u64 off, blocks; + int stat[NAND_READ_STATUS_OK + 1]; + enum nand_read_status ret; + u_char *buf; + + if (argc < 2) + return CMD_RET_USAGE; + + mtd = get_mtd_by_name(argv[1]); + if (IS_ERR_OR_NULL(mtd)) + return CMD_RET_FAILURE; + + if (!mtd_can_have_bb(mtd)) { + printf("Only NAND-based devices can be checked\n"); + goto test_error; + } + + if (!mtd->_read_oob) { + printf("RAW reading is not supported\n"); + goto test_error; + } + + buf = malloc_cache_aligned(2 * mtd->erasesize); + if (!buf) { + printf("Can't allocate memory for the test\n"); + goto test_error; + } + + blocks = mtd->size; + do_div(blocks, mtd->erasesize); + + printf("ECC strength: %d\n", mtd->ecc_strength); + printf("ECC theshold: %d\n", mtd->bitflip_threshold); + printf("ECC step size: %d\n", mtd->ecc_step_size); + printf("Erase block size: 0x%x\n", mtd->erasesize); + printf("Total blocks: %lld\n", blocks); + + printf("\nworking...\n"); + memset(stat, 0, sizeof(stat)); + for (off = 0; off < mtd->size; off += mtd->erasesize) { + ret = nand_read_block_check(mtd, off, mtd->erasesize, buf); + stat[ret]++; + + switch (ret) { + case NAND_READ_STATUS_BAD_BLOCK: + case NAND_READ_STATUS_BITFLIP_MAX: + case NAND_READ_STATUS_UNRELIABLE: + if (!mtd_block_isbad(mtd, off)) + printf("\r block at 0x%llx: should be marked " + "as BAD\n", off); + break; + + case NAND_READ_STATUS_OK: + if (mtd_block_isbad(mtd, off)) + printf("\r block at 0x%llx: marked as BAD, but " + "probably is GOOD\n", off); + break; + + default: + break; + } + } + + free(buf); + + put_mtd_device(mtd); + printf("\n"); + printf("results:\n"); + printf(" Good blocks: %d\n", stat[NAND_READ_STATUS_OK]); + printf(" Physically bad blocks: %d\n", stat[NAND_READ_STATUS_BAD_BLOCK]); + printf(" Unreliable blocks: %d\n", stat[NAND_READ_STATUS_BITFLIP_MAX] + + stat[NAND_READ_STATUS_UNRELIABLE]); + printf(" Non checked blocks: %d\n", stat[NAND_READ_STATUS_UNKNOWN]); + printf(" Failed to check blocks: %d\n", stat[NAND_READ_STATUS_NONECC_READ_FAIL] + + stat[NAND_READ_STATUS_ECC_READ_FAIL]); + printf(" Suspictious blocks: %d\n", stat[NAND_READ_STATUS_BITFLIP_ABOVE_MAX] + + stat[NAND_READ_STATUS_BITFLIP_MISMATCH]); + return CMD_RET_SUCCESS; + +test_error: + put_mtd_device(mtd); + return CMD_RET_FAILURE; +} +#endif + static int do_mtd_bad(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { @@ -781,18 +1216,27 @@ static int mtd_name_complete(int argc, char *const argv[], char last_char, U_BOOT_LONGHELP(mtd, "- generic operations on memory technology devices\n\n" "mtd list\n" - "mtd read[.raw][.oob] <name> <addr> [<off> [<size>]]\n" - "mtd dump[.raw][.oob] <name> [<off> [<size>]]\n" - "mtd write[.raw][.oob][.dontskipff] <name> <addr> [<off> [<size>]]\n" - "mtd erase[.dontskipbad] <name> [<off> [<size>]]\n" + "mtd read[.raw][.oob][.benchmark] <name> <addr> [<off> [<size>]]\n" + "mtd dump[.raw][.oob] <name> [<off> [<size>]]\n" + "mtd write[.raw][.oob][.dontskipff][.benchmark] <name> <addr> [<off> [<size>]]\n" + "mtd erase[.dontskipbad] <name> [<off> [<size>]]\n" "\n" "Specific functions:\n" - "mtd bad <name>\n" + "mtd bad <name>\n" #if CONFIG_IS_ENABLED(CMD_MTD_OTP) - "mtd otpread <name> [u|f] <off> <size>\n" - "mtd otpwrite <name> <off> <hex string>\n" - "mtd otplock <name> <off> <size>\n" - "mtd otpinfo <name> [u|f]\n" + "mtd otpread <name> [u|f] <off> <size>\n" + "mtd otpwrite <name> <off> <hex string>\n" + "mtd otplock <name> <off> <size>\n" + "mtd otpinfo <name> [u|f]\n" +#endif +#if CONFIG_IS_ENABLED(CMD_MTD_MARKBAD) + "mtd markbad <name> <off> [<off> ...]\n" +#endif +#if CONFIG_IS_ENABLED(CMD_MTD_NAND_WRITE_TEST) + "mtd nand_write_test <name> [<off> [<size>]]\n" +#endif +#if CONFIG_IS_ENABLED(CMD_MTD_NAND_READ_TEST) + "mtd nand_read_test <name>\n" #endif "\n" "With:\n" @@ -827,5 +1271,19 @@ U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils", mtd_help_text, mtd_name_complete), U_BOOT_SUBCMD_MKENT_COMPLETE(erase, 4, 0, do_mtd_erase, mtd_name_complete), +#if CONFIG_IS_ENABLED(CMD_MTD_MARKBAD) + U_BOOT_SUBCMD_MKENT_COMPLETE(markbad, 20, 0, do_mtd_markbad, + mtd_name_complete), +#endif +#if CONFIG_IS_ENABLED(CMD_MTD_NAND_WRITE_TEST) + U_BOOT_SUBCMD_MKENT_COMPLETE(nand_write_test, 4, 0, + do_nand_write_test, + mtd_name_complete), +#endif +#if CONFIG_IS_ENABLED(CMD_MTD_NAND_READ_TEST) + U_BOOT_SUBCMD_MKENT_COMPLETE(nand_read_test, 2, 0, + do_mtd_nand_read_test, + mtd_name_complete), +#endif U_BOOT_SUBCMD_MKENT_COMPLETE(bad, 2, 1, do_mtd_bad, mtd_name_complete)); |