| /* |
| * nanddump.c |
| * |
| * Copyright (C) 2000 David Woodhouse (dwmw2@infradead.org) |
| * Steven J. Hill (sjhill@realitydiluted.com) |
| * |
| * 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. |
| * |
| * Overview: |
| * This utility dumps the contents of raw NAND chips or NAND |
| * chips contained in DoC devices. |
| */ |
| |
| #define _GNU_SOURCE |
| #include <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <getopt.h> |
| #include <sys/ioctl.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| |
| #include <asm/types.h> |
| #include <mtd/mtd-user.h> |
| |
| #define PROGRAM "nanddump" |
| #define VERSION "$Revision: 1.29 $" |
| |
| static struct nand_oobinfo none_oobinfo = { |
| .useecc = MTD_NANDECC_OFF, |
| }; |
| |
| static void display_help (void) |
| { |
| printf( |
| "Usage: nanddump [OPTIONS] MTD-device\n" |
| "Dumps the contents of a nand mtd partition.\n" |
| "\n" |
| " --help Display this help and exit\n" |
| " --version Output version information and exit\n" |
| "-f file --file=file Dump to file\n" |
| "-i --ignoreerrors Ignore errors\n" |
| "-l length --length=length Length\n" |
| "-n --noecc Read without error correction\n" |
| "-o --omitoob Omit oob data\n" |
| "-b --omitbad Omit bad blocks from the dump\n" |
| "-p --prettyprint Print nice (hexdump)\n" |
| "-q --quiet Don't display progress and status messages\n" |
| "-s addr --startaddress=addr Start address\n" |
| "-e --omitempty Omit empty pages\n" |
| ); |
| exit(EXIT_SUCCESS); |
| } |
| |
| static void display_version (void) |
| { |
| printf(PROGRAM " " VERSION "\n" |
| "\n" |
| PROGRAM " comes with NO WARRANTY\n" |
| "to the extent permitted by law.\n" |
| "\n" |
| "You may redistribute copies of " PROGRAM "\n" |
| "under the terms of the GNU General Public Licence.\n" |
| "See the file `COPYING' for more information.\n"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| // Option variables |
| |
| static bool ignoreerrors = false; // ignore errors |
| static bool pretty_print = false; // print nice in ascii |
| static bool noecc = false; // don't error correct |
| static bool omitoob = false; // omit oob data |
| static unsigned long start_addr; // start address |
| static unsigned long length; // dump length |
| static const char *mtddev; // mtd device name |
| static const char *dumpfile; // dump file name |
| static bool omitbad = false; |
| static bool quiet = false; // suppress diagnostic output |
| static bool omitempty = false; // don't output empty pages |
| |
| static void process_options (int argc, char * const argv[]) |
| { |
| int error = 0; |
| |
| for (;;) { |
| int option_index = 0; |
| static const char *short_options = "bs:f:il:opqne"; |
| static const struct option long_options[] = { |
| {"help", no_argument, 0, 0}, |
| {"version", no_argument, 0, 0}, |
| {"file", required_argument, 0, 'f'}, |
| {"ignoreerrors", no_argument, 0, 'i'}, |
| {"prettyprint", no_argument, 0, 'p'}, |
| {"omitoob", no_argument, 0, 'o'}, |
| {"omitbad", no_argument, 0, 'b'}, |
| {"startaddress", required_argument, 0, 's'}, |
| {"length", required_argument, 0, 'l'}, |
| {"noecc", no_argument, 0, 'n'}, |
| {"quiet", no_argument, 0, 'q'}, |
| {"omitempty", no_argument, 0, 'e'}, |
| {0, 0, 0, 0}, |
| }; |
| |
| int c = getopt_long(argc, argv, short_options, |
| long_options, &option_index); |
| if (c == EOF) { |
| break; |
| } |
| |
| switch (c) { |
| case 0: |
| switch (option_index) { |
| case 0: |
| display_help(); |
| break; |
| case 1: |
| display_version(); |
| break; |
| } |
| break; |
| case 'b': |
| omitbad = true; |
| break; |
| case 's': |
| start_addr = strtol(optarg, NULL, 0); |
| break; |
| case 'f': |
| if (!(dumpfile = strdup(optarg))) { |
| perror("stddup"); |
| exit(EXIT_FAILURE); |
| } |
| break; |
| case 'i': |
| ignoreerrors = true; |
| break; |
| case 'l': |
| length = strtol(optarg, NULL, 0); |
| break; |
| case 'o': |
| omitoob = true; |
| break; |
| case 'p': |
| pretty_print = true; |
| break; |
| case 'q': |
| quiet = true; |
| break; |
| case 'n': |
| noecc = true; |
| break; |
| case 'e': |
| omitempty = true; |
| break; |
| case '?': |
| error++; |
| break; |
| } |
| } |
| |
| if (quiet && pretty_print) { |
| fprintf(stderr, "The quiet and pretty print options are mutually-\n" |
| "exclusive. Choose one or the other.\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| if ((argc - optind) != 1 || error) |
| display_help (); |
| |
| mtddev = argv[optind]; |
| } |
| |
| /* |
| * Main program |
| */ |
| int main(int argc, char * const argv[]) |
| { |
| unsigned long ofs, end_addr = 0; |
| unsigned long long blockstart = 1; |
| int ret, i, fd, ofd = 0, bs, badblock = 0; |
| struct mtd_oob_buf oob; |
| mtd_info_t meminfo; |
| char pretty_buf[80]; |
| int oobinfochanged = 0 ; |
| struct nand_oobinfo old_oobinfo; |
| struct mtd_ecc_stats stat1, stat2; |
| bool eccstats = false; |
| bool isempty; |
| unsigned char *readbuf = NULL, *oobbuf = NULL; |
| |
| process_options(argc, argv); |
| |
| /* Open MTD device */ |
| if ((fd = open(mtddev, O_RDONLY)) == -1) { |
| perror(mtddev); |
| exit (EXIT_FAILURE); |
| } |
| |
| /* Fill in MTD device capability structure */ |
| if (ioctl(fd, MEMGETINFO, &meminfo) != 0) { |
| perror("MEMGETINFO"); |
| close(fd); |
| exit (EXIT_FAILURE); |
| } |
| |
| /* Allocate buffers */ |
| oobbuf = malloc(sizeof(oobbuf) * meminfo.oobsize); |
| readbuf = malloc(sizeof(readbuf) * meminfo.writesize); |
| |
| if (oobbuf == NULL || readbuf == NULL) |
| goto closeall; |
| |
| /* Fill in oob info */ |
| oob.start = 0; |
| oob.length = meminfo.oobsize; |
| oob.ptr = oobbuf; |
| |
| if (noecc) { |
| ret = ioctl(fd, MTDFILEMODE, (void *) MTD_MODE_RAW); |
| if (ret == 0) { |
| oobinfochanged = 2; |
| } else { |
| switch (errno) { |
| case ENOTTY: |
| if (ioctl (fd, MEMGETOOBSEL, &old_oobinfo) != 0) { |
| perror ("MEMGETOOBSEL"); |
| goto closeall; |
| } |
| if (ioctl (fd, MEMSETOOBSEL, &none_oobinfo) != 0) { |
| perror ("MEMSETOOBSEL"); |
| goto closeall; |
| } |
| oobinfochanged = 1; |
| break; |
| default: |
| perror ("MTDFILEMODE"); |
| goto closeall; |
| } |
| } |
| } else { |
| |
| /* check if we can read ecc stats */ |
| if (!ioctl(fd, ECCGETSTATS, &stat1)) { |
| eccstats = true; |
| if (!quiet) { |
| fprintf(stderr, "ECC failed: %d\n", stat1.failed); |
| fprintf(stderr, "ECC corrected: %d\n", stat1.corrected); |
| fprintf(stderr, "Number of bad blocks: %d\n", stat1.badblocks); |
| fprintf(stderr, "Number of bbt blocks: %d\n", stat1.bbtblocks); |
| } |
| } else |
| perror("No ECC status information available"); |
| } |
| |
| /* Open output file for writing. If file name is "-", write to standard |
| * output. */ |
| if (!dumpfile) { |
| ofd = STDOUT_FILENO; |
| } else if ((ofd = open(dumpfile, O_WRONLY | O_TRUNC | O_CREAT, 0644))== -1) { |
| perror (dumpfile); |
| goto closeall; |
| } |
| |
| /* Initialize start/end addresses and block size */ |
| if (length) |
| end_addr = start_addr + length; |
| if (!length || end_addr > meminfo.size) |
| end_addr = meminfo.size; |
| |
| bs = meminfo.writesize; |
| |
| /* Print informative message */ |
| |
| if (!quiet) { |
| fprintf(stderr, "Block size %u, page size %u, OOB size %u\n", |
| meminfo.erasesize, meminfo.writesize, meminfo.oobsize); |
| fprintf(stderr, |
| "Dumping data starting at 0x%08x and ending at 0x%08x...\n", |
| (unsigned int) start_addr, (unsigned int) end_addr); |
| } |
| /* Dump the flash contents */ |
| for (ofs = start_addr; ofs < end_addr ; ofs+=bs) { |
| |
| // new eraseblock , check for bad block |
| if (blockstart != (ofs & (~meminfo.erasesize + 1))) { |
| blockstart = ofs & (~meminfo.erasesize + 1); |
| if ((badblock = ioctl(fd, MEMGETBADBLOCK, &blockstart)) < 0) { |
| perror("ioctl(MEMGETBADBLOCK)"); |
| goto closeall; |
| } |
| } |
| |
| if (badblock) { |
| if (omitbad) |
| continue; |
| memset (readbuf, 0xff, bs); |
| } else { |
| /* Read page data and exit on failure */ |
| if (pread(fd, readbuf, bs, ofs) != bs) { |
| perror("pread"); |
| goto closeall; |
| } |
| } |
| |
| /* ECC stats available ? */ |
| if (eccstats) { |
| if (ioctl(fd, ECCGETSTATS, &stat2)) { |
| perror("ioctl(ECCGETSTATS)"); |
| goto closeall; |
| } |
| if (stat1.failed != stat2.failed) |
| fprintf(stderr, "ECC: %d uncorrectable bitflip(s)" |
| " at offset 0x%08lx\n", |
| stat2.failed - stat1.failed, ofs); |
| if (stat1.corrected != stat2.corrected) |
| fprintf(stderr, "ECC: %d corrected bitflip(s) at" |
| " offset 0x%08lx\n", |
| stat2.corrected - stat1.corrected, ofs); |
| stat1 = stat2; |
| } |
| |
| if (omitempty) { |
| isempty = true; |
| for (i = 0; i < bs; i++) { |
| if (readbuf[i] != 0xff) { |
| isempty = false; |
| break; |
| } |
| } |
| |
| if (isempty) { |
| if (!quiet) |
| fprintf(stderr, "Skipping empty page at offset 0x%08lx\n", ofs); |
| |
| continue; |
| } |
| } |
| |
| /* Write out page data */ |
| if (pretty_print) { |
| for (i = 0; i < bs; i += 16) { |
| sprintf(pretty_buf, |
| "0x%08x: %02x %02x %02x %02x %02x %02x %02x " |
| "%02x %02x %02x %02x %02x %02x %02x %02x %02x\n", |
| (unsigned int) (ofs + i), readbuf[i], |
| readbuf[i+1], readbuf[i+2], |
| readbuf[i+3], readbuf[i+4], |
| readbuf[i+5], readbuf[i+6], |
| readbuf[i+7], readbuf[i+8], |
| readbuf[i+9], readbuf[i+10], |
| readbuf[i+11], readbuf[i+12], |
| readbuf[i+13], readbuf[i+14], |
| readbuf[i+15]); |
| write(ofd, pretty_buf, 60); |
| } |
| } else |
| write(ofd, readbuf, bs); |
| |
| |
| |
| if (omitoob) |
| continue; |
| |
| if (badblock) { |
| memset (readbuf, 0xff, meminfo.oobsize); |
| } else { |
| /* Read OOB data and exit on failure */ |
| oob.start = ofs; |
| if (ioctl(fd, MEMREADOOB, &oob) != 0) { |
| perror("ioctl(MEMREADOOB)"); |
| goto closeall; |
| } |
| } |
| |
| /* Write out OOB data */ |
| if (pretty_print) { |
| if (meminfo.oobsize < 16) { |
| sprintf(pretty_buf, " OOB Data: %02x %02x %02x %02x %02x %02x " |
| "%02x %02x\n", |
| oobbuf[0], oobbuf[1], oobbuf[2], |
| oobbuf[3], oobbuf[4], oobbuf[5], |
| oobbuf[6], oobbuf[7]); |
| write(ofd, pretty_buf, 48); |
| continue; |
| } |
| |
| for (i = 0; i < meminfo.oobsize; i += 16) { |
| sprintf(pretty_buf, " OOB Data: %02x %02x %02x %02x %02x %02x " |
| "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", |
| oobbuf[i], oobbuf[i+1], oobbuf[i+2], |
| oobbuf[i+3], oobbuf[i+4], oobbuf[i+5], |
| oobbuf[i+6], oobbuf[i+7], oobbuf[i+8], |
| oobbuf[i+9], oobbuf[i+10], oobbuf[i+11], |
| oobbuf[i+12], oobbuf[i+13], oobbuf[i+14], |
| oobbuf[i+15]); |
| write(ofd, pretty_buf, 60); |
| } |
| } else |
| write(ofd, oobbuf, meminfo.oobsize); |
| } |
| |
| /* reset oobinfo */ |
| if (oobinfochanged == 1) { |
| if (ioctl (fd, MEMSETOOBSEL, &old_oobinfo) != 0) { |
| perror ("MEMSETOOBSEL"); |
| goto closeall; |
| } |
| } |
| /* Close the output file and MTD device, free memory */ |
| close(fd); |
| close(ofd); |
| free(oobbuf); |
| free(readbuf); |
| |
| /* Exit happy */ |
| return EXIT_SUCCESS; |
| |
| closeall: |
| /* The new mode change is per file descriptor ! */ |
| if (oobinfochanged == 1) { |
| if (ioctl (fd, MEMSETOOBSEL, &old_oobinfo) != 0) { |
| perror ("MEMSETOOBSEL"); |
| } |
| } |
| close(fd); |
| close(ofd); |
| free(oobbuf); |
| free(readbuf); |
| exit(EXIT_FAILURE); |
| } |