From 4c6eb0a05500f8ff3bc38b52b3c0e68b25e22e0b Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Mon, 22 Aug 2016 16:19:55 +0200 Subject: mtd-utils: Add nand page test utility 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 Signed-off-by: Richard Weinberger --- .gitignore | 1 + tests/mtd-tests/Makemodule.am | 7 +- tests/mtd-tests/nandpagetest.c | 592 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 599 insertions(+), 1 deletion(-) create mode 100644 tests/mtd-tests/nandpagetest.c diff --git a/.gitignore b/.gitignore index 718f655..ddb170e 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,7 @@ flash_stress flash_speed flash_readtest nandbiterrs +nandpagetest ubiattach ubiblock ubicrc32 diff --git a/tests/mtd-tests/Makemodule.am b/tests/mtd-tests/Makemodule.am index 231a971..55d8329 100644 --- a/tests/mtd-tests/Makemodule.am +++ b/tests/mtd-tests/Makemodule.am @@ -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 index 0000000..faf5fe3 --- /dev/null +++ b/tests/mtd-tests/nandpagetest.c @@ -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 + * + * Based on linux pagetest.c + * Author: Adrian Hunter + */ +#define PROGRAM_NAME "nandpagetest" + +#include +#include +#include +#include +#include +#include +#include +#include + +#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] \n\n" + "Options:\n" + " -h, --help Display this help output\n" + " -b, --peb Index of the first erase block to use\n" + " -c, --count Number of erase blocks to use (default all)\n" + " -s, --skip Number of erase blocks to skip\n" + " -S, --seed 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; +} -- cgit v1.2.3