blob: 6acbfee25bc8a00deac71611b80292665d7e134e [file] [log] [blame]
/*
* 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;
}