| /* |
| * nandwrite.c |
| * |
| * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) |
| * 2003 Thomas Gleixner (tglx@linutronix.de) |
| * |
| * 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 writes a binary image directly to a NAND flash |
| * chip or NAND chips contained in DoC devices. This is the |
| * "inverse operation" of nanddump. |
| * |
| * tglx: Major rewrite to handle bad blocks, write data with or without ECC |
| * write oob data only on request |
| * |
| * Bug/ToDo: |
| */ |
| |
| #define PROGRAM_NAME "nandwrite" |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <sys/stat.h> |
| #include <sys/ioctl.h> |
| #include <sys/types.h> |
| #include <getopt.h> |
| |
| #include <asm/types.h> |
| #include "mtd/mtd-user.h" |
| #include "common.h" |
| #include <libmtd.h> |
| |
| static void display_help(int status) |
| { |
| fprintf(status == EXIT_SUCCESS ? stdout : stderr, |
| "Usage: nandwrite [OPTION] MTD_DEVICE [INPUTFILE|-]\n" |
| "Writes to the specified MTD device.\n" |
| "\n" |
| " -a, --autoplace Use auto OOB layout\n" |
| " -m, --markbad Mark blocks bad if write fails\n" |
| " -n, --noecc Write without ecc\n" |
| " -N, --noskipbad Write without bad block skipping\n" |
| " -o, --oob Input contains oob data\n" |
| " -O, --onlyoob Input contains oob data and only write the oob part\n" |
| " -s addr, --start=addr Set output start address (default is 0)\n" |
| " -p, --pad Pad writes to page size\n" |
| " -b, --blockalign=1|2|4 Set multiple of eraseblocks to align to\n" |
| " --input-skip=length Skip |length| bytes of the input file\n" |
| " --input-size=length Only read |length| bytes of the input file\n" |
| " -q, --quiet Don't display progress messages\n" |
| " -h, --help Display this help and exit\n" |
| " --version Output version information and exit\n" |
| ); |
| exit(status); |
| } |
| |
| static void display_version(void) |
| { |
| printf("%1$s " VERSION "\n" |
| "\n" |
| "Copyright (C) 2003 Thomas Gleixner \n" |
| "\n" |
| "%1$s comes with NO WARRANTY\n" |
| "to the extent permitted by law.\n" |
| "\n" |
| "You may redistribute copies of %1$s\n" |
| "under the terms of the GNU General Public Licence.\n" |
| "See the file `COPYING' for more information.\n", |
| PROGRAM_NAME); |
| exit(EXIT_SUCCESS); |
| } |
| |
| static const char *standard_input = "-"; |
| static const char *mtd_device, *img; |
| static long long mtdoffset = 0; |
| static long long inputskip = 0; |
| static long long inputsize = 0; |
| static bool quiet = false; |
| static bool writeoob = false; |
| static bool onlyoob = false; |
| static bool markbad = false; |
| static bool noecc = false; |
| static bool autoplace = false; |
| static bool noskipbad = false; |
| static bool pad = false; |
| static int blockalign = 1; /* default to using actual block size */ |
| |
| static void process_options(int argc, char * const argv[]) |
| { |
| int error = 0; |
| |
| for (;;) { |
| int option_index = 0; |
| static const char short_options[] = "hb:mnNoOpqs:a"; |
| static const struct option long_options[] = { |
| /* Order of these args with val==0 matters; see option_index. */ |
| {"version", no_argument, 0, 0}, |
| {"input-skip", required_argument, 0, 0}, |
| {"input-size", required_argument, 0, 0}, |
| {"help", no_argument, 0, 'h'}, |
| {"blockalign", required_argument, 0, 'b'}, |
| {"markbad", no_argument, 0, 'm'}, |
| {"noecc", no_argument, 0, 'n'}, |
| {"noskipbad", no_argument, 0, 'N'}, |
| {"oob", no_argument, 0, 'o'}, |
| {"onlyoob", no_argument, 0, 'O'}, |
| {"pad", no_argument, 0, 'p'}, |
| {"quiet", no_argument, 0, 'q'}, |
| {"start", required_argument, 0, 's'}, |
| {"autoplace", no_argument, 0, 'a'}, |
| {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: /* --version */ |
| display_version(); |
| break; |
| case 1: /* --input-skip */ |
| inputskip = simple_strtoll(optarg, &error); |
| break; |
| case 2: /* --input-size */ |
| inputsize = simple_strtoll(optarg, &error); |
| break; |
| } |
| break; |
| case 'q': |
| quiet = true; |
| break; |
| case 'n': |
| noecc = true; |
| break; |
| case 'N': |
| noskipbad = true; |
| break; |
| case 'm': |
| markbad = true; |
| break; |
| case 'o': |
| writeoob = true; |
| break; |
| case 'O': |
| writeoob = true; |
| onlyoob = true; |
| break; |
| case 'p': |
| pad = true; |
| break; |
| case 's': |
| mtdoffset = simple_strtoll(optarg, &error); |
| break; |
| case 'b': |
| blockalign = atoi(optarg); |
| break; |
| case 'a': |
| autoplace = true; |
| break; |
| case 'h': |
| display_help(EXIT_SUCCESS); |
| break; |
| case '?': |
| error++; |
| break; |
| } |
| } |
| |
| if (mtdoffset < 0) |
| errmsg_die("Can't specify negative device offset with option" |
| " -s: %lld", mtdoffset); |
| |
| if (blockalign < 0) |
| errmsg_die("Can't specify negative blockalign with option -b:" |
| " %d", blockalign); |
| |
| if (autoplace && noecc) |
| errmsg_die("Autoplacement and no-ECC are mutually exclusive"); |
| |
| if (!onlyoob && (pad && writeoob)) |
| errmsg_die("Can't pad when oob data is present"); |
| |
| argc -= optind; |
| argv += optind; |
| |
| /* |
| * There must be at least the MTD device node positional |
| * argument remaining and, optionally, the input file. |
| */ |
| |
| if (argc < 1 || argc > 2 || error) |
| display_help(EXIT_FAILURE); |
| |
| mtd_device = argv[0]; |
| |
| /* |
| * Standard input may be specified either explictly as "-" or |
| * implicity by simply omitting the second of the two |
| * positional arguments. |
| */ |
| |
| img = ((argc == 2) ? argv[1] : standard_input); |
| } |
| |
| static void erase_buffer(void *buffer, size_t size) |
| { |
| const uint8_t kEraseByte = 0xff; |
| |
| if (buffer != NULL && size > 0) |
| memset(buffer, kEraseByte, size); |
| } |
| |
| /* |
| * Main program |
| */ |
| int main(int argc, char * const argv[]) |
| { |
| int fd = -1; |
| int ifd = -1; |
| int pagelen; |
| long long imglen = 0; |
| bool baderaseblock = false; |
| long long blockstart = -1; |
| struct mtd_dev_info mtd; |
| long long offs; |
| int ret; |
| bool failed = true; |
| /* contains all the data read from the file so far for the current eraseblock */ |
| unsigned char *filebuf = NULL; |
| size_t filebuf_max = 0; |
| size_t filebuf_len = 0; |
| /* points to the current page inside filebuf */ |
| unsigned char *writebuf = NULL; |
| /* points to the OOB for the current page in filebuf */ |
| unsigned char *oobbuf = NULL; |
| libmtd_t mtd_desc; |
| int ebsize_aligned; |
| uint8_t write_mode; |
| |
| process_options(argc, argv); |
| |
| /* Open the device */ |
| if ((fd = open(mtd_device, O_RDWR)) == -1) |
| sys_errmsg_die("%s", mtd_device); |
| |
| mtd_desc = libmtd_open(); |
| if (!mtd_desc) |
| errmsg_die("can't initialize libmtd"); |
| |
| /* Fill in MTD device capability structure */ |
| if (mtd_get_dev_info(mtd_desc, mtd_device, &mtd) < 0) |
| errmsg_die("mtd_get_dev_info failed"); |
| |
| /* |
| * Pretend erasesize is specified number of blocks - to match jffs2 |
| * (virtual) block size |
| * Use this value throughout unless otherwise necessary |
| */ |
| ebsize_aligned = mtd.eb_size * blockalign; |
| |
| if (mtdoffset & (mtd.min_io_size - 1)) |
| errmsg_die("The start address is not page-aligned !\n" |
| "The pagesize of this NAND Flash is 0x%x.\n", |
| mtd.min_io_size); |
| |
| /* Select OOB write mode */ |
| if (noecc) |
| write_mode = MTD_OPS_RAW; |
| else if (autoplace) |
| write_mode = MTD_OPS_AUTO_OOB; |
| else |
| write_mode = MTD_OPS_PLACE_OOB; |
| |
| if (noecc) { |
| ret = ioctl(fd, MTDFILEMODE, MTD_FILE_MODE_RAW); |
| if (ret) { |
| switch (errno) { |
| case ENOTTY: |
| errmsg_die("ioctl MTDFILEMODE is missing"); |
| default: |
| sys_errmsg_die("MTDFILEMODE"); |
| } |
| } |
| } |
| |
| /* Determine if we are reading from standard input or from a file. */ |
| if (strcmp(img, standard_input) == 0) |
| ifd = STDIN_FILENO; |
| else |
| ifd = open(img, O_RDONLY); |
| |
| if (ifd == -1) { |
| perror(img); |
| goto closeall; |
| } |
| |
| pagelen = mtd.min_io_size + ((writeoob) ? mtd.oob_size : 0); |
| |
| if (ifd == STDIN_FILENO) { |
| imglen = inputsize ? : pagelen; |
| if (inputskip) { |
| errmsg("seeking stdin not supported"); |
| goto closeall; |
| } |
| } else { |
| if (!inputsize) { |
| struct stat st; |
| if (fstat(ifd, &st)) { |
| sys_errmsg("unable to stat input image"); |
| goto closeall; |
| } |
| imglen = st.st_size - inputskip; |
| } else |
| imglen = inputsize; |
| |
| if (inputskip && lseek(ifd, inputskip, SEEK_CUR) == -1) { |
| sys_errmsg("lseek input by %lld failed", inputskip); |
| goto closeall; |
| } |
| } |
| |
| /* Check, if file is page-aligned */ |
| if (!pad && (imglen % pagelen) != 0) { |
| fprintf(stderr, "Input file is not page-aligned. Use the padding " |
| "option.\n"); |
| goto closeall; |
| } |
| |
| /* Check, if length fits into device */ |
| if ((imglen / pagelen) * mtd.min_io_size > mtd.size - mtdoffset) { |
| fprintf(stderr, "Image %lld bytes, NAND page %d bytes, OOB area %d" |
| " bytes, device size %lld bytes\n", |
| imglen, pagelen, mtd.oob_size, mtd.size); |
| sys_errmsg("Input file does not fit into device"); |
| goto closeall; |
| } |
| |
| /* |
| * Allocate a buffer big enough to contain all the data (OOB included) |
| * for one eraseblock. The order of operations here matters; if ebsize |
| * and pagelen are large enough, then "ebsize_aligned * pagelen" could |
| * overflow a 32-bit data type. |
| */ |
| filebuf_max = ebsize_aligned / mtd.min_io_size * pagelen; |
| filebuf = xmalloc(filebuf_max); |
| erase_buffer(filebuf, filebuf_max); |
| |
| /* |
| * Get data from input and write to the device while there is |
| * still input to read and we are still within the device |
| * bounds. Note that in the case of standard input, the input |
| * length is simply a quasi-boolean flag whose values are page |
| * length or zero. |
| */ |
| while ((imglen > 0 || writebuf < filebuf + filebuf_len) |
| && mtdoffset < mtd.size) { |
| /* |
| * New eraseblock, check for bad block(s) |
| * Stay in the loop to be sure that, if mtdoffset changes because |
| * of a bad block, the next block that will be written to |
| * is also checked. Thus, we avoid errors if the block(s) after the |
| * skipped block(s) is also bad (number of blocks depending on |
| * the blockalign). |
| */ |
| while (blockstart != (mtdoffset & (~ebsize_aligned + 1))) { |
| blockstart = mtdoffset & (~ebsize_aligned + 1); |
| offs = blockstart; |
| |
| /* |
| * if writebuf == filebuf, we are rewinding so we must |
| * not reset the buffer but just replay it |
| */ |
| if (writebuf != filebuf) { |
| erase_buffer(filebuf, filebuf_len); |
| filebuf_len = 0; |
| writebuf = filebuf; |
| } |
| |
| baderaseblock = false; |
| if (!quiet) |
| fprintf(stdout, "Writing data to block %lld at offset 0x%llx\n", |
| blockstart / ebsize_aligned, blockstart); |
| |
| /* Check all the blocks in an erase block for bad blocks */ |
| if (noskipbad) |
| continue; |
| |
| do { |
| ret = mtd_is_bad(&mtd, fd, offs / ebsize_aligned); |
| if (ret < 0) { |
| sys_errmsg("%s: MTD get bad block failed", mtd_device); |
| goto closeall; |
| } else if (ret == 1) { |
| baderaseblock = true; |
| if (!quiet) |
| fprintf(stderr, "Bad block at %llx, %u block(s) " |
| "from %llx will be skipped\n", |
| offs, blockalign, blockstart); |
| } |
| |
| if (baderaseblock) { |
| mtdoffset = blockstart + ebsize_aligned; |
| |
| if (mtdoffset > mtd.size) { |
| errmsg("too many bad blocks, cannot complete request"); |
| goto closeall; |
| } |
| } |
| |
| offs += ebsize_aligned / blockalign; |
| } while (offs < blockstart + ebsize_aligned); |
| |
| } |
| |
| /* Read more data from the input if there isn't enough in the buffer */ |
| if (writebuf + mtd.min_io_size > filebuf + filebuf_len) { |
| size_t readlen = mtd.min_io_size; |
| size_t alreadyread = (filebuf + filebuf_len) - writebuf; |
| size_t tinycnt = alreadyread; |
| ssize_t cnt = 0; |
| |
| while (tinycnt < readlen) { |
| cnt = read(ifd, writebuf + tinycnt, readlen - tinycnt); |
| if (cnt == 0) { /* EOF */ |
| break; |
| } else if (cnt < 0) { |
| perror("File I/O error on input"); |
| goto closeall; |
| } |
| tinycnt += cnt; |
| } |
| |
| /* No padding needed - we are done */ |
| if (tinycnt == 0) { |
| /* |
| * For standard input, set imglen to 0 to signal |
| * the end of the "file". For nonstandard input, |
| * leave it as-is to detect an early EOF. |
| */ |
| if (ifd == STDIN_FILENO) |
| imglen = 0; |
| |
| break; |
| } |
| |
| /* Padding */ |
| if (tinycnt < readlen) { |
| if (!pad) { |
| fprintf(stderr, "Unexpected EOF. Expecting at least " |
| "%zu more bytes. Use the padding option.\n", |
| readlen - tinycnt); |
| goto closeall; |
| } |
| erase_buffer(writebuf + tinycnt, readlen - tinycnt); |
| } |
| |
| filebuf_len += readlen - alreadyread; |
| if (ifd != STDIN_FILENO) { |
| imglen -= tinycnt - alreadyread; |
| } else if (cnt == 0) { |
| /* No more bytes - we are done after writing the remaining bytes */ |
| imglen = 0; |
| } |
| } |
| |
| if (writeoob) { |
| oobbuf = writebuf + mtd.min_io_size; |
| |
| /* Read more data for the OOB from the input if there isn't enough in the buffer */ |
| if (oobbuf + mtd.oob_size > filebuf + filebuf_len) { |
| size_t readlen = mtd.oob_size; |
| size_t alreadyread = (filebuf + filebuf_len) - oobbuf; |
| size_t tinycnt = alreadyread; |
| ssize_t cnt; |
| |
| while (tinycnt < readlen) { |
| cnt = read(ifd, oobbuf + tinycnt, readlen - tinycnt); |
| if (cnt == 0) { /* EOF */ |
| break; |
| } else if (cnt < 0) { |
| perror("File I/O error on input"); |
| goto closeall; |
| } |
| tinycnt += cnt; |
| } |
| |
| if (tinycnt < readlen) { |
| fprintf(stderr, "Unexpected EOF. Expecting at least " |
| "%zu more bytes for OOB\n", readlen - tinycnt); |
| goto closeall; |
| } |
| |
| filebuf_len += readlen - alreadyread; |
| if (ifd != STDIN_FILENO) { |
| imglen -= tinycnt - alreadyread; |
| } else if (cnt == 0) { |
| /* No more bytes - we are done after writing the remaining bytes */ |
| imglen = 0; |
| } |
| } |
| } |
| |
| /* Write out data */ |
| ret = mtd_write(mtd_desc, &mtd, fd, mtdoffset / mtd.eb_size, |
| mtdoffset % mtd.eb_size, |
| onlyoob ? NULL : writebuf, |
| onlyoob ? 0 : mtd.min_io_size, |
| writeoob ? oobbuf : NULL, |
| writeoob ? mtd.oob_size : 0, |
| write_mode); |
| if (ret) { |
| long long i; |
| if (errno != EIO) { |
| sys_errmsg("%s: MTD write failure", mtd_device); |
| goto closeall; |
| } |
| |
| /* Must rewind to blockstart if we can */ |
| writebuf = filebuf; |
| |
| fprintf(stderr, "Erasing failed write from %#08llx to %#08llx\n", |
| blockstart, blockstart + ebsize_aligned - 1); |
| for (i = blockstart; i < blockstart + ebsize_aligned; i += mtd.eb_size) { |
| if (mtd_erase(mtd_desc, &mtd, fd, i / mtd.eb_size)) { |
| int errno_tmp = errno; |
| sys_errmsg("%s: MTD Erase failure", mtd_device); |
| if (errno_tmp != EIO) |
| goto closeall; |
| } |
| } |
| |
| if (markbad) { |
| fprintf(stderr, "Marking block at %08llx bad\n", |
| mtdoffset & (~mtd.eb_size + 1)); |
| if (mtd_mark_bad(&mtd, fd, mtdoffset / mtd.eb_size)) { |
| sys_errmsg("%s: MTD Mark bad block failure", mtd_device); |
| goto closeall; |
| } |
| } |
| mtdoffset = blockstart + ebsize_aligned; |
| |
| continue; |
| } |
| mtdoffset += mtd.min_io_size; |
| writebuf += pagelen; |
| } |
| |
| failed = false; |
| |
| closeall: |
| close(ifd); |
| libmtd_close(mtd_desc); |
| free(filebuf); |
| close(fd); |
| |
| if (failed || (ifd != STDIN_FILENO && imglen > 0) |
| || (writebuf < filebuf + filebuf_len)) |
| sys_errmsg_die("Data was only partially written due to error"); |
| |
| /* Return happy */ |
| return EXIT_SUCCESS; |
| } |