123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- /*
- * 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
- #define PAGE_ERASED 0x08
- 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' },
- { "erased", no_argument, NULL, 'e' },
- { "writes", required_argument, NULL, 'w' },
- { "incremental", no_argument, NULL, 'i' },
- { "overwrite", no_argument, NULL, 'o' },
- { NULL, 0, NULL, 0 },
- };
- static NORETURN 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"
- " -e, --erased Test erased pages instead of written pages\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:eiow:", 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 'e':
- flags |= PAGE_ERASED;
- 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 void init_buffer(void)
- {
- unsigned int i;
- if (flags & PAGE_ERASED) {
- memset(wbuffer, 0xff, pagesize);
- } else {
- for (i = 0; i < pagesize; ++i)
- wbuffer[i] = hash(i+seed);
- }
- }
- static int write_page(void)
- {
- int raw = flags & PAGE_ERASED;
- int err;
- if (raw && ioctl(fd, MTDFILEMODE, MTD_FILE_MODE_RAW) != 0)
- goto fail_mode;
- 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);
- if (raw && ioctl(fd, MTDFILEMODE, MTD_FILE_MODE_NORMAL) != 0)
- goto fail_mode;
- return err;
- fail_mode:
- perror("MTDFILEMODE");
- return -1;
- }
- 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 (ioctl(fd, ECCGETSTATS, &new) != 0)
- goto failstats;
- if (new.failed > old.failed) {
- fprintf(stderr, "Failed to recover %d bitflips\n",
- new.failed - old.failed);
- return -1;
- }
- return new.corrected - old.corrected;
- failstats:
- perror("ECCGETSTATS");
- return -1;
- }
- static int verify_page(void)
- {
- int erased = flags & PAGE_ERASED;
- unsigned int i, errs = 0;
- for (i = 0; i < pagesize; ++i) {
- if (rbuffer[i] != (erased ? 0xff : 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 >>= 1) {
- if (wbuffer[byte] & mask) {
- wbuffer[byte] &= ~mask;
- printf("Inserted biterror @ %u/%u\n", byte, bit);
- return 0;
- }
- }
- }
- 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 errs_per_subpage = 0;
- int count = 0;
- puts("incremental biterrors test");
- init_buffer();
- 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");
- init_buffer();
- 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;
- }
|