commit f3deffa6abf6ab563eaa1842fa3836351d867f30
Author: Sascha Hauer <s.hauer@pengutronix.de>
Date: Thu Jul 24 10:32:52 2014 +0200
Commands: Add a nand-read-test command
NAND flashes suffer from read disturbance. This means that if a page
is read often enough there will be bitflips. This test tool continuously
reads a bunch of pages and shows a statistic over the number of bitfips
occured after which read iteration. This can be used to test a NAND flash
but also to test a NAND driver. The page reads are optionally compared
to the initial read of the page. If there is a difference, but the driver
has not reported an error the driver is buggy.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
@@ -1800,6 +1800,11 @@ config CMD_NANDTEST
-o OFFS start offset on flash
-l LEN length of flash to test
+config CMD_NAND_READ_TEST
+ tristate
+ depends on NAND
+ prompt "nand read test"
+
config CMD_POWEROFF
tristate
depends on HAS_POWEROFF
@@ -48,6 +48,7 @@ obj-$(CONFIG_CMD_SAVEENV) += saveenv.o
obj-$(CONFIG_CMD_LOADENV) += loadenv.o
obj-$(CONFIG_CMD_NAND) += nand.o
obj-$(CONFIG_CMD_NANDTEST) += nandtest.o
+obj-$(CONFIG_CMD_NAND_READ_TEST) += nand-read-test.o
obj-$(CONFIG_CMD_MEMTEST) += memtest.o
obj-$(CONFIG_CMD_TRUE) += true.o
obj-$(CONFIG_CMD_FALSE) += false.o
new file mode 100644
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2014 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
+ *
+ * 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.
+ *
+ */
+#include <common.h>
+#include <command.h>
+#include <fs.h>
+#include <linux/stat.h>
+#include <errno.h>
+#include <malloc.h>
+#include <getopt.h>
+#include <xfuncs.h>
+#include <init.h>
+#include <ioctl.h>
+#include <nand.h>
+#include <linux/mtd/mtd-abi.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <string.h>
+#include <clock.h>
+#include <linux/log2.h>
+#include <linux/mtd/mtd.h>
+
+#define MAXFLIPS 8
+
+/*
+ * NAND flashes suffer from read disturbance. This means that if a page
+ * is read often enough there will be bitflips. This test tool continuously
+ * reads a bunch of pages and shows a statistic over the number of bitfips
+ * occured after which read iteration. This can be used to test a NAND flash
+ * but also to test a NAND driver. The page reads are optionally compared
+ * to the initial read of the page. If there is a difference, but the driver
+ * has not reported an error the driver is buggy.
+ */
+struct status {
+ int flips[MAXFLIPS];
+ int err_it;
+ int err;
+ void *buf;
+ void *failbuf;
+};
+
+static void print_status_one(struct status *s, int page)
+{
+ int i;
+
+ printf("%-3d: ", page);
+
+ for (i = 0; i < MAXFLIPS; i++) {
+ if (s->flips[i] == -1)
+ printf("- ");
+ else
+ printf("%-7d ", s->flips[i]);
+ }
+
+ printf("err: %d @ %d\n", s->err, s->err_it);
+}
+
+int print_status(struct status *status, int num_pages, struct mtd_info *mtd, int iteration)
+{
+ int i, ret;
+
+ printf("\nstatistic after iteration %d:\n\n", iteration);
+
+ printf(" Page no\n");
+ printf(" / 1st bitflip after iteration #\n");
+ printf("| / 2nd bitflip after iteration #\n");
+ printf("| | / 3rd 4th 5th 6th 7th 8th bitflip after iteration #\n");
+ printf("| | | / / / / / / error <errcode> @ iteration #\n");
+ printf("| | | | | | | | | /\n");
+ printf("| | | | | | | | | |\n");
+
+ for (i = 0; i < num_pages; i++) {
+ if (ctrlc())
+ return -EINTR;
+ print_status_one(&status[i], i);
+ }
+
+ for (i = 0; i < num_pages; i++) {
+ struct status *s = &status[i];
+
+ if (ctrlc())
+ return -EINTR;
+
+ if (s->failbuf) {
+ printf("Undetected read failure on page %d:\n", i);
+ printf("Should be:\n");
+ ret = memory_display(s->buf, i * mtd->writesize, mtd->writesize, 4, 0);
+ if (ret)
+ return ret;
+ printf("read instead:\n");
+ ret = memory_display(s->failbuf, i * mtd->writesize, mtd->writesize, 4, 0);
+ if (ret)
+ return ret;
+ }
+ }
+
+ mdelay(200);
+ if (ctrlc())
+ return -EINTR;
+
+ return 0;
+}
+
+static void nand_read_test(struct mtd_info *mtd, int num_pages, int compare)
+{
+ void *_buf;
+ uint64_t start;
+ int i = 0, j, n;
+ loff_t addr;
+ int ret;
+ struct status *status;
+ size_t read;
+ void *pagebuf;
+
+ status = xzalloc(sizeof(struct status) * num_pages);
+ pagebuf = xzalloc(mtd->writesize);
+
+ if (compare) {
+ for (n = 0; n < num_pages; n++) {
+ struct status *s = &status[n];
+
+ addr = n * mtd->writesize;
+ s->buf = malloc(mtd->writesize);
+ if (!s->buf)
+ goto out;
+
+ ret = mtd->read(mtd, addr, mtd->writesize, &read, s->buf);
+ if (ret < 0) {
+ printf("Error while reading compare buffer: %s\n",
+ strerror(-ret));
+ goto out;
+ }
+ }
+ }
+
+ for (i = 0; i < num_pages; i++) {
+ struct status *s = &status[i];
+ for (j = 0; j < MAXFLIPS; j++)
+ s->flips[j] = -1;
+ }
+
+ start = get_time_ns();
+
+ i = 0;
+ while (1) {
+ for (n = 0; n < num_pages; n++) {
+ struct status *s = &status[n];
+ addr = n * mtd->writesize;
+
+ ret = mtd->read(mtd, addr, mtd->writesize, &read, pagebuf);
+ if (ret < 0) {
+ if (!s->err) {
+ s->err_it = i;
+ s->err = ret;
+ }
+ }
+
+ if (ret > 0 && ret <= MAXFLIPS) {
+ if (s->flips[ret - 1] == -1)
+ s->flips[ret - 1] = i;
+ }
+
+ if (compare && ret >= 0 && !s->failbuf && memcmp(s->buf, pagebuf, mtd->writesize))
+ s->failbuf = memdup(pagebuf, mtd->writesize);
+
+ _buf += mtd->writesize;
+ }
+
+ if (ctrlc()) {
+ ret = print_status(status, num_pages, mtd, i);
+ if (ret)
+ goto out;
+ }
+
+ if (is_timeout(start, SECOND * 60)) {
+ printf("iteration: %d\n", i);
+ start = get_time_ns();
+ }
+
+ i++;
+ }
+out:
+ free(pagebuf);
+ for (n = 0; n < num_pages; n++) {
+ struct status *s = &status[n];
+ free(s->buf);
+ free(s->failbuf);
+ }
+
+ free(status);
+
+ return;
+}
+
+static int do_nand_read_test(int argc, char *argv[])
+{
+ int opt, ret, fd;
+ static struct mtd_info_user meminfo;
+ int verbose;
+ int num_pages = 64;
+ int compare = 0;
+
+ while ((opt = getopt(argc, argv, "vn:c")) > 0) {
+ switch (opt) {
+ case 'v':
+ verbose = 1;
+ break;
+ case 'n':
+ num_pages = simple_strtoul(optarg, NULL, 0);
+ break;
+ case 'c':
+ compare = 1;
+ break;
+ default:
+ return COMMAND_ERROR_USAGE;
+ }
+ }
+
+ if (optind >= argc)
+ return COMMAND_ERROR_USAGE;
+
+ fd = open(argv[optind], O_RDWR);
+ if (fd < 0)
+ return fd;
+
+ ret = ioctl(fd, MEMGETINFO, &meminfo);
+
+ close(fd);
+
+ if (ret)
+ return ret;
+
+ if (num_pages * meminfo.writesize > meminfo.size) {
+ num_pages = meminfo.size >> ilog2(meminfo.writesize);
+ printf("WARNING: Device too small. Limiting to %d pages\n", num_pages);
+ }
+
+ printf("Starting NAND read disturbance test on %s with %d pages\n",
+ argv[optind], num_pages);
+ printf("Hit <ctrl-c> once to show current statistics, twice to stop the test\n");
+
+ nand_read_test(meminfo.mtd, num_pages, compare);
+
+ return 0;
+}
+
+BAREBOX_CMD_HELP_START(nand_read_test)
+BAREBOX_CMD_HELP_TEXT("This test tool continuously reads a bunch of NAND pages and")
+BAREBOX_CMD_HELP_TEXT("Prints a statistic about the number of bitflips and crc errors")
+BAREBOX_CMD_HELP_TEXT("occuring on each page")
+BAREBOX_CMD_HELP_TEXT("Options:")
+BAREBOX_CMD_HELP_OPT ("-n <npages>", "Specify number of pages to test (64)")
+BAREBOX_CMD_HELP_OPT ("-c", "Compare each page read with the first read")
+BAREBOX_CMD_HELP_END
+
+BAREBOX_CMD_START(nand_read_test)
+ .cmd = do_nand_read_test,
+ BAREBOX_CMD_DESC("NAND read disturbance test")
+ BAREBOX_CMD_OPTS("NANDDEV")
+ BAREBOX_CMD_GROUP(CMD_GRP_HWMANIP)
+ BAREBOX_CMD_HELP(cmd_nand_read_test_help)
+BAREBOX_CMD_END