aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--tests/mtd-tests/Makemodule.am6
-rw-r--r--tests/mtd-tests/nandbiterrs.c516
3 files changed, 522 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index 5f3d4dc..1e6f844 100644
--- a/.gitignore
+++ b/.gitignore
@@ -81,6 +81,7 @@ test_2
flash_torture
flash_stress
flash_speed
+nandbiterrs
ubiattach
ubiblock
ubicrc32
diff --git a/tests/mtd-tests/Makemodule.am b/tests/mtd-tests/Makemodule.am
index ac40697..38b419f 100644
--- a/tests/mtd-tests/Makemodule.am
+++ b/tests/mtd-tests/Makemodule.am
@@ -10,8 +10,12 @@ flash_speed_SOURCES = tests/mtd-tests/flash_speed.c
flash_speed_LDADD = libmtd.a
flash_speed_CPPFLAGS = $(AM_CPPFLAGS)
+nandbiterrs_SOURCES = tests/mtd-tests/nandbiterrs.c
+nandbiterrs_LDADD = libmtd.a
+nandbiterrs_CPPFLAGS = $(AM_CPPFLAGS)
+
MTDTEST_BINS = \
- flash_torture flash_stress flash_speed
+ flash_torture flash_stress flash_speed nandbiterrs
if INSTALL_TESTS
pkglibexec_PROGRAMS += $(MTDTEST_BINS)
diff --git a/tests/mtd-tests/nandbiterrs.c b/tests/mtd-tests/nandbiterrs.c
new file mode 100644
index 0000000..6acbfee
--- /dev/null
+++ b/tests/mtd-tests/nandbiterrs.c
@@ -0,0 +1,516 @@
+/*
+ * Copyright © 2012 NetCommWireless
+ * Iwo Mergler <Iwo.Mergler@netcommwireless.com.au>
+ *
+ * Copyright © 2015 sigma star gmbh
+ * David Oberhollenzer <david.oberhollenzer@sigma-star.at>
+ *
+ * Test for multi-bit error recovery on a NAND page. This mostly tests the
+ * ECC controller / driver.
+ *
+ * There are two test modes:
+ *
+ * 0 - artificially inserting bit errors until the ECC fails
+ * This is the default method and fairly quick. It should
+ * be independent of the quality of the FLASH.
+ *
+ * 1 - re-writing the same pattern repeatedly until the ECC fails.
+ * This method relies on the physics of NAND FLASH to eventually
+ * generate '0' bits if '1' has been written sufficient times.
+ * Depending on the NAND, the first bit errors will appear after
+ * 1000 or more writes and then will usually snowball, reaching the
+ * limits of the ECC quickly.
+ *
+ * The test stops after 10000 cycles, should your FLASH be
+ * exceptionally good and not generate bit errors before that. Try
+ * a different page in that case.
+ *
+ * Please note that neither of these tests will significantly 'use up' any
+ * FLASH endurance. Only a maximum of two erase operations will be performed.
+ *
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; see the file COPYING. If not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#define PROGRAM_NAME "nandbiterrs"
+
+#include <mtd/mtd-user.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <libmtd.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "common.h"
+
+/* We don't expect more than this many correctable bit errors per page. */
+#define MAXBITS 512
+
+#define KEEP_CONTENTS 0x01
+#define MODE_INCREMENTAL 0x02
+#define MODE_OVERWRITE 0x04
+
+static int peb = -1, page = -1, max_overwrite = -1, seed = -1;
+static const char *mtddev;
+
+static unsigned char *wbuffer, *rbuffer, *old_data;
+static int fd, pagesize, pagecount, flags;
+static struct mtd_dev_info mtd;
+static libmtd_t mtd_desc;
+
+static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "keep", no_argument, NULL, 'k' },
+ { "peb", required_argument, NULL, 'b' },
+ { "page", required_argument, NULL, 'p' },
+ { "seed", required_argument, NULL, 's' },
+ { "writes", required_argument, NULL, 'w' },
+ { "incremental", no_argument, NULL, 'i' },
+ { "overwrite", no_argument, NULL, 'o' },
+ { NULL, 0, NULL, 0 },
+};
+
+static void usage(int status)
+{
+ fputs(
+ "Usage: "PROGRAM_NAME" [OPTIONS] <device>\n\n"
+ "Common options:\n"
+ " -h, --help Display this help output\n"
+ " -k, --keep Restore existing contents after test\n"
+ " -b, --peb <num> Use this physical erase block\n"
+ " -p, --page <num> Use this page within the erase block\n"
+ " -s, --seed <num> Specify seed for PRNG\n\n"
+ "Options controling test mode:\n"
+ " -i, --incremental Manually insert bit errors until ECC fails\n"
+ " -o, --overwrite Rewrite page until bits flip and ECC fails\n\n"
+ "Test mode specific options:\n"
+ " -w, --writes <num> Number of writes (default 10000)\n",
+ status==EXIT_SUCCESS ? stdout : stderr);
+ exit(status);
+}
+
+static long read_num(int opt, const char *arg)
+{
+ char *end;
+ long num;
+
+ num = strtol(arg, &end, 0);
+
+ if (!end || *end != '\0') {
+ fprintf(stderr, "-%c: expected integer argument\n", opt);
+ exit(EXIT_FAILURE);
+ }
+ return num;
+}
+
+static void process_options(int argc, char **argv)
+{
+ int c;
+
+ while (1) {
+ c = getopt_long(argc, argv, "hkb:p:s:iow:", options, NULL);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'k':
+ if (flags & KEEP_CONTENTS)
+ goto failmulti;
+ flags |= KEEP_CONTENTS;
+ break;
+ case 'b':
+ if (peb >= 0)
+ goto failmulti;
+ peb = read_num(c, optarg);
+ if (peb < 0)
+ goto failarg;
+ break;
+ case 'i':
+ if (flags & (MODE_INCREMENTAL|MODE_OVERWRITE))
+ goto failmultimode;
+ flags |= MODE_INCREMENTAL;
+ break;
+ case 'o':
+ if (flags & (MODE_INCREMENTAL|MODE_OVERWRITE))
+ goto failmultimode;
+ flags |= MODE_OVERWRITE;
+ break;
+ case 'w':
+ if (max_overwrite > 0)
+ goto failmulti;
+ max_overwrite = read_num(c, optarg);
+ if (max_overwrite <= 0)
+ goto failarg;
+ break;
+ case 's':
+ if (seed >= 0)
+ goto failmulti;
+ seed = read_num(c, optarg);
+ if (seed < 0)
+ goto failarg;
+ break;
+ case 'p':
+ if (page > 0)
+ goto failmulti;
+ page = read_num(c, optarg);
+ if (page < 0)
+ goto failarg;
+ break;
+ case 'h':
+ usage(EXIT_SUCCESS);
+ default:
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (optind < argc)
+ mtddev = argv[optind++];
+ else
+ errmsg_die("No device specified!\n");
+
+ if (optind < argc)
+ usage(EXIT_FAILURE);
+
+ if (!(flags & (MODE_OVERWRITE|MODE_INCREMENTAL)))
+ errmsg_die("No test mode specified!");
+
+ if ((max_overwrite > 0) && !(flags & MODE_OVERWRITE))
+ errmsg_die("Write count specified but mode is not --overwrite!");
+
+ if (max_overwrite < 0)
+ max_overwrite = 10000;
+ if (peb < 0)
+ peb = 0;
+ if (page < 0)
+ page = 0;
+ if (seed < 0)
+ seed = 0;
+ return;
+failmultimode:
+ errmsg_die("Test mode specified more than once!");
+failmulti:
+ errmsg_die("'-%c' specified more than once!", c);
+failarg:
+ errmsg_die("Invalid argument for '-%c'!", c);
+}
+
+/* 'random' bytes from known offsets */
+static unsigned char hash(unsigned int offset)
+{
+ unsigned int v = offset;
+ unsigned char c;
+ v ^= 0x7f7edfd3;
+ v = v ^ (v >> 3);
+ v = v ^ (v >> 5);
+ v = v ^ (v >> 13);
+ c = v & 0xFF;
+ /* Reverse bits of result. */
+ c = (c & 0x0F) << 4 | (c & 0xF0) >> 4;
+ c = (c & 0x33) << 2 | (c & 0xCC) >> 2;
+ c = (c & 0x55) << 1 | (c & 0xAA) >> 1;
+ return c;
+}
+
+static int write_page(void)
+{
+ int err;
+
+ err = mtd_write(mtd_desc, &mtd, fd, peb, page*pagesize,
+ wbuffer, pagesize, NULL, 0, 0);
+
+ if (err)
+ fprintf(stderr, "Failed to write page %d in block %d\n", peb, page);
+
+ return err;
+}
+
+static int rewrite_page(void)
+{
+ if (ioctl(fd, MTDFILEMODE, MTD_FILE_MODE_RAW) != 0)
+ goto fail_mode;
+
+ if (write_page() != 0)
+ return -1;
+
+ if (ioctl(fd, MTDFILEMODE, MTD_FILE_MODE_NORMAL) != 0)
+ goto fail_mode;
+
+ return 0;
+fail_mode:
+ perror("MTDFILEMODE");
+ return -1;
+}
+
+static int read_page(void)
+{
+ struct mtd_ecc_stats old, new;
+ int err = 0;
+
+ if (ioctl(fd, ECCGETSTATS, &old) != 0)
+ goto failstats;
+
+ err = mtd_read(&mtd, fd, peb, page*pagesize, rbuffer, pagesize);
+ if (err) {
+ fputs("Read failed!\n", stderr);
+ return -1;
+ }
+
+ if (new.failed > old.failed) {
+ fprintf(stderr, "Failed to recover %d bitflips\n",
+ new.failed - old.failed);
+ return -1;
+ }
+
+ if (ioctl(fd, ECCGETSTATS, &new) != 0)
+ goto failstats;
+
+ return new.corrected - old.corrected;
+failstats:
+ perror("ECCGETSTATS");
+ return -1;
+}
+
+static int verify_page(void)
+{
+ unsigned int i, errs = 0;
+
+ for (i = 0; i < pagesize; ++i) {
+ if (rbuffer[i] != hash(i+seed))
+ ++errs;
+ }
+
+ if (errs)
+ fputs("ECC failure, invalid data despite read success\n", stderr);
+
+ return errs;
+}
+
+/* Finds the first '1' bit in wbuffer and sets it to '0'. */
+static int insert_biterror(void)
+{
+ int bit, mask, byte;
+
+ for (byte = 0; byte < pagesize; ++byte) {
+ for (bit = 7, mask = 0x80; bit >= 0; bit--, mask>>=0) {
+ if (wbuffer[byte] & mask) {
+ wbuffer[byte] &= ~mask;
+ printf("Inserted biterror @ %u/%u\n", byte, bit);
+ return 0;
+ }
+ }
+ ++byte;
+ }
+ fputs("biterror: Failed to find a '1' bit\n", stderr);
+ return -1;
+}
+
+/* Writes 'random' data to page and then introduces deliberate bit
+ * errors into the page, while verifying each step. */
+static int incremental_errors_test(void)
+{
+ unsigned int i, errs_per_subpage = 0;
+ int count = 0;
+
+ puts("incremental biterrors test");
+
+ for (i = 0; i < pagesize; ++i)
+ wbuffer[i] = hash(i+seed);
+
+ if (write_page() != 0)
+ return -1;
+
+ for (errs_per_subpage = 0; ; ++errs_per_subpage) {
+ if (rewrite_page() != 0)
+ return -1;
+
+ count = read_page();
+ if (count > 0)
+ printf("Read reported %d corrected bit errors\n", count);
+ if (count < 0) {
+ fprintf(stderr, "Read error after %d bit errors per page\n",
+ errs_per_subpage);
+ return 0;
+ }
+
+ if (verify_page() != 0)
+ return -1;
+
+ printf("Successfully corrected %d bit errors per subpage\n",
+ errs_per_subpage);
+
+ if (insert_biterror() != 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/* Writes 'random' data to page and then re-writes that same data repeatedly.
+ This eventually develops bit errors (bits written as '1' will slowly become
+ '0'), which are corrected as far as the ECC is capable of. */
+static int overwrite_test(void)
+{
+ unsigned int i, max_corrected = 0, opno;
+ unsigned int bitstats[MAXBITS]; /* bit error histogram. */
+ int err = 0;
+
+ memset(bitstats, 0, sizeof(bitstats));
+
+ puts("overwrite biterrors test");
+
+ for (i = 0; i < pagesize; ++i)
+ wbuffer[i] = hash(i+seed);
+
+ if (write_page() != 0)
+ return -1;
+
+ for (opno = 0; opno < max_overwrite; ++opno) {
+ err = write_page();
+ if (err)
+ break;
+
+ err = read_page();
+ if (err >= 0) {
+ if (err >= MAXBITS) {
+ puts("Implausible number of bit errors corrected");
+ err = -1;
+ break;
+ }
+ bitstats[err]++;
+ if (err > max_corrected) {
+ max_corrected = err;
+ printf("Read reported %d corrected bit errors\n", err);
+ }
+ } else {
+ err = 0;
+ break;
+ }
+
+ err = verify_page();
+ if (err) {
+ bitstats[max_corrected] = opno;
+ break;
+ }
+ }
+
+ /* At this point bitstats[0] contains the number of ops with no bit
+ * errors, bitstats[1] the number of ops with 1 bit error, etc. */
+ printf("Bit error histogram (%d operations total):\n", opno);
+ for (i = 0; i < max_corrected; ++i) {
+ printf("Page reads with %3d corrected bit errors: %d\n",
+ i, bitstats[i]);
+ }
+ return err;
+}
+
+int main(int argc, char **argv)
+{
+ int err = 0, status = EXIT_FAILURE;
+
+ process_options(argc, argv);
+
+ mtd_desc = libmtd_open();
+ if (!mtd_desc)
+ return errmsg("can't initialize libmtd");
+
+ if (mtd_get_dev_info(mtd_desc, mtddev, &mtd) < 0)
+ return errmsg("mtd_get_dev_info failed");
+
+ if (mtd.type!=MTD_MLCNANDFLASH && mtd.type!=MTD_NANDFLASH)
+ return errmsg("%s is not a NAND flash!", mtddev);
+
+ pagesize = mtd.subpage_size;
+ pagecount = mtd.eb_size / pagesize;
+
+ if (peb >= mtd.eb_cnt)
+ return errmsg("Physical erase block %d is out of range!", peb);
+ if (page >= pagecount)
+ return errmsg("Page number %d is out of range!", page);
+ if ((fd = open(mtddev, O_RDWR)) == -1) {
+ perror(mtddev);
+ return EXIT_FAILURE;
+ }
+
+ if (flags & KEEP_CONTENTS) {
+ old_data = malloc(mtd.eb_size);
+ if (!old_data) {
+ perror(NULL);
+ goto fail_dev;
+ }
+ if (mtd_read(&mtd, fd, peb, 0, old_data, mtd.eb_size)) {
+ fprintf(stderr, "Reading erase block %d failed!\n", peb);
+ goto fail_dev;
+ }
+ }
+
+ wbuffer = malloc(pagesize);
+ if (!wbuffer) {
+ perror(NULL);
+ goto fail_dev;
+ }
+
+ rbuffer = malloc(pagesize);
+ if (!rbuffer) {
+ perror(NULL);
+ goto fail_rbuffer;
+ }
+
+ if (mtd_erase(mtd_desc, &mtd, fd, peb)) {
+ fprintf(stderr, "Cannot erase block %d\n", peb);
+ goto fail_test;
+ }
+
+ if (flags & MODE_INCREMENTAL)
+ err = incremental_errors_test();
+ else if (flags & MODE_OVERWRITE)
+ err = overwrite_test();
+
+ status = err ? EXIT_FAILURE : EXIT_SUCCESS;
+
+ if (flags & KEEP_CONTENTS) {
+ if (mtd_erase(mtd_desc, &mtd, fd, peb)) {
+ fprintf(stderr, "Restoring: Cannot erase block %d\n", peb);
+ status = EXIT_FAILURE;
+ goto fail_test;
+ }
+
+ err = mtd_write(mtd_desc, &mtd, fd, peb, 0,
+ old_data, mtd.eb_size, NULL, 0, 0);
+
+ if (err) {
+ fputs("Failed restoring old block contents!\n", stderr);
+ status = EXIT_FAILURE;
+ }
+ } else {
+ /* We leave the block un-erased in case of test failure. */
+ if (err)
+ goto fail_test;
+
+ if (mtd_erase(mtd_desc, &mtd, fd, peb)) {
+ fprintf(stderr, "Cannot erase block %d\n", peb);
+ status = EXIT_FAILURE;
+ }
+ }
+fail_test:
+ free(rbuffer);
+fail_rbuffer:
+ free(wbuffer);
+fail_dev:
+ close(fd);
+ free(old_data);
+ return status;
+}