| /* |
| * Copyright (c) International Business Machines Corp., 2006, 2007 |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| * |
| * Author: Frank Haverkamp |
| * |
| * An utility to decompose NAND images and strip OOB off. Not yet finished ... |
| * |
| * 1.2 Removed argp because we want to use uClibc. |
| * 1.3 Minor cleanup |
| * 1.4 Fixed OOB output file |
| * 1.5 Added verbose output and option to set blocksize. |
| * Added split block mode for more convenient analysis. |
| * 1.6 Fixed ECC error detection and correction. |
| */ |
| |
| #include <config.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <getopt.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include "config.h" |
| #include "nandecc.h" |
| |
| #define PROGRAM_VERSION "1.6" |
| |
| #define MAXPATH 1024 |
| #define MIN(x,y) ((x)<(y)?(x):(y)) |
| |
| struct args { |
| const char *oob_file; |
| const char *output_file; |
| size_t pagesize; |
| size_t blocksize; |
| int split_blocks; |
| size_t in_len; /* size of input file */ |
| int correct_ecc; |
| |
| /* special stuff needed to get additional arguments */ |
| char *arg1; |
| char **options; /* [STRING...] */ |
| }; |
| |
| static struct args myargs = { |
| .output_file = "data.bin", |
| .oob_file = "oob.bin", |
| .pagesize = 2048, |
| .blocksize = 128 * 1024, |
| .in_len = 0, |
| .split_blocks = 0, |
| .correct_ecc = 0, |
| .arg1 = NULL, |
| .options = NULL, |
| }; |
| |
| static char doc[] = "\nVersion: " PROGRAM_VERSION "\n" |
| "nand2bin - split data and OOB.\n"; |
| |
| static const char *optionsstr = |
| " -o, --output=<output> Data output file\n" |
| " -O, --oob=<oob> OOB output file\n" |
| " -p, --pagesize=<pagesize> NAND pagesize\n" |
| " -b, --blocksize=<blocksize> NAND blocksize\n" |
| " -s, --split-blocks generate binaries for each block\n" |
| " -e, --correct-ecc Correct data according to ECC info\n" |
| " -v, --verbose verbose output\n" |
| " -?, --help Give this help list\n" |
| " --usage Give a short usage message\n"; |
| |
| static const char *usage = |
| "Usage: nand2bin [-?] [-o <output>] [-O <oob>] [-p <pagesize>]\n" |
| " [--output=<output>] [--oob=<oob>] [--pagesize=<pagesize>] [--help]\n" |
| " [--usage] input.mif\n"; |
| |
| static int verbose = 0; |
| |
| static struct option long_options[] = { |
| { .name = "output", .has_arg = 1, .flag = NULL, .val = 'o' }, |
| { .name = "oob", .has_arg = 1, .flag = NULL, .val = 'O' }, |
| { .name = "pagesize", .has_arg = 1, .flag = NULL, .val = 'p' }, |
| { .name = "blocksize", .has_arg = 1, .flag = NULL, .val = 'b' }, |
| { .name = "split-blocks", .has_arg = 0, .flag = NULL, .val = 's' }, |
| { .name = "correct-ecc", .has_arg = 0, .flag = NULL, .val = 'e' }, |
| { .name = "verbose", .has_arg = 0, .flag = NULL, .val = 'v' }, |
| { .name = "help", .has_arg = 0, .flag = NULL, .val = '?' }, |
| { .name = "usage", .has_arg = 0, .flag = NULL, .val = 0 }, |
| { NULL, 0, NULL, 0} |
| }; |
| |
| /* |
| * str_to_num - Convert string into number and cope with endings like |
| * k, K, kib, KiB for kilobyte |
| * m, M, mib, MiB for megabyte |
| */ |
| static uint32_t str_to_num(char *str) |
| { |
| char *s = str; |
| ulong num = strtoul(s, &s, 0); |
| |
| if (*s != '\0') { |
| if (strcmp(s, "KiB") == 0) |
| num *= 1024; |
| else if (strcmp(s, "MiB") == 0) |
| num *= 1024*1024; |
| else { |
| fprintf(stderr, "WARNING: Wrong number format " |
| "\"%s\", check your paramters!\n", str); |
| } |
| } |
| return num; |
| } |
| |
| /* |
| * @brief Parse the arguments passed into the test case. |
| * |
| * @param argc The number of arguments |
| * @param argv The argument list |
| * @param args Pointer to program args structure |
| * |
| * @return error |
| * |
| */ |
| static int parse_opt(int argc, char **argv, struct args *args) |
| { |
| while (1) { |
| int key; |
| |
| key = getopt_long(argc, argv, "b:eo:O:p:sv?", long_options, NULL); |
| if (key == -1) |
| break; |
| |
| switch (key) { |
| case 'p': /* --pagesize<pagesize> */ |
| args->pagesize = str_to_num(optarg); |
| break; |
| |
| case 'b': /* --blocksize<blocksize> */ |
| args->blocksize = str_to_num(optarg); |
| break; |
| |
| case 'v': /* --verbose */ |
| verbose++; |
| break; |
| |
| case 's': /* --split-blocks */ |
| args->split_blocks = 1; |
| break; |
| |
| case 'e': /* --correct-ecc */ |
| args->correct_ecc = 1; |
| break; |
| |
| case 'o': /* --output=<output.bin> */ |
| args->output_file = optarg; |
| break; |
| |
| case 'O': /* --oob=<oob.bin> */ |
| args->oob_file = optarg; |
| break; |
| |
| case '?': /* help */ |
| printf("Usage: nand2bin [OPTION...] input.mif\n"); |
| printf("%s", doc); |
| printf("%s", optionsstr); |
| printf("\nReport bugs to %s\n", |
| PACKAGE_BUGREPORT); |
| exit(0); |
| break; |
| |
| case 'V': |
| printf("%s\n", PROGRAM_VERSION); |
| exit(0); |
| break; |
| |
| default: |
| printf("%s", usage); |
| exit(-1); |
| } |
| } |
| |
| if (optind < argc) |
| args->arg1 = argv[optind++]; |
| |
| return 0; |
| } |
| |
| static int calc_oobsize(size_t pagesize) |
| { |
| switch (pagesize) { |
| case 512: return 16; |
| case 2048: return 64; |
| default: |
| exit(EXIT_FAILURE); |
| } |
| return 0; |
| } |
| |
| static inline void hexdump(FILE *fp, const uint8_t *buf, ssize_t size) |
| { |
| int k; |
| |
| for (k = 0; k < size; k++) { |
| fprintf(fp, "%02x ", buf[k]); |
| if ((k & 15) == 15) |
| fprintf(fp, "\n"); |
| } |
| } |
| |
| static int process_page(uint8_t* buf, uint8_t *oobbuf, size_t pagesize) |
| { |
| int eccpoi, oobsize; |
| size_t i; |
| |
| switch (pagesize) { |
| case 2048: oobsize = 64; eccpoi = 64 / 2; break; |
| case 512: oobsize = 16; eccpoi = 16 / 2; break; |
| default: |
| fprintf(stderr, "Unsupported page size: %zd\n", pagesize); |
| return -EINVAL; |
| } |
| memset(oobbuf, 0xff, oobsize); |
| |
| for (i = 0; i < pagesize; i += 256, eccpoi += 3) { |
| oobbuf[eccpoi++] = 0x0; |
| /* Calculate ECC */ |
| nand_calculate_ecc(&buf[i], &oobbuf[eccpoi]); |
| } |
| return 0; |
| } |
| |
| static int bad_marker_offs_in_oob(int pagesize) |
| { |
| switch (pagesize) { |
| case 2048: return 0; |
| case 512: return 5; |
| } |
| return -EINVAL; |
| } |
| |
| static int decompose_image(struct args *args, FILE *in_fp, |
| FILE *bin_fp, FILE *oob_fp) |
| { |
| int read, rc, page = 0; |
| size_t oobsize = calc_oobsize(args->pagesize); |
| uint8_t *buf = malloc(args->pagesize); |
| uint8_t *oob = malloc(oobsize); |
| uint8_t *calc_oob = malloc(oobsize); |
| uint8_t *calc_buf = malloc(args->pagesize); |
| uint8_t *page_buf; |
| int pages_per_block = args->blocksize / args->pagesize; |
| int eccpoi = 0, eccpoi_start; |
| unsigned int i; |
| int badpos = bad_marker_offs_in_oob(args->pagesize); |
| |
| switch (args->pagesize) { |
| case 2048: eccpoi_start = 64 / 2; break; |
| case 512: eccpoi_start = 16 / 2; break; |
| default: exit(EXIT_FAILURE); |
| } |
| |
| if (!buf) |
| exit(EXIT_FAILURE); |
| if (!oob) |
| exit(EXIT_FAILURE); |
| if (!calc_oob) |
| exit(EXIT_FAILURE); |
| if (!calc_buf) |
| exit(EXIT_FAILURE); |
| |
| while (!feof(in_fp)) { |
| /* read page by page */ |
| read = fread(buf, 1, args->pagesize, in_fp); |
| if (ferror(in_fp)) { |
| fprintf(stderr, "I/O Error."); |
| exit(EXIT_FAILURE); |
| } |
| if (read != (ssize_t)args->pagesize) |
| break; |
| |
| read = fread(oob, 1, oobsize, in_fp); |
| if (ferror(in_fp)) { |
| fprintf(stderr, "I/O Error."); |
| exit(EXIT_FAILURE); |
| } |
| |
| page_buf = buf; /* default is unmodified data */ |
| |
| if ((page == 0 || page == 1) && (oob[badpos] != 0xff)) { |
| if (verbose) |
| printf("Block %d is bad\n", |
| page / pages_per_block); |
| goto write_data; |
| } |
| if (args->correct_ecc) |
| page_buf = calc_buf; |
| |
| process_page(buf, calc_oob, args->pagesize); |
| memcpy(calc_buf, buf, args->pagesize); |
| |
| /* |
| * Our oob format uses only the last 3 bytes out of 4. |
| * The first byte is 0x00 when the ECC is generated by |
| * our toolset and 0xff when generated by Linux. This |
| * is to be fixed when we want nand2bin work for other |
| * ECC layouts too. |
| */ |
| for (i = 0, eccpoi = eccpoi_start; i < args->pagesize; |
| i += 256, eccpoi += 4) |
| oob[eccpoi] = calc_oob[eccpoi] = 0xff; |
| |
| if (verbose && memcmp(oob, calc_oob, oobsize) != 0) { |
| printf("\nECC compare mismatch found at block %d page %d!\n", |
| page / pages_per_block, page % pages_per_block); |
| |
| printf("Read out OOB Data:\n"); |
| hexdump(stdout, oob, oobsize); |
| |
| printf("Calculated OOB Data:\n"); |
| hexdump(stdout, calc_oob, oobsize); |
| } |
| |
| /* Do correction on subpage base */ |
| for (i = 0, eccpoi = eccpoi_start; i < args->pagesize; |
| i += 256, eccpoi += 4) { |
| rc = nand_correct_data(calc_buf + i, &oob[eccpoi + 1], |
| &calc_oob[eccpoi + 1]); |
| |
| if (rc == -1) |
| fprintf(stdout, "Uncorrectable ECC error at " |
| "block %d page %d/%d\n", |
| page / pages_per_block, |
| page % pages_per_block, i / 256); |
| else if (rc > 0) |
| fprintf(stdout, "Correctable ECC error at " |
| "block %d page %d/%d\n", |
| page / pages_per_block, |
| page % pages_per_block, i / 256); |
| } |
| |
| write_data: |
| rc = fwrite(page_buf, 1, args->pagesize, bin_fp); |
| if (ferror(bin_fp)) { |
| fprintf(stderr, "I/O Error."); |
| exit(EXIT_FAILURE); |
| } |
| rc = fwrite(oob, 1, oobsize, oob_fp); |
| if (ferror(bin_fp)) { |
| fprintf(stderr, "I/O Error."); |
| exit(EXIT_FAILURE); |
| } |
| |
| page++; |
| } |
| free(calc_buf); |
| free(calc_oob); |
| free(oob); |
| free(buf); |
| return 0; |
| } |
| |
| static int split_blocks(struct args *args, FILE *in_fp) |
| { |
| uint8_t *buf; |
| size_t oobsize = calc_oobsize(args->pagesize); |
| int pages_per_block = args->blocksize / args->pagesize; |
| int block_len = pages_per_block * (args->pagesize + oobsize); |
| int blocks = args->in_len / block_len; |
| char bname[256] = { 0, }; |
| int badpos = bad_marker_offs_in_oob(args->pagesize); |
| int bad_blocks = 0, i, bad_block = 0; |
| ssize_t rc; |
| FILE *b; |
| |
| buf = malloc(block_len); |
| if (!buf) { |
| perror("Not enough memory"); |
| exit(EXIT_FAILURE); |
| } |
| |
| for (i = 0; i < blocks; i++) { |
| rc = fread(buf, 1, block_len, in_fp); |
| if (rc != block_len) { |
| fprintf(stderr, "cannot read enough data!\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| /* do block analysis */ |
| bad_block = 0; |
| if ((buf[args->pagesize + badpos] != 0xff) || |
| (buf[2 * args->pagesize + oobsize + badpos] != 0xff)) { |
| bad_blocks++; |
| bad_block = 1; |
| } |
| if ((verbose && bad_block) || (verbose > 1)) { |
| printf("-- (block %d oob of page 0 and 1)\n", i); |
| hexdump(stdout, buf + args->pagesize, oobsize); |
| printf("--\n"); |
| hexdump(stdout, buf + 2 * args->pagesize + |
| oobsize, oobsize); |
| } |
| |
| /* write complete block out */ |
| snprintf(bname, sizeof(bname) - 1, "%s.%d", args->arg1, i); |
| b = fopen(bname, "w+"); |
| if (!b) { |
| perror("Cannot open file"); |
| exit(EXIT_FAILURE); |
| } |
| rc = fwrite(buf, 1, block_len, b); |
| if (rc != block_len) { |
| fprintf(stderr, "could not write all data!\n"); |
| exit(EXIT_FAILURE); |
| } |
| fclose(b); |
| } |
| |
| free(buf); |
| if (bad_blocks || verbose) |
| fprintf(stderr, "%d blocks, %d bad blocks\n", |
| blocks, bad_blocks); |
| return 0; |
| } |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| FILE *in, *bin = NULL, *oob = NULL; |
| struct stat file_info; |
| |
| parse_opt(argc, argv, &myargs); |
| |
| if (!myargs.arg1) { |
| fprintf(stderr, "Please specify input file!\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (stat(myargs.arg1, &file_info) != 0) { |
| perror("Cannot fetch file size from input file.\n"); |
| exit(EXIT_FAILURE); |
| } |
| myargs.in_len = file_info.st_size; |
| |
| in = fopen(myargs.arg1, "r"); |
| if (!in) { |
| perror("Cannot open file"); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (myargs.split_blocks) { |
| split_blocks(&myargs, in); |
| goto out; |
| } |
| |
| bin = fopen(myargs.output_file, "w+"); |
| if (!bin) { |
| perror("Cannot open file"); |
| exit(EXIT_FAILURE); |
| } |
| oob = fopen(myargs.oob_file, "w+"); |
| if (!oob) { |
| perror("Cannot open file"); |
| exit(EXIT_FAILURE); |
| } |
| decompose_image(&myargs, in, bin, oob); |
| |
| out: |
| if (in) fclose(in); |
| if (bin) fclose(bin); |
| if (oob) fclose(oob); |
| exit(EXIT_SUCCESS); |
| } |