Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevets...@iopsys.eu>
---
cmd/Kconfig | 6 ++
cmd/mtd.c | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 202 insertions(+)
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 53ebafcb610..698e0e697f2 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1439,6 +1439,12 @@ config CMD_MTD_MARKBAD
help
MTD markbad command support.
+config CMD_MTD_TORTURE
+ bool "mtd torture"
+ depends on CMD_MTD
+ help
+ MTD torture command support.
+
config CMD_MUX
bool "mux"
depends on MULTIPLEXER
diff --git a/cmd/mtd.c b/cmd/mtd.c
index 45084d39d3a..69ddfe1d9c6 100644
--- a/cmd/mtd.c
+++ b/cmd/mtd.c
@@ -18,6 +18,7 @@
#include <mtd.h>
#include <dm/devres.h>
#include <linux/err.h>
+#include <memalign.h>
#include <linux/ctype.h>
@@ -742,6 +743,194 @@ out_put_mtd:
}
#endif
+#ifdef CONFIG_CMD_MTD_TORTURE
+/**
+ * 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_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 mtd nand mtd instance
+ * @param offset offset in flash
+ * Return: 0 if the block is still good
+ */
+static int nand_torture(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_mtd_torture(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 torture: 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_torture(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
+
static int do_mtd_bad(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
@@ -827,6 +1016,9 @@ U_BOOT_LONGHELP(mtd,
#endif
#ifdef CONFIG_CMD_MTD_MARKBAD
"mtd markbad <name> <off> [<off>
...]\n"
+#endif
+#ifdef CONFIG_CMD_MTD_TORTURE
+ "mtd torture <name> [<off> [<size>]]\n"
#endif
"\n"
"With:\n"
@@ -864,6 +1056,10 @@ U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils", mtd_help_text,
#ifdef CONFIG_CMD_MTD_MARKBAD
U_BOOT_SUBCMD_MKENT_COMPLETE(markbad, 20, 0, do_mtd_markbad,
mtd_name_complete),
+#endif
+#ifdef CONFIG_CMD_MTD_TORTURE
+ U_BOOT_SUBCMD_MKENT_COMPLETE(torture, 4, 0, do_mtd_torture,
+ mtd_name_complete),
#endif
U_BOOT_SUBCMD_MKENT_COMPLETE(bad, 2, 1, do_mtd_bad,
mtd_name_complete));