]> www.infradead.org Git - mtd-utils.git/commitdiff
mtd-utils: Add nand page test utility
authorDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>
Mon, 22 Aug 2016 14:19:55 +0000 (16:19 +0200)
committerDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>
Thu, 17 Nov 2016 10:36:56 +0000 (11:36 +0100)
Basically a user space port of the mtd page test kernel module.
In addition to the module parameters, the utility supports using
only a sub-range of the flash erase blocks with a configurable stride.

Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Signed-off-by: Richard Weinberger <richard@nod.at>
.gitignore
tests/mtd-tests/Makemodule.am
tests/mtd-tests/nandpagetest.c [new file with mode: 0644]

index 718f65564c2bc6a2de89923a0b8972a4bc27ceaa..ddb170ee26454814bb010b7ea3184c920f3f6a15 100644 (file)
@@ -83,6 +83,7 @@ flash_stress
 flash_speed
 flash_readtest
 nandbiterrs
+nandpagetest
 ubiattach
 ubiblock
 ubicrc32
index 231a9713aa0970d3cf2df3cf3d7273f34302965f..55d83295d765fc59dab9ec1338e033b359a4e6a4 100644 (file)
@@ -18,8 +18,13 @@ flash_readtest_SOURCES = tests/mtd-tests/flash_readtest.c
 flash_readtest_LDADD = libmtd.a
 flash_readtest_CPPFLAGS = $(AM_CPPFLAGS)
 
+nandpagetest_SOURCES = tests/mtd-tests/nandpagetest.c
+nandpagetest_LDADD = libmtd.a
+nandpagetest_CPPFLAGS = $(AM_CPPFLAGS)
+
 MTDTEST_BINS = \
-       flash_torture flash_stress flash_speed nandbiterrs flash_readtest
+       flash_torture flash_stress flash_speed nandbiterrs flash_readtest \
+       nandpagetest
 
 if INSTALL_TESTS
 pkglibexec_PROGRAMS += $(MTDTEST_BINS)
