commit f3deffa6abf6ab563eaa1842fa3836351d867f30 Author: Sascha Hauer 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 diff --git a/commands/Kconfig b/commands/Kconfig index d73a393885e9..e1d4fba6bc08 100644 --- a/commands/Kconfig +++ b/commands/Kconfig @@ -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 diff --git a/commands/Makefile b/commands/Makefile index b1cdf331c441..66d63c8c869a 100644 --- a/commands/Makefile +++ b/commands/Makefile @@ -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 diff --git a/commands/nand-read-test.c b/commands/nand-read-test.c new file mode 100644 index 000000000000..cac219178047 --- /dev/null +++ b/commands/nand-read-test.c @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2014 Sascha Hauer , 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 @ 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 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 ", "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