summaryrefslogtreecommitdiff
path: root/cmd/mtd.c
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/mtd.c')
-rw-r--r--cmd/mtd.c480
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));