diff --git a/tests/mtd-tests/nandpagetest.c b/tests/mtd-tests/nandpagetest.c
new file mode 100644 (file)
index 0000000..faf5fe3
--- /dev/null
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2006-2008 Nokia Corporation
+ * Copyright (C) 2016 sigma star gmbh
+ *
+ * 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.
+ *
+ * Test page read and write on MTD device.
+ *
+ * Author: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
+ *
+ * Based on linux pagetest.c
+ * Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
+ */
+#define PROGRAM_NAME "nandpagetest"
+
+#include <mtd/mtd-user.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <libmtd.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include "common.h"
+
+#define KEEP_CONTENTS 0x01
+#define SEED_SET 0x02
+
+static struct mtd_dev_info mtd;
+static const char *mtddev;
+static libmtd_t mtd_desc;
+
+static unsigned char *bbt=NULL, *writebuf=NULL, *backup=NULL;
+static unsigned char *twopages=NULL, *boundary=NULL;
+static int peb = -1, seed = -1, skip = -1, ebcnt = -1, flags = 0;
+static int fd, bufsize, pgsize, pgcnt;
+static unsigned int rnd_state;
+
+static const struct option options[] = {
+       { "help", no_argument, NULL, 'h' },
+       { "keep", no_argument, NULL, 'k' },
+       { "peb", required_argument, NULL, 'b' },
+       { "count", required_argument, NULL, 'c' },
+       { "skip", required_argument, NULL, 's' },
+       { "seed", required_argument, NULL, 'S' },
+       { NULL, 0, NULL, 0 },
+};
+
+static void usage(int status)
+{
+       fputs(
+       "Usage: "PROGRAM_NAME" [OPTIONS] <device>\n\n"
+       "Options:\n"
+       "  -h, --help         Display this help output\n"
+       "  -b, --peb <num>    Index of the first erase block to use\n"
+       "  -c, --count <num>  Number of erase blocks to use (default all)\n"
+       "  -s, --skip <num>   Number of erase blocks to skip\n"
+       "  -S, --seed <num>   Seed for pseudor random number generator\n"
+       "  -k, --keep         Restore existing contents after test\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, "hb:c:s:Sk", options, NULL);
+               if (c == -1)
+                       break;
+
+               switch (c) {
+               case 'b':
+                       if (peb >= 0)
+                               goto failmulti;
+                       peb = read_num(c, optarg);
+                       if (peb < 0)
+                               goto failarg;
+                       break;
+               case 'c':
+                       if (ebcnt >= 0)
+                               goto failmulti;
+                       ebcnt = read_num(c, optarg);
+                       if (ebcnt < 0)
+                               goto failarg;
+                       break;
+               case 's':
+                       if (skip >= 0)
+                               goto failmulti;
+                       skip = read_num(c, optarg);
+                       if (skip < 0)
+                               goto failarg;
+                       break;
+               case 'S':
+                       if (flags & SEED_SET)
+                               goto failmulti;
+                       seed = read_num(c, optarg);
+                       flags |= SEED_SET;
+                       break;
+               case 'k':
+                       if (flags & KEEP_CONTENTS)
+                               goto failmulti;
+                       flags |= KEEP_CONTENTS;
+                       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 (peb < 0)
+               peb = 0;
+       if (!(flags & SEED_SET))
+               seed = time(NULL);
+       if (skip < 0)
+               skip = 0;
+       return;
+failmulti:
+       errmsg_die("'-%c' specified more than once!", c);
+failarg:
+       errmsg_die("Invalid argument for '-%c'!", c);
+}
+
+static int write_eraseblock(int ebnum)
+{
+       int i;
+
+       for (i = 0; i < mtd.eb_size; ++i)
+               writebuf[i] = rand_r(&rnd_state);
+
+       return mtd_write(mtd_desc, &mtd, fd, ebnum, 0,
+                               writebuf, mtd.eb_size, NULL, 0, 0);
+}
+
+static void get_first_and_last_block(int *first, int *last)
+{
+       int i;
+
+       *first = peb;
+       for (i = 0; i < ebcnt && bbt[i]; ++i)
+               *first += skip + 1;
+
+       *last = peb + (ebcnt - 1) * (skip + 1);
+       for (i = 0; i < ebcnt && bbt[ebcnt - i - 1]; ++i)
+               *last -= skip + 1;
+}
+
+/* Do a read to set the internal dataRAMs to different data */
+static int flush_data_rams(int eb0, int ebn)
+{
+       int err;
+       err = mtd_read(&mtd, fd, eb0, 0, twopages, bufsize);
+       if (err)
+               return err;
+       err = mtd_read(&mtd, fd, ebn, mtd.eb_size-bufsize,
+                                       twopages, bufsize);
+       if (err)
+               return err;
+       memset(twopages, 0, bufsize);
+       return 0;
+}
+
+static int verify_eraseblock(int ebnum)
+{
+       int err = 0, i, ret = 0, eb0, ebn, rd, diff;
+       loff_t  offset = 0, addr, addrn;
+       unsigned int j, old_state;
+
+       for (i = 0; i < mtd.eb_size; ++i)
+               writebuf[i] = rand_r(&rnd_state);
+
+       get_first_and_last_block(&eb0, &ebn);
+
+       for (j = 0; j < pgcnt - 1; ++j, offset += pgsize) {
+               err = flush_data_rams(eb0, ebn);
+               if (err)
+                       return err;
+               err = mtd_read(&mtd, fd, ebnum, offset, twopages, bufsize);
+               if (err)
+                       break;
+               if (memcmp(twopages, writebuf + (j * pgsize), bufsize)) {
+                       fprintf(stderr, "error: verify failed at block %d, page %ld\n",
+                                       ebnum, ((long)offset) / pgsize );
+                       ret = -1;
+               }
+       }
+       /* Check boundary between eraseblocks */
+       addr = (loff_t)ebnum*mtd.eb_size + offset;
+       addrn = (loff_t)ebn*mtd.eb_size + mtd.eb_size - 2*pgsize;
+
+       if (addr <= addrn && !mtd_is_bad(&mtd, fd, ebnum+1)) {
+               old_state = rnd_state;
+               err = flush_data_rams(eb0, ebn);
+               if (err)
+                       return err;
+
+               if (lseek(fd, addr, SEEK_SET) != addr) {
+                       fprintf(stderr, "cannot seek mtd%d to offset %"PRIdoff_t,
+                                       mtd.mtd_num, addr);
+                       return -1;
+               }
+
+               for (rd = 0; rd < bufsize; rd += diff) {
+                       diff = read(fd, twopages + rd, bufsize - rd);
+                       if (diff < 0) {
+                               fprintf(stderr, "cannot read %d bytes from mtd%d "
+                                                               "(eraseblock %d, offset %d)",
+                                                               bufsize-rd, mtd.mtd_num, ebnum,
+                                                               (int)offset+rd);
+                               return -1;
+                       }
+               }
+
+               memcpy(boundary, writebuf + mtd.eb_size - pgsize, pgsize);
+
+               for (j = 0; j < pgsize; ++j)
+                       (boundary + pgsize)[j] = rand_r(&rnd_state);
+
+               if (memcmp(twopages, boundary, bufsize)) {
+                       fprintf(stderr, "error: verify failed at block %d, page %ld\n",
+                                       ebnum, ((long)offset) / pgsize );
+                       ret = -1;
+               }
+               rnd_state = old_state;
+       }
+       return ret;
+}
+
+static int crosstest(void)
+{
+       unsigned char *pp1, *pp2, *pp3, *pp4;
+       int eb0, ebn, err = 0, offset;
+
+       puts("crosstest");
+       pp1 = xzalloc(pgsize * 4);
+       if (!pp1)
+               return -ENOMEM;
+       pp2 = pp1 + pgsize;
+       pp3 = pp2 + pgsize;
+       pp4 = pp3 + pgsize;
+
+       get_first_and_last_block(&eb0, &ebn);
+
+       /* Read 2nd-to-last page to pp1 */
+       err = mtd_read(&mtd, fd, ebn, mtd.eb_size - 2*pgsize, pp1, pgsize);
+       if (err)
+               goto out;
+
+       /* Read 3rd-to-last page to pp1 */
+       err = mtd_read(&mtd, fd, ebn, mtd.eb_size - 3*pgsize, pp1, pgsize);
+       if (err)
+               goto out;
+
+       /* Read first page to pp2 */
+       printf("reading page at block %d, page %d\n", eb0, 0);
+       err = mtd_read(&mtd, fd, eb0, 0, pp2, pgsize);
+       if (err)
+               goto out;
+
+       /* Read last page to pp3 */
+       offset = mtd.eb_size - pgsize;
+       printf("reading page at block %d, page %d\n", ebn, offset/pgsize);
+       err = mtd_read(&mtd, fd, ebn, offset, pp3, pgsize);
+       if (err)
+               goto out;
+
+       /* Read first page again to pp4 */
+       printf("reading page at block %d, page %d\n", eb0, 0);
+       err = mtd_read(&mtd, fd, eb0, 0, pp4, pgsize);
+       if (err)
+               goto out;
+
+       /* pp2 and pp4 should be the same */
+       printf("verifying pages read at block %d match\n", eb0);
+       if (memcmp(pp2, pp4, pgsize)) {
+               fputs("verify failed!\n", stderr);
+               err = -1;
+       } else {
+               puts("crosstest ok");
+       }
+out:
+       free(pp1);
+       return err;
+}
+
+static int erasecrosstest(void)
+{
+       unsigned char *readbuf = twopages;
+       int err = 0, i, eb0, ebn;
+
+       puts("erasecrosstest");
+
+       get_first_and_last_block(&eb0, &ebn);
+
+       printf("erasing block %d\n", eb0);
+       err = mtd_erase(mtd_desc, &mtd, fd, eb0);
+       if (err)
+               return err;
+
+       printf("writing 1st page of block %d\n", eb0);
+       for (i = 0; i < pgsize; ++i)
+               writebuf[i] = rand_r(&rnd_state);
+       strcpy((char*)writebuf, "There is no data like this!");
+       err = mtd_write(mtd_desc, &mtd, fd, eb0, 0, writebuf, pgsize, NULL, 0, 0);
+       if (err)
+               return err;
+
+       printf("reading 1st page of block %d\n", eb0);
+       memset(readbuf, 0, pgsize);
+       err = mtd_read(&mtd, fd, eb0, 0, readbuf, pgsize);
+       if (err)
+               return err;
+
+       printf("verifying 1st page of block %d\n", eb0);
+       if (memcmp(writebuf, readbuf, pgsize)) {
+               fputs("verify failed!\n", stderr);
+               return -1;
+       }
+
+       printf("erasing block %d\n", eb0);
+       err = mtd_erase(mtd_desc, &mtd, fd, eb0);
+       if (err)
+               return err;
+
+       printf("writing 1st page of block %d\n", eb0);
+       for (i = 0; i < pgsize; ++i)
+               writebuf[i] = rand_r(&rnd_state);
+       strcpy((char*)writebuf, "There is no data like this!");
+       err = mtd_write(mtd_desc, &mtd, fd, eb0, 0, writebuf, pgsize, NULL, 0, 0);
+       if (err)
+               return err;
+
+       printf("erasing block %d\n", ebn);
+       err = mtd_erase(mtd_desc, &mtd, fd, ebn);
+       if (err)
+               return err;
+
+       printf("reading 1st page of block %d\n", eb0);
+       memset(readbuf, 0, pgsize);
+       err = mtd_read(&mtd, fd, eb0, 0, readbuf, pgsize);
+       if (err)
+               return err;
+
+       printf("verifying 1st page of block %d\n", eb0);
+       if (memcmp(writebuf, readbuf, pgsize)) {
+               fputs("verify failed!\n", stderr);
+               return -1;
+       }
+
+       puts("erasecrosstest ok");
+       return 0;
+}
+
+static int erasetest(void)
+{
+       int err = 0, i, ebnum, ebn;
+
+       puts("erasetest");
+       get_first_and_last_block(&ebnum, &ebn);
+
+       printf("erasing block %d\n", ebnum);
+       err = mtd_erase(mtd_desc, &mtd, fd, ebnum);
+       if (err)
+               return err;
+
+       printf("writing 1st page of block %d\n", ebnum);
+       for (i = 0; i < pgsize; ++i)
+               writebuf[i] = rand_r(&rnd_state);
+       err = mtd_write(mtd_desc, &mtd, fd, ebnum, 0,
+                                       writebuf, pgsize, NULL, 0, 0);
+       if (err)
+               return err;
+
+       printf("erasing block %d\n", ebnum);
+       err = mtd_erase(mtd_desc, &mtd, fd, ebnum);
+       if (err)
+               return err;
+
+       printf("reading 1st page of block %d\n", ebnum);
+       err = mtd_read(&mtd, fd, ebnum, 0, twopages, pgsize);
+       if (err)
+               return err;
+
+       printf("verifying 1st page of block %d is all 0xff\n", ebnum);
+       for (i = 0; i < pgsize; ++i) {
+               if (twopages[i] != 0xff) {
+                       fprintf(stderr, "verifying all 0xff failed at %d\n", i);
+                       return -1;
+               }
+       }
+
+       puts("erasetest ok");
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       int i, eb, err = 0, status = EXIT_FAILURE;
+       unsigned char *backupptr;
+
+       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);
+
+       pgsize = mtd.min_io_size;
+       pgcnt = mtd.eb_size / pgsize;
+       bufsize = pgsize * 2;
+
+       if (ebcnt < 0)
+               ebcnt = (mtd.eb_cnt - peb) / (skip + 1);
+
+       if (peb >= mtd.eb_cnt)
+               return errmsg("physical erase block %d is out of range!", peb);
+
+       eb = peb + (ebcnt - 1)*(skip + 1);
+
+       if (eb >= mtd.eb_cnt)
+               return errmsg("last physical erase block %d is out of range!", eb);
+
+       writebuf = xmalloc(mtd.eb_size);
+       twopages = xmalloc(bufsize);
+       boundary = xmalloc(bufsize);
+       bbt = xzalloc(ebcnt);
+
+       if ((fd = open(mtddev, O_RDWR)) == -1) {
+               perror(mtddev);
+               goto out_cleanup;
+       }
+
+       /* find bad blocks */
+       for (i = 0; i < ebcnt; ++i) {
+               eb = peb + i*(skip+1);
+               bbt[i] = mtd_is_bad(&mtd, fd, eb);
+
+               if (bbt[i])
+                       printf("ignoring bad erase block %d\n", eb);
+       }
+
+       /* create block backup */
+       if (flags & KEEP_CONTENTS) {
+               eb = 0;
+               for (i = 0; i < ebcnt; ++i) {
+                       if (!bbt[i])
+                               ++eb;
+               }
+               backup = malloc(mtd.eb_size * eb);
+               if (!backup) {
+                       fprintf(stderr, "not enough memory to keep block contents!\n");
+                       goto out_cleanup;
+               }
+               printf("reading %d blocks into memory\n", eb);
+               backupptr = backup;
+               for (i = 0; i < ebcnt; ++i) {
+                       if (bbt[i])
+                               continue;
+                       eb = peb + i*(skip+1);
+                       err = mtd_read(&mtd, fd, eb, 0, backupptr, mtd.eb_size);
+                       if (err) {
+                               fprintf(stderr, "error reading block %d!\n", eb);
+                               goto out_cleanup;
+                       }
+                       backupptr += mtd.eb_size;
+               }
+       }
+
+       /* Erase all eraseblocks */
+       puts("erasing all blocks");
+       for (i = 0; i < ebcnt; ++i) {
+               if (bbt[i])
+                       continue;
+               eb = peb + i*(skip+1);
+               if (mtd_erase(mtd_desc, &mtd, fd, eb)) {
+                       fprintf(stderr, "error erasing block %d\n", eb);
+                       goto out;
+               }
+       }
+       printf("erased %u eraseblocks\n", ebcnt);
+
+       /* Write all eraseblocks */
+       rnd_state = seed;
+       puts("writing all blocks");
+       for (i = 0; i < ebcnt; ++i) {
+               if (bbt[i])
+                       continue;
+               eb = peb + i*(skip+1);
+               err = write_eraseblock(eb);
+               if (err)
+                       goto out;
+               if (i % 256 == 0)
+                       printf("written up to eraseblock %u\n", i);
+       }
+       printf("written %u eraseblocks\n", i);
+
+       /* Check all eraseblocks */
+       rnd_state = seed;
+       puts("verifying all eraseblocks");
+       for (i = 0; i < ebcnt; ++i) {
+               eb = peb + i*(skip+1);
+               if (bbt[i])
+                       continue;
+               err = verify_eraseblock(eb);
+               if (err)
+                       goto out;
+               if (i % 256 == 0)
+                       printf("verified up to eraseblock %u\n", i);
+       }
+       printf("verified %u eraseblocks\n", i);
+
+       if (crosstest())
+               goto out;
+
+       if (erasecrosstest())
+               goto out;
+
+       if (erasetest())
+               goto out;
+
+       status = EXIT_SUCCESS;
+out:
+       /* restore block backup */
+       if (flags & KEEP_CONTENTS) {
+               puts("restoring original contents");
+               backupptr = backup;
+               for (i = 0; i < ebcnt; ++i) {
+                       if (bbt[i])
+                               continue;
+                       eb = peb + i*(skip+1);
+                       if (mtd_erase(mtd_desc, &mtd, fd, eb)) {
+                               fprintf(stderr, "error erasing block %d!\n", eb);
+                               status = EXIT_FAILURE;
+                       }
+                       err = mtd_write(mtd_desc, &mtd, fd, eb, 0,
+                                                       backupptr, mtd.eb_size, NULL, 0, 0);
+                       if (err) {
+                               fprintf(stderr, "error restoring block %d!\n", eb);
+                               status = EXIT_FAILURE;
+                       }
+                       backupptr += mtd.eb_size;
+               }
+       }
+out_cleanup:
+       free(bbt);
+       free(boundary);
+       free(twopages);
+       free(writebuf);
+       free(backup);
+       close(fd);
+       return status;
+}