nandwrite: always check the first erase block
[platform/upstream/busybox.git] / miscutils / nandwrite.c
1 /*
2  * nandwrite.c - ported to busybox from mtd-utils
3  *
4  * Author: Baruch Siach <baruch@tkos.co.il>, Orex Computed Radiography
5  *
6  * Licensed under GPLv2, see file LICENSE in this source tree.
7  *
8  * TODO: add support for large (>4GB) MTD devices
9  */
10
11 //applet:IF_NANDWRITE(APPLET(nandwrite, _BB_DIR_USR_SBIN, _BB_SUID_DROP))
12
13 //kbuild:lib-$(CONFIG_NANDWRITE) += nandwrite.o
14
15 //config:config NANDWRITE
16 //config:       bool "nandwrite"
17 //config:       default n
18 //config:       depends on PLATFORM_LINUX
19 //config:       help
20 //config:         Write to the specified MTD device, with bad blocks awareness
21
22 #include "libbb.h"
23 #include <mtd/mtd-user.h>
24
25 //usage:#define nandwrite_trivial_usage
26 //usage:        "[-p] [-s ADDR] MTD_DEVICE [FILE]"
27 //usage:#define nandwrite_full_usage "\n\n"
28 //usage:        "Write to the specified MTD device\n"
29 //usage:     "\nOptions:"
30 //usage:     "\n        -p      Pad to page size"
31 //usage:     "\n        -s ADDR Start address"
32
33 static unsigned next_good_eraseblock(int fd, struct mtd_info_user *meminfo,
34                 unsigned block_offset)
35 {
36         while (1) {
37                 loff_t offs;
38                 if (block_offset >= meminfo->size)
39                         bb_error_msg_and_die("not enough space in MTD device");
40                 offs = block_offset;
41                 if (xioctl(fd, MEMGETBADBLOCK, &offs) == 0)
42                         return block_offset;
43                 /* ioctl returned 1 => "bad block" */
44                 printf("Skipping bad block at 0x%08x\n", block_offset);
45                 block_offset += meminfo->erasesize;
46         }
47 }
48
49 int nandwrite_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
50 int nandwrite_main(int argc UNUSED_PARAM, char **argv)
51 {
52         unsigned opts;
53         int fd;
54         ssize_t cnt;
55         unsigned mtdoffset, meminfo_writesize, blockstart;
56         struct mtd_info_user meminfo;
57         unsigned char *filebuf;
58         const char *opt_s = "0";
59         enum {
60                 OPT_p = (1 << 0),
61                 OPT_s = (1 << 1),
62         };
63
64         opt_complementary = "-1:?2";
65         opts = getopt32(argv, "ps:", &opt_s);
66         argv += optind;
67
68         if (argv[1])
69                 xmove_fd(xopen_stdin(argv[1]), STDIN_FILENO);
70
71         fd = xopen(argv[0], O_RDWR);
72         xioctl(fd, MEMGETINFO, &meminfo);
73
74         mtdoffset = bb_strtou(opt_s, NULL, 0);
75         if (errno)
76                 bb_error_msg_and_die("invalid number '%s'", opt_s);
77
78         /* Pull it into a CPU register (hopefully) - smaller code that way */
79         meminfo_writesize = meminfo.writesize;
80
81         if (mtdoffset & (meminfo_writesize - 1))
82                 bb_error_msg_and_die("start address is not page aligned");
83
84         filebuf = xmalloc(meminfo_writesize);
85
86         blockstart = mtdoffset & ~(meminfo.erasesize - 1);
87         if (blockstart != mtdoffset) {
88                 unsigned tmp;
89                 /* mtdoffset is in the middle of an erase block, verify that
90                  * this block is OK. Advance mtdoffset only if this block is
91                  * bad.
92                  */
93                 tmp = next_good_eraseblock(fd, &meminfo, blockstart);
94                 if (tmp != blockstart) /* bad block(s), advance mtdoffset */
95                         mtdoffset = tmp;
96         }
97
98         cnt = -1;
99         while (mtdoffset < meminfo.size) {
100                 blockstart = mtdoffset & ~(meminfo.erasesize - 1);
101                 if (blockstart == mtdoffset) {
102                         /* starting a new eraseblock */
103                         mtdoffset = next_good_eraseblock(fd, &meminfo, blockstart);
104                         printf("Writing at 0x%08x\n", mtdoffset);
105                 }
106                 /* get some more data from input */
107                 cnt = full_read(STDIN_FILENO, filebuf, meminfo_writesize);
108                 if (cnt == 0) {
109                         /* even with -p, we do not pad past the end of input
110                          * (-p only zero-pads last incomplete page)
111                          */
112                         break;
113                 }
114                 if (cnt < meminfo_writesize) {
115                         if (!(opts & OPT_p))
116                                 bb_error_msg_and_die("input size is not rounded up to page size, "
117                                                 "use -p to zero pad");
118                         /* zero pad to end of write block */
119                         memset(filebuf + cnt, 0, meminfo_writesize - cnt);
120                 }
121                 xlseek(fd, mtdoffset, SEEK_SET);
122                 xwrite(fd, filebuf, meminfo_writesize);
123                 mtdoffset += meminfo_writesize;
124                 if (cnt < meminfo_writesize)
125                         break;
126         }
127
128         if (cnt != 0) {
129                 /* We filled entire MTD, but did we reach EOF on input? */
130                 if (full_read(STDIN_FILENO, filebuf, meminfo_writesize) != 0) {
131                         /* no */
132                         bb_error_msg_and_die("not enough space in MTD device");
133                 }
134         }
135
136         if (ENABLE_FEATURE_CLEAN_UP) {
137                 free(filebuf);
138                 close(fd);
139         }
140
141         return EXIT_SUCCESS;
142 }