| /* |
| * 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 %"PRIdloff_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; |
| } |