improve mtd image check - don't check trx header if the target partition is not ...
[openwrt.git] / package / mtd / src / mtd.c
1 /*
2  * mtd - simple memory technology device manipulation tool
3  *
4  * Copyright (C) 2005 Waldemar Brodkorb <wbx@dass-it.de>,
5  *                        Felix Fietkau <nbd@openwrt.org>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  *
21  * $Id$
22  *
23  * The code is based on the linux-mtd examples.
24  */
25
26 #include <limits.h>
27 #include <unistd.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <stdint.h>
31 #include <fcntl.h>
32 #include <errno.h>
33 #include <error.h>
34 #include <time.h>
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
37 #include <sys/param.h>
38 #include <sys/mount.h>
39 #include <sys/stat.h>
40 #include <sys/reboot.h>
41 #include <string.h>
42
43 #include <linux/mtd/mtd.h>
44
45 #define TRX_MAGIC       0x30524448      /* "HDR0" */
46 #define BUFSIZE (16 * 1024)
47 #define MAX_ARGS 8
48
49 #define DEBUG
50
51 #define SYSTYPE_UNKNOWN     0
52 #define SYSTYPE_BROADCOM    1
53 /* to be continued */
54
55 struct trx_header {
56         uint32_t magic;         /* "HDR0" */
57         uint32_t len;           /* Length of file including header */
58         uint32_t crc32;         /* 32-bit CRC from flag_version to end of file */
59         uint32_t flag_version;  /* 0:15 flags, 16:31 version */
60         uint32_t offsets[3];    /* Offsets of partitions from start of header */
61 };
62
63 char buf[BUFSIZE];
64 int buflen;
65 int quiet;
66
67 int
68 image_check_bcom(int imagefd, const char *mtd)
69 {
70         struct trx_header *trx = (struct trx_header *) buf;
71         struct mtd_info_user mtdInfo;
72         int fd;
73
74         if (strcmp(mtd, "linux") != 0)
75                 return 1;
76         
77         buflen = read(imagefd, buf, 32);
78         if (buflen < 32) {
79                 fprintf(stdout, "Could not get image header, file too small (%ld bytes)\n", buflen);
80                 return 0;
81         }
82
83         switch(trx->magic) {
84                 case 0x47343557: /* W54G */
85                 case 0x53343557: /* W54S */
86                 case 0x73343557: /* W54s */
87                 case 0x46343557: /* W54F */
88                 case 0x55343557: /* W54U */
89                         /* ignore the first 32 bytes */
90                         buflen = read(imagefd, buf, sizeof(struct trx_header));
91                         break;
92         }
93         
94         if (trx->magic != TRX_MAGIC || trx->len < sizeof(struct trx_header)) {
95                 if (quiet < 2) {
96                         fprintf(stderr, "Bad trx header\n");
97                         fprintf(stderr, "If this is a firmware in bin format, like some of the\n"
98                                         "original firmware files are, you need to convert it to trx.\n");
99                 }
100                 return 0;
101         }
102
103         /* check if image fits to mtd device */
104         fd = mtd_open(mtd, O_RDWR | O_SYNC);
105         if(fd < 0) {
106                 fprintf(stderr, "Could not open mtd device: %s\n", mtd);
107                 exit(1);
108         }
109
110         if(ioctl(fd, MEMGETINFO, &mtdInfo)) {
111                 fprintf(stderr, "Could not get MTD device info from %s\n", mtd);
112                 exit(1);
113         }
114                 
115         if(mtdInfo.size < trx->len) {
116                 fprintf(stderr, "Image too big for partition: %s\n", mtd);
117                 close(fd);
118                 return 0;
119         }       
120         
121         close(fd);
122         return 1;
123 }
124
125 int
126 image_check(int imagefd, const char *mtd)
127 {
128         int fd, systype;
129         size_t count;
130         char *c;
131         FILE *f;
132
133         systype = SYSTYPE_UNKNOWN;
134         f = fopen("/proc/cpuinfo", "r");
135         while (!feof(f) && (fgets(buf, BUFSIZE - 1, f) != NULL)) {
136                 if ((strncmp(buf, "system type", 11) == 0) && (c = strchr(buf, ':'))) {
137                         c += 2;
138                         if (strncmp(c, "Broadcom BCM947XX", 17) == 0)
139                                 systype = SYSTYPE_BROADCOM;
140                 }
141         }
142         fclose(f);
143         
144         switch(systype) {
145                 case SYSTYPE_BROADCOM:
146                         return image_check_bcom(imagefd, mtd);
147                 default:
148                         return 1;
149         }
150 }
151
152 int mtd_check(char *mtd)
153 {
154         struct mtd_info_user mtdInfo;
155         int fd;
156
157         fd = mtd_open(mtd, O_RDWR | O_SYNC);
158         if(fd < 0) {
159                 fprintf(stderr, "Could not open mtd device: %s\n", mtd);
160                 return 0;
161         }
162
163         if(ioctl(fd, MEMGETINFO, &mtdInfo)) {
164                 fprintf(stderr, "Could not get MTD device info from %s\n", mtd);
165                 close(fd);
166                 return 0;
167         }
168
169         close(fd);
170         return 1;
171 }
172
173 int
174 mtd_unlock(const char *mtd)
175 {
176         int fd;
177         struct mtd_info_user mtdInfo;
178         struct erase_info_user mtdLockInfo;
179
180         fd = mtd_open(mtd, O_RDWR | O_SYNC);
181         if(fd < 0) {
182                 fprintf(stderr, "Could not open mtd device: %s\n", mtd);
183                 exit(1);
184         }
185
186         if(ioctl(fd, MEMGETINFO, &mtdInfo)) {
187                 fprintf(stderr, "Could not get MTD device info from %s\n", mtd);
188                 close(fd);
189                 exit(1);
190         }
191
192         mtdLockInfo.start = 0;
193         mtdLockInfo.length = mtdInfo.size;
194         if(ioctl(fd, MEMUNLOCK, &mtdLockInfo)) {
195                 close(fd);
196                 return 0;
197         }
198                 
199         close(fd);
200         return 0;
201 }
202
203 int
204 mtd_open(const char *mtd, int flags)
205 {
206         FILE *fp;
207         char dev[PATH_MAX];
208         int i;
209
210         if ((fp = fopen("/proc/mtd", "r"))) {
211                 while (fgets(dev, sizeof(dev), fp)) {
212                         if (sscanf(dev, "mtd%d:", &i) && strstr(dev, mtd)) {
213                                 snprintf(dev, sizeof(dev), "/dev/mtd/%d", i);
214                                 fclose(fp);
215                                 return open(dev, flags);
216                         }
217                 }
218                 fclose(fp);
219         }
220
221         return open(mtd, flags);
222 }
223
224 int
225 mtd_erase(const char *mtd)
226 {
227         int fd;
228         struct mtd_info_user mtdInfo;
229         struct erase_info_user mtdEraseInfo;
230
231         fd = mtd_open(mtd, O_RDWR | O_SYNC);
232         if(fd < 0) {
233                 fprintf(stderr, "Could not open mtd device: %s\n", mtd);
234                 exit(1);
235         }
236
237         if(ioctl(fd, MEMGETINFO, &mtdInfo)) {
238                 fprintf(stderr, "Could not get MTD device info from %s\n", mtd);
239                 close(fd);
240                 exit(1);
241         }
242
243         mtdEraseInfo.length = mtdInfo.erasesize;
244
245         for (mtdEraseInfo.start = 0;
246                  mtdEraseInfo.start < mtdInfo.size;
247                  mtdEraseInfo.start += mtdInfo.erasesize) {
248                 
249                 ioctl(fd, MEMUNLOCK, &mtdEraseInfo);
250                 if(ioctl(fd, MEMERASE, &mtdEraseInfo)) {
251                         fprintf(stderr, "Could not erase MTD device: %s\n", mtd);
252                         close(fd);
253                         exit(1);
254                 }
255         }               
256
257         close(fd);
258         return 0;
259
260 }
261
262 int
263 mtd_write(int imagefd, const char *mtd)
264 {
265         int fd, i, result;
266         size_t r, w, e;
267         struct mtd_info_user mtdInfo;
268         struct erase_info_user mtdEraseInfo;
269         int ret = 0;
270
271         fd = mtd_open(mtd, O_RDWR | O_SYNC);
272         if(fd < 0) {
273                 fprintf(stderr, "Could not open mtd device: %s\n", mtd);
274                 exit(1);
275         }
276
277         if(ioctl(fd, MEMGETINFO, &mtdInfo)) {
278                 fprintf(stderr, "Could not get MTD device info from %s\n", mtd);
279                 close(fd);
280                 exit(1);
281         }
282                 
283         r = w = e = 0;
284         if (!quiet)
285                 fprintf(stderr, " [ ]");
286
287         for (;;) {
288                 /* buffer may contain data already (from trx check) */
289                 r = buflen;
290                 r += read(imagefd, buf + buflen, BUFSIZE - buflen);
291                 w += r;
292
293                 /* EOF */
294                 if (r <= 0) break;
295
296                 /* need to erase the next block before writing data to it */
297                 while (w > e) {
298                         mtdEraseInfo.start = e;
299                         mtdEraseInfo.length = mtdInfo.erasesize;
300
301                         if (!quiet)
302                                 fprintf(stderr, "\b\b\b[e]");
303                         /* erase the chunk */
304                         if (ioctl (fd,MEMERASE,&mtdEraseInfo) < 0) {
305                                 fprintf(stderr, "Erasing mtd failed: %s\n", mtd);
306                                 exit(1);
307                         }
308                         e += mtdInfo.erasesize;
309                 }
310                 
311                 if (!quiet)
312                         fprintf(stderr, "\b\b\b[w]");
313                 
314                 if ((result = write(fd, buf, r)) < r) {
315                         if (result < 0) {
316                                 fprintf(stderr, "Error writing image.\n");
317                                 exit(1);
318                         } else {
319                                 fprintf(stderr, "Insufficient space.\n");
320                                 exit(1);
321                         }
322                 }
323                 
324                 buflen = 0;
325         }
326         if (!quiet)
327                 fprintf(stderr, "\b\b\b\b");
328
329         close(fd);
330         return 0;
331 }
332
333 void usage(void)
334 {
335         fprintf(stderr, "Usage: mtd [<options> ...] <command> [<arguments> ...] <device>\n\n"
336         "The device is in the format of mtdX (eg: mtd4) or its label.\n"
337         "mtd recognizes these commands:\n"
338         "        unlock                  unlock the device\n"
339         "        erase                   erase all data on device\n"
340         "        write <imagefile>|-     write <imagefile> (use - for stdin) to device\n"
341         "Following options are available:\n"
342         "        -q                      quiet mode (once: no [w] on writing,\n"
343         "                                           twice: no status messages)\n"
344         "        -r                      reboot after successful command\n"
345         "        -f                      force write without trx checks\n"
346         "        -e <device>             erase <device> before executing the command\n\n"
347         "Example: To write linux.trx to mtd4 labeled as linux and reboot afterwards\n"
348         "         mtd -r write linux.trx linux\n\n");
349         exit(1);
350 }
351
352 int main (int argc, char **argv)
353 {
354         int ch, i, boot, unlock, imagefd, force, unlocked;
355         char *erase[MAX_ARGS], *device, *imagefile;
356         enum {
357                 CMD_ERASE,
358                 CMD_WRITE,
359                 CMD_UNLOCK
360         } cmd;
361         
362         erase[0] = NULL;
363         boot = 0;
364         force = 0;
365         buflen = 0;
366         quiet = 0;
367
368         while ((ch = getopt(argc, argv, "frqe:")) != -1)
369                 switch (ch) {
370                         case 'f':
371                                 force = 1;
372                                 break;
373                         case 'r':
374                                 boot = 1;
375                                 break;
376                         case 'q':
377                                 quiet++;
378                                 break;
379                         case 'e':
380                                 i = 0;
381                                 while ((erase[i] != NULL) && ((i + 1) < MAX_ARGS))
382                                         i++;
383                                         
384                                 erase[i++] = optarg;
385                                 erase[i] = NULL;
386                                 break;
387                         
388                         case '?':
389                         default:
390                                 usage();
391                 }
392         argc -= optind;
393         argv += optind;
394         
395         if (argc < 2)
396                 usage();
397
398         if ((strcmp(argv[0], "unlock") == 0) && (argc == 2)) {
399                 cmd = CMD_UNLOCK;
400                 device = argv[1];
401         } else if ((strcmp(argv[0], "erase") == 0) && (argc == 2)) {
402                 cmd = CMD_ERASE;
403                 device = argv[1];
404         } else if ((strcmp(argv[0], "write") == 0) && (argc == 3)) {
405                 cmd = CMD_WRITE;
406                 device = argv[2];
407         
408                 if (strcmp(argv[1], "-") == 0) {
409                         imagefile = "<stdin>";
410                         imagefd = 0;
411                 } else {
412                         imagefile = argv[1];
413                         if ((imagefd = open(argv[1], O_RDONLY)) < 0) {
414                                 fprintf(stderr, "Couldn't open image file: %s!\n", imagefile);
415                                 exit(1);
416                         }
417                 }
418         
419                 /* check trx file before erasing or writing anything */
420                 if (!image_check(imagefd, device)) {
421                         if (!force) {
422                                 fprintf(stderr, "Image check failed.\n");
423                                 exit(1);
424                         }
425                 } else {
426                         if (!mtd_check(device)) {
427                                 fprintf(stderr, "Can't open device for writing!\n");
428                                 exit(1);
429                         }
430                 }
431         } else {
432                 usage();
433         }
434
435         sync();
436         
437         i = 0;
438         unlocked = 0;
439         while (erase[i] != NULL) {
440                 if (quiet < 2)
441                         fprintf(stderr, "Unlocking %s ...\n", erase[i]);
442                 mtd_unlock(erase[i]);
443                 if (quiet < 2)
444                         fprintf(stderr, "Erasing %s ...\n", erase[i]);
445                 mtd_erase(erase[i]);
446                 if (strcmp(erase[i], device) == 0)
447                         unlocked = 1;
448                 i++;
449         }
450         
451         if (!unlocked) {
452                 if (quiet < 2) 
453                         fprintf(stderr, "Unlocking %s ...\n", device);
454                 mtd_unlock(device);
455         }
456                 
457         switch (cmd) {
458                 case CMD_UNLOCK:
459                         break;
460                 case CMD_ERASE:
461                         if (quiet < 2)
462                                 fprintf(stderr, "Erasing %s ...\n", device);
463                         mtd_erase(device);
464                         break;
465                 case CMD_WRITE:
466                         if (quiet < 2)
467                                 fprintf(stderr, "Writing from %s to %s ... ", imagefile, device);
468                         mtd_write(imagefd, device);
469                         if (quiet < 2)
470                                 fprintf(stderr, "\n");
471                         break;
472         }
473
474         sync();
475         
476         if (boot)
477                 kill(1, 15); // send SIGTERM to init for reboot
478
479         return 0;
480 }