| /* |
| * 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 _GNU_SOURCE |
| #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" |
| |
| #define PROGRAM "nandwrite" |
| #define VERSION "$Revision: 1.32 $" |
| |
| #define MAX_PAGE_SIZE 4096 |
| #define MAX_OOB_SIZE 128 |
| |
| // oob layouts to pass into the kernel as default |
| static struct nand_oobinfo none_oobinfo = { |
| .useecc = MTD_NANDECC_OFF, |
| }; |
| |
| static struct nand_oobinfo jffs2_oobinfo = { |
| .useecc = MTD_NANDECC_PLACE, |
| .eccbytes = 6, |
| .eccpos = { 0, 1, 2, 3, 6, 7 } |
| }; |
| |
| static struct nand_oobinfo yaffs_oobinfo = { |
| .useecc = MTD_NANDECC_PLACE, |
| .eccbytes = 6, |
| .eccpos = { 8, 9, 10, 13, 14, 15} |
| }; |
| |
| static struct nand_oobinfo autoplace_oobinfo = { |
| .useecc = MTD_NANDECC_AUTOPLACE |
| }; |
| |
| static void display_help (void) |
| { |
| printf( |
| "Usage: nandwrite [OPTION] MTD_DEVICE [INPUTFILE|-]\n" |
| "Writes to the specified MTD device.\n" |
| "\n" |
| " -a, --autoplace Use auto oob layout\n" |
| " -j, --jffs2 Force jffs2 oob layout (legacy support)\n" |
| " -y, --yaffs Force yaffs oob layout (legacy support)\n" |
| " -f, --forcelegacy Force legacy support on autoplacement-enabled mtd\n" |
| " device\n" |
| " -m, --markbad Mark blocks bad if write fails\n" |
| " -n, --noecc Write without ecc\n" |
| " -o, --oob Image contains oob data\n" |
| " -s addr, --start=addr Set start address (default is 0)\n" |
| " -p, --pad Pad to page size\n" |
| " -b, --blockalign=1|2|4 Set multiple of eraseblocks to align to\n" |
| " -q, --quiet Don't display progress messages\n" |
| " --help Display this help and exit\n" |
| " --version Output version information and exit\n" |
| ); |
| exit (EXIT_SUCCESS); |
| } |
| |
| static void display_version (void) |
| { |
| printf(PROGRAM " " VERSION "\n" |
| "\n" |
| "Copyright (C) 2003 Thomas Gleixner \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); |
| } |
| |
| static const char *standard_input = "-"; |
| static const char *mtd_device, *img; |
| static int mtdoffset = 0; |
| static bool quiet = false; |
| static bool writeoob = false; |
| static bool autoplace = false; |
| static bool markbad = false; |
| static bool forcejffs2 = false; |
| static bool forceyaffs = false; |
| static bool forcelegacy = false; |
| static bool noecc = false; |
| static bool pad = false; |
| static int blockalign = 1; /*default to using 16K block size */ |
| |
| static void process_options (int argc, char * const argv[]) |
| { |
| int error = 0; |
| |
| for (;;) { |
| int option_index = 0; |
| static const char *short_options = "ab:fjmnopqs:y"; |
| static const struct option long_options[] = { |
| {"help", no_argument, 0, 0}, |
| {"version", no_argument, 0, 0}, |
| {"autoplace", no_argument, 0, 'a'}, |
| {"blockalign", required_argument, 0, 'b'}, |
| {"forcelegacy", no_argument, 0, 'f'}, |
| {"jffs2", no_argument, 0, 'j'}, |
| {"markbad", no_argument, 0, 'm'}, |
| {"noecc", no_argument, 0, 'n'}, |
| {"oob", no_argument, 0, 'o'}, |
| {"pad", no_argument, 0, 'p'}, |
| {"quiet", no_argument, 0, 'q'}, |
| {"start", required_argument, 0, 's'}, |
| {"yaffs", no_argument, 0, 'y'}, |
| {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 'q': |
| quiet = true; |
| break; |
| case 'a': |
| autoplace = true; |
| break; |
| case 'j': |
| forcejffs2 = true; |
| break; |
| case 'y': |
| forceyaffs = true; |
| break; |
| case 'f': |
| forcelegacy = true; |
| break; |
| case 'n': |
| noecc = true; |
| break; |
| case 'm': |
| markbad = true; |
| break; |
| case 'o': |
| writeoob = true; |
| break; |
| case 'p': |
| pad = true; |
| break; |
| case 's': |
| mtdoffset = strtol (optarg, NULL, 0); |
| break; |
| case 'b': |
| blockalign = atoi (optarg); |
| break; |
| case '?': |
| error++; |
| break; |
| } |
| } |
| |
| if (mtdoffset < 0) { |
| fprintf(stderr, "Can't specify a negative device offset `%d'\n", |
| mtdoffset); |
| exit (EXIT_FAILURE); |
| } |
| |
| 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 (); |
| |
| 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 cnt = 0; |
| int fd = -1; |
| int ifd = -1; |
| int imglen = 0, pagelen; |
| bool baderaseblock = false; |
| int blockstart = -1; |
| struct mtd_info_user meminfo; |
| struct mtd_oob_buf oob; |
| loff_t offs; |
| int ret; |
| int oobinfochanged = 0; |
| struct nand_oobinfo old_oobinfo; |
| 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 *oobreadbuf = NULL; |
| unsigned char oobbuf[MAX_OOB_SIZE]; |
| |
| process_options(argc, argv); |
| |
| erase_buffer(oobbuf, sizeof(oobbuf)); |
| |
| if (pad && writeoob) { |
| fprintf(stderr, "Can't pad when oob data is present.\n"); |
| exit (EXIT_FAILURE); |
| } |
| |
| /* Open the device */ |
| if ((fd = open(mtd_device, O_RDWR)) == -1) { |
| perror(mtd_device); |
| exit (EXIT_FAILURE); |
| } |
| |
| /* Fill in MTD device capability structure */ |
| if (ioctl(fd, MEMGETINFO, &meminfo) != 0) { |
| perror("MEMGETINFO"); |
| close(fd); |
| exit (EXIT_FAILURE); |
| } |
| |
| /* Set erasesize to specified number of blocks - to match jffs2 |
| * (virtual) block size */ |
| meminfo.erasesize *= blockalign; |
| |
| /* Make sure device page sizes are valid */ |
| if (!(meminfo.oobsize == 16 && meminfo.writesize == 512) && |
| !(meminfo.oobsize == 8 && meminfo.writesize == 256) && |
| !(meminfo.oobsize == 64 && meminfo.writesize == 2048) && |
| !(meminfo.oobsize == 128 && meminfo.writesize == 4096)) { |
| fprintf(stderr, "Unknown flash (not normal NAND)\n"); |
| close(fd); |
| exit (EXIT_FAILURE); |
| } |
| |
| if (autoplace) { |
| /* Read the current oob info */ |
| if (ioctl (fd, MEMGETOOBSEL, &old_oobinfo) != 0) { |
| perror ("MEMGETOOBSEL"); |
| close (fd); |
| exit (EXIT_FAILURE); |
| } |
| |
| // autoplace ECC ? |
| if (autoplace && (old_oobinfo.useecc != MTD_NANDECC_AUTOPLACE)) { |
| |
| if (ioctl (fd, MEMSETOOBSEL, &autoplace_oobinfo) != 0) { |
| perror ("MEMSETOOBSEL"); |
| close (fd); |
| exit (EXIT_FAILURE); |
| } |
| oobinfochanged = 1; |
| } |
| } |
| |
| 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"); |
| close (fd); |
| exit (EXIT_FAILURE); |
| } |
| if (ioctl (fd, MEMSETOOBSEL, &none_oobinfo) != 0) { |
| perror ("MEMSETOOBSEL"); |
| close (fd); |
| exit (EXIT_FAILURE); |
| } |
| oobinfochanged = 1; |
| break; |
| default: |
| perror ("MTDFILEMODE"); |
| close (fd); |
| exit (EXIT_FAILURE); |
| } |
| } |
| } |
| |
| /* |
| * force oob layout for jffs2 or yaffs ? |
| * Legacy support |
| */ |
| if (forcejffs2 || forceyaffs) { |
| struct nand_oobinfo *oobsel = forcejffs2 ? &jffs2_oobinfo : &yaffs_oobinfo; |
| |
| if (autoplace) { |
| fprintf(stderr, "Autoplacement is not possible for legacy -j/-y options\n"); |
| goto restoreoob; |
| } |
| if ((old_oobinfo.useecc == MTD_NANDECC_AUTOPLACE) && !forcelegacy) { |
| fprintf(stderr, "Use -f option to enforce legacy placement on autoplacement enabled mtd device\n"); |
| goto restoreoob; |
| } |
| if (meminfo.oobsize == 8) { |
| if (forceyaffs) { |
| fprintf (stderr, "YAFSS cannot operate on 256 Byte page size"); |
| goto restoreoob; |
| } |
| /* Adjust number of ecc bytes */ |
| jffs2_oobinfo.eccbytes = 3; |
| } |
| |
| if (ioctl (fd, MEMSETOOBSEL, oobsel) != 0) { |
| perror ("MEMSETOOBSEL"); |
| goto restoreoob; |
| } |
| } |
| |
| oob.length = meminfo.oobsize; |
| oob.ptr = noecc ? oobreadbuf : oobbuf; |
| |
| /* 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 restoreoob; |
| } |
| |
| pagelen = meminfo.writesize + ((writeoob) ? meminfo.oobsize : 0); |
| |
| /* |
| * For the standard input case, the input size is merely an |
| * invariant placeholder and is set to the write page |
| * size. Otherwise, just use the input file size. |
| * |
| * TODO: Add support for the -l,--length=length option (see |
| * previous discussion by Tommi Airikka <tommi.airikka@ericsson.com> at |
| * <http://lists.infradead.org/pipermail/linux-mtd/2008-September/ |
| * 022913.html> |
| */ |
| |
| if (ifd == STDIN_FILENO) { |
| imglen = pagelen; |
| } else { |
| imglen = lseek(ifd, 0, SEEK_END); |
| lseek (ifd, 0, SEEK_SET); |
| } |
| |
| // 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) * meminfo.writesize) > (meminfo.size - mtdoffset)) { |
| fprintf (stderr, "Image %d bytes, NAND page %d bytes, OOB area %u bytes, device size %u bytes\n", |
| imglen, pagelen, meminfo.writesize, meminfo.size); |
| perror ("Input file does not fit into device"); |
| goto closeall; |
| } |
| |
| // Allocate a buffer big enough to contain all the data (OOB included) for one eraseblock |
| filebuf_max = pagelen * meminfo.erasesize / meminfo.writesize; |
| filebuf = (unsigned char*)malloc(filebuf_max); |
| if (!filebuf) { |
| fprintf(stderr, "Failed to allocate memory for file buffer (%d bytes)\n", |
| pagelen * meminfo.erasesize / meminfo.writesize); |
| goto closeall; |
| } |
| 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 < meminfo.size)) |
| { |
| // new eraseblock , check for bad block(s) |
| // Stay in the loop to be sure if the mtdoffset changes because |
| // of a bad block, that the next block that will be written to |
| // is also checked. Thus avoiding errors if the block(s) after the |
| // skipped block(s) is also bad (number of blocks depending on |
| // the blockalign |
| while (blockstart != (mtdoffset & (~meminfo.erasesize + 1))) { |
| blockstart = mtdoffset & (~meminfo.erasesize + 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 %d at offset 0x%x\n", |
| blockstart / meminfo.erasesize, blockstart); |
| |
| /* Check all the blocks in an erase block for bad blocks */ |
| do { |
| if ((ret = ioctl(fd, MEMGETBADBLOCK, &offs)) < 0) { |
| perror("ioctl(MEMGETBADBLOCK)"); |
| goto closeall; |
| } |
| if (ret == 1) { |
| baderaseblock = true; |
| if (!quiet) |
| fprintf (stderr, "Bad block at %x, %u block(s) " |
| "from %x will be skipped\n", |
| (int) offs, blockalign, blockstart); |
| } |
| |
| if (baderaseblock) { |
| mtdoffset = blockstart + meminfo.erasesize; |
| if (mtdoffset >= meminfo.size) { |
| goto closeall; |
| } |
| } |
| offs += meminfo.erasesize / blockalign ; |
| } while ( offs < blockstart + meminfo.erasesize ); |
| |
| } |
| |
| // Read more data from the input if there isn't enough in the buffer |
| if ((writebuf + meminfo.writesize) > (filebuf + filebuf_len)) { |
| int readlen = meminfo.writesize; |
| |
| int alreadyread = (filebuf + filebuf_len) - writebuf; |
| int tinycnt = alreadyread; |
| |
| 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 the imglen to 0 to signal |
| // the end of the "file". For non standard 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 " |
| "%d 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) { |
| oobreadbuf = writebuf + meminfo.writesize; |
| |
| // Read more data for the OOB from the input if there isn't enough in the buffer |
| if ((oobreadbuf + meminfo.oobsize) > (filebuf + filebuf_len)) { |
| int readlen = meminfo.oobsize; |
| |
| int alreadyread = (filebuf + filebuf_len) - oobreadbuf; |
| int tinycnt = alreadyread; |
| |
| while (tinycnt < readlen) { |
| cnt = read(ifd, oobreadbuf + 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 " |
| "%d 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; |
| } |
| } |
| |
| if (noecc) { |
| oob.ptr = oobreadbuf; |
| } else { |
| int i, start, len; |
| /* |
| * We use autoplacement and have the oobinfo with the autoplacement |
| * information from the kernel available |
| * |
| * Modified to support out of order oobfree segments, |
| * such as the layout used by diskonchip.c |
| */ |
| if (!oobinfochanged && (old_oobinfo.useecc == MTD_NANDECC_AUTOPLACE)) { |
| for (i = 0;old_oobinfo.oobfree[i][1]; i++) { |
| /* Set the reserved bytes to 0xff */ |
| start = old_oobinfo.oobfree[i][0]; |
| len = old_oobinfo.oobfree[i][1]; |
| memcpy(oobbuf + start, |
| oobreadbuf + start, |
| len); |
| } |
| } else { |
| /* Set at least the ecc byte positions to 0xff */ |
| start = old_oobinfo.eccbytes; |
| len = meminfo.oobsize - start; |
| memcpy(oobbuf + start, |
| oobreadbuf + start, |
| len); |
| } |
| } |
| /* Write OOB data first, as ecc will be placed in there*/ |
| oob.start = mtdoffset; |
| if (ioctl(fd, MEMWRITEOOB, &oob) != 0) { |
| perror ("ioctl(MEMWRITEOOB)"); |
| goto closeall; |
| } |
| } |
| |
| /* Write out the Page data */ |
| if (pwrite(fd, writebuf, meminfo.writesize, mtdoffset) != meminfo.writesize) { |
| erase_info_t erase; |
| |
| if (errno != EIO) { |
| perror("pwrite"); |
| goto closeall; |
| } |
| |
| /* Must rewind to blockstart if we can */ |
| writebuf = filebuf; |
| |
| erase.start = blockstart; |
| erase.length = meminfo.erasesize; |
| fprintf(stderr, "Erasing failed write from %08lx-%08lx\n", |
| (long)erase.start, (long)erase.start+erase.length-1); |
| if (ioctl(fd, MEMERASE, &erase) != 0) { |
| int errno_tmp = errno; |
| perror("MEMERASE"); |
| if (errno_tmp != EIO) { |
| goto closeall; |
| } |
| } |
| |
| if (markbad) { |
| loff_t bad_addr = mtdoffset & (~(meminfo.erasesize / blockalign) + 1); |
| fprintf(stderr, "Marking block at %08lx bad\n", (long)bad_addr); |
| if (ioctl(fd, MEMSETBADBLOCK, &bad_addr)) { |
| perror("MEMSETBADBLOCK"); |
| goto closeall; |
| } |
| } |
| mtdoffset = blockstart + meminfo.erasesize; |
| |
| continue; |
| } |
| mtdoffset += meminfo.writesize; |
| writebuf += pagelen; |
| } |
| |
| failed = false; |
| |
| closeall: |
| if (filebuf) { |
| free(filebuf); |
| } |
| |
| close(ifd); |
| |
| restoreoob: |
| if (oobinfochanged == 1) { |
| if (ioctl (fd, MEMSETOOBSEL, &old_oobinfo) != 0) { |
| perror ("MEMSETOOBSEL"); |
| close (fd); |
| exit (EXIT_FAILURE); |
| } |
| } |
| |
| close(fd); |
| |
| if (failed |
| || ((ifd != STDIN_FILENO) && (imglen > 0)) |
| || (writebuf < (filebuf + filebuf_len))) |
| { |
| perror ("Data was only partially written due to error\n"); |
| exit (EXIT_FAILURE); |
| } |
| |
| /* Return happy */ |
| return EXIT_SUCCESS; |
| } |