diff options
Diffstat (limited to 'nand-utils/nandwrite.c')
-rw-r--r-- | nand-utils/nandwrite.c | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/nand-utils/nandwrite.c b/nand-utils/nandwrite.c new file mode 100644 index 0000000..9c3fe8f --- /dev/null +++ b/nand-utils/nandwrite.c @@ -0,0 +1,578 @@ +/* + * 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; +} |