ramips: add basics for Poray devices
[openwrt.git] / tools / firmware-utils / src / mkporayfw.c
1 /*
2  * Builder/viewer/extractor utility for Poray firmware image files
3  *
4  * Copyright (C) 2013 Michel Stempin <michel.stempin@wanadoo.fr>
5  * Copyright (C) 2013 Felix Kaechele <felix@fetzig.org>
6  * Copyright (C) 2013 <admin@openschemes.com>
7  *
8  * This tool is based on:
9  *   TP-Link firmware upgrade tool.
10  *   Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org>
11  *
12  * Itself based on:
13  *   TP-Link WR941 V2 firmware checksum fixing tool.
14  *   Copyright (C) 2008,2009 Wang Jian <lark@linux.net.cn>
15  *
16  * This program is free software; you can redistribute it and/or modify it
17  * under the terms of the GNU General Public License version 2 as published
18  * by the Free Software Foundation.
19  *
20  */
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stdint.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <libgen.h>
28 #include <getopt.h>
29 #include <stdarg.h>
30 #include <errno.h>
31 #include <sys/stat.h>
32 #include <arpa/inet.h>
33 #include <netinet/in.h>
34
35 #if (__BYTE_ORDER == __BIG_ENDIAN)
36 #  define HOST_TO_BE32(x)       (x)
37 #  define BE32_TO_HOST(x)       (x)
38 #  define HOST_TO_LE32(x)       bswap_32(x)
39 #  define LE32_TO_HOST(x)       bswap_32(x)
40 #else
41 #  define HOST_TO_BE32(x)       bswap_32(x)
42 #  define BE32_TO_HOST(x)       bswap_32(x)
43 #  define HOST_TO_LE32(x)       (x)
44 #  define LE32_TO_HOST(x)       (x)
45 #endif
46
47 /* Fixed header flags */
48 #define HEADER_FLAGS            0x020e0000
49
50 /* Recognized Hardware ID magic */
51 #define HWID_HAME_MPR_A1_L8     0x32473352
52 #define HWID_PORAY_R50B         0x31353033
53 #define HWID_PORAY_R50D         0x33353033
54 #define HWID_PORAY_R50E         0x34353033
55 #define HWID_PORAY_M3           0x31353335
56 #define HWID_PORAY_M4           0x32353335
57 #define HWID_PORAY_Q3           0x33353335
58 #define HWID_PORAY_X5_X6        0x35353335
59 #define HWID_PORAY_X8           0x36353335
60 #define HWID_PORAY_X1           0x38353335
61
62 /* Recognized XOR obfuscation keys */
63 #define KEY_HAME                0
64 #define KEY_PORAY_1             1
65 #define KEY_PORAY_2             2
66 #define KEY_PORAY_3             3
67 #define KEY_PORAY_4             4
68
69 /* XOR key length */
70 #define KEY_LEN                 15
71
72 struct file_info {
73         char            *file_name;     /* Name of the file */
74         uint32_t        file_size;      /* Length of the file */
75 };
76
77 struct fw_header {
78         uint32_t        hw_id;          /* Hardware id */
79         uint32_t        firmware_len;   /* Firmware data length */
80         uint32_t        flags;          /* Header flags */
81         uint8_t         pad[16];
82 } __attribute__ ((packed));
83
84 struct flash_layout {
85         char            *id;
86         uint32_t        fw_max_len;
87 };
88
89 struct board_info {
90         char            *id;
91         uint32_t        hw_id;
92         char            *layout_id;
93         uint32_t        key;
94 };
95
96 /*
97  * Globals
98  */
99 static char *ofname;
100 static char *progname;
101
102 static char *board_id;
103 static struct board_info *board;
104 static char *layout_id;
105 static struct flash_layout *layout;
106 static char *opt_hw_id;
107 static uint32_t hw_id;
108 static struct file_info firmware_info;
109 static uint32_t firmware_len = 0;
110
111 static int inspect = 0;
112 static int extract = 0;
113
114 static uint8_t key[][KEY_LEN] = {
115   {0xC8, 0x3C, 0x3A, 0x93, 0xA2, 0x95, 0xC3, 0x63, 0x48, 0x45, 0x58, 0x09, 0x12, 0x03, 0x08},
116   {0x89, 0x6B, 0x5A, 0x93, 0x92, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0xE6, 0xC7},
117   {0xC9, 0x1C, 0x3A, 0x93, 0x92, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0xE6, 0xC7},
118   {0x19, 0x1B, 0x3A, 0x93, 0x92, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0xE6, 0xC7},
119   {0x79, 0x7B, 0x7A, 0x93, 0x92, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0xE6, 0xC7},
120 };
121
122 static struct flash_layout layouts[] = {
123         {
124                 .id             = "4M",
125                 .fw_max_len     = 0x3c0000,
126         }, {
127                 .id             = "8M",
128                 .fw_max_len     = 0x7c0000,
129         }, {
130                 /* terminating entry */
131         }
132 };
133
134 static struct board_info boards[] = {
135         {
136                 .id             = "MPR-A1",
137                 .hw_id          = HWID_HAME_MPR_A1_L8,
138                 .layout_id      = "4M",
139                 .key            = KEY_HAME,
140         }, {
141                 .id             = "MPR-L8",
142                 .hw_id          = HWID_HAME_MPR_A1_L8,
143                 .layout_id      = "4M",
144                 .key            = KEY_HAME,
145         }, {
146                 .id             = "R50B",
147                 .hw_id          = HWID_PORAY_R50B,
148                 .layout_id      = "4M",
149                 .key            = KEY_PORAY_2,
150         }, {
151                 .id             = "R50D",
152                 .hw_id          = HWID_PORAY_R50D,
153                 .layout_id      = "4M",
154                 .key            = KEY_PORAY_3,
155         }, {
156                 .id             = "R50E",
157                 .hw_id          = HWID_PORAY_R50E,
158                 .layout_id      = "4M",
159                 .key            = KEY_PORAY_4,
160         }, {
161                 .id             = "M3",
162                 .hw_id          = HWID_PORAY_M3,
163                 .layout_id      = "4M",
164                 .key            = KEY_PORAY_1,
165         }, {
166                 .id             = "M4",
167                 .hw_id          = HWID_PORAY_M4,
168                 .layout_id      = "4M",
169                 .key            = KEY_PORAY_1,
170         }, {
171                 .id             = "Q3",
172                 .hw_id          = HWID_PORAY_Q3,
173                 .layout_id      = "4M",
174                 .key            = KEY_PORAY_1,
175         }, {
176                 .id             = "X5 or X6",
177                 .hw_id          = HWID_PORAY_X5_X6,
178                 .layout_id      = "8M",
179                 .key            = KEY_PORAY_1,
180         }, {
181                 .id             = "X5",
182                 .hw_id          = HWID_PORAY_X5_X6,
183                 .layout_id      = "8M",
184                 .key            = KEY_PORAY_1,
185         }, {
186                 .id             = "X6",
187                 .hw_id          = HWID_PORAY_X5_X6,
188                 .layout_id      = "8M",
189                 .key            = KEY_PORAY_1,
190         }, {
191                 .id             = "X8",
192                 .hw_id          = HWID_PORAY_X8,
193                 .layout_id      = "8M",
194                 .key            = KEY_PORAY_1,
195         }, {
196                 .id             = "X1",
197                 .hw_id          = HWID_PORAY_X1,
198                 .layout_id      = "8M",
199                 .key            = KEY_PORAY_1,
200         }, {
201                 /* terminating entry */
202         }
203 };
204
205 /*
206  * Message macros
207  */
208 #define ERR(fmt, ...) do { \
209         fflush(0); \
210         fprintf(stderr, "[%s] *** error: " fmt "\n", \
211                         progname, ## __VA_ARGS__ ); \
212 } while (0)
213
214 #define ERRS(fmt, ...) do { \
215         int save = errno; \
216         fflush(0); \
217         fprintf(stderr, "[%s] *** error: " fmt ":%s\n", \
218                         progname, ## __VA_ARGS__, strerror(save)); \
219 } while (0)
220
221 #define DBG(fmt, ...) do { \
222         fprintf(stderr, "[%s] " fmt "\n", progname, ## __VA_ARGS__ ); \
223 } while (0)
224
225 /*
226  * Find a board by its name
227  */
228 static struct board_info *find_board(char *id)
229 {
230         struct board_info *ret;
231         struct board_info *board;
232
233         ret = NULL;
234         for (board = boards; board->id != NULL; board++){
235                 if (strcasecmp(id, board->id) == 0) {
236                         ret = board;
237                         break;
238                 }
239         };
240
241         return ret;
242 }
243
244 /*
245  * Find a board by its hardware ID
246  */
247 static struct board_info *find_board_by_hwid(uint32_t hw_id)
248 {
249         struct board_info *board;
250
251         for (board = boards; board->id != NULL; board++) {
252                 if (hw_id == board->hw_id)
253                         return board;
254         };
255
256         return NULL;
257 }
258
259 /*
260  * Find a Flash memory layout by its name
261  */
262 static struct flash_layout *find_layout(char *id)
263 {
264         struct flash_layout *ret;
265         struct flash_layout *l;
266
267         ret = NULL;
268         for (l = layouts; l->id != NULL; l++){
269                 if (strcasecmp(id, l->id) == 0) {
270                         ret = l;
271                         break;
272                 }
273         };
274
275         return ret;
276 }
277
278 /*
279  * Display usage
280  */
281 static void usage(int status)
282 {
283         FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout;
284
285         fprintf(stream, "Usage: %s [OPTIONS...]\n", progname);
286         fprintf(stream,
287 "\n"
288 "Options:\n"
289 "  -B <board>      create image for the board specified with <board>\n"
290 "  -H <hwid>       use hardware id specified with <hwid>\n"
291 "  -F <id>         use flash layout specified with <id>\n"
292 "  -f <file>       read firmware image from the file <file>\n"
293 "  -o <file>       write output to the file <file>\n"
294 "  -i              inspect given firmware file (requires -f)\n"
295 "  -x              extract combined kernel and rootfs while inspecting (implies -i)\n"
296 "  -h              show this screen\n"
297         );
298
299         exit(status);
300 }
301
302 /*
303  * Get file statistics
304  */
305 static int get_file_stat(struct file_info *fdata)
306 {
307         struct stat st;
308         int res;
309
310         if (fdata->file_name == NULL) {
311                 return 0;
312         }
313         res = stat(fdata->file_name, &st);
314         if (res){
315                 ERRS("stat failed on %s", fdata->file_name);
316                 return res;
317         }
318
319         fdata->file_size = st.st_size;
320         return 0;
321 }
322
323 /*
324  * Read file into buffer
325  */
326 static int read_to_buf(struct file_info *fdata, uint8_t *buf)
327 {
328         FILE *f;
329         int ret = EXIT_FAILURE;
330
331         f = fopen(fdata->file_name, "rb");
332         if (f == NULL) {
333                 ERRS("could not open \"%s\" for reading", fdata->file_name);
334                 goto out;
335         }
336
337         errno = 0;
338         fread(buf, fdata->file_size, 1, f);
339         if (errno != 0) {
340                 ERRS("unable to read from file \"%s\"", fdata->file_name);
341                 goto out_close;
342         }
343
344         ret = EXIT_SUCCESS;
345
346  out_close:
347         fclose(f);
348  out:
349         return ret;
350 }
351
352 /*
353  * Check command line options
354  */
355 static int check_options(void)
356 {
357         int ret;
358
359         if (firmware_info.file_name == NULL) {
360                 ERR("no firmware image specified");
361                 return -1;
362         }
363
364         ret = get_file_stat(&firmware_info);
365         if (ret)
366                 return ret;
367
368         if (inspect)
369                 return 0;
370
371         if (board_id == NULL && opt_hw_id == NULL) {
372                 ERR("either board or hardware id must be specified");
373                 return -1;
374         }
375
376         if (board_id) {
377                 board = find_board(board_id);
378                 if (board == NULL) {
379                         ERR("unknown/unsupported board id \"%s\"", board_id);
380                         return -1;
381                 }
382                 if (layout_id == NULL) {
383                         layout_id = board->layout_id;
384                 }
385                 hw_id = board->hw_id;
386         } else {
387                 hw_id = strtoul(opt_hw_id, NULL, 0);
388                 board = find_board_by_hwid(hw_id);
389                 if (layout_id == NULL) {
390                         layout_id = board->layout_id;
391                 }
392         }
393
394         layout = find_layout(layout_id);
395         if (layout == NULL) {
396                 ERR("unknown flash layout \"%s\"", layout_id);
397                 return -1;
398         }
399
400         firmware_len = firmware_info.file_size;
401
402         if (firmware_info.file_size >
403                 layout->fw_max_len - sizeof (struct fw_header)) {
404                 ERR("firmware image is too big");
405                 return -1;
406         }
407
408         if (ofname == NULL) {
409                 ERR("no output file specified");
410                 return -1;
411         }
412         return 0;
413 }
414
415 /*
416  * Fill in firmware header
417  */
418 static void fill_header(uint8_t *buf)
419 {
420         struct fw_header *hdr = (struct fw_header *) buf;
421
422         memset(hdr, 0, sizeof (struct fw_header));
423         hdr->hw_id = HOST_TO_LE32(hw_id);
424         hdr->firmware_len = HOST_TO_LE32(firmware_len);
425         hdr->flags = HOST_TO_LE32(HEADER_FLAGS);
426 }
427
428 /*
429  * Compute firmware checksum
430  */
431 static uint16_t checksum_fw(uint8_t *data, int len)
432 {
433         int i;
434         int32_t checksum = 0;
435
436         for (i = 0; i < len - 1; i += 2) {
437                 checksum += (data[i + 1] << 8) | data[i];
438         }
439         if (i < len) {
440                 checksum += data[i];
441         }
442         checksum = checksum + (checksum >> 16) + 0xffff;
443         checksum = ~(checksum + (checksum >> 16)) & 0xffff;
444         return (uint16_t) checksum;
445 }
446
447 /*
448  * (De)obfuscate firmware using an XOR operation with a fixed length key
449  */
450 static void xor_fw(uint8_t *data, int len)
451 {
452         int i;
453
454         for (i = 0; i <= len; i++) {
455                 data[i] ^= key[board->key][i % KEY_LEN];
456         }
457 }
458
459 /*
460  * Write firmware to file
461  */
462 static int write_fw(uint8_t *data, int len)
463 {
464         FILE *f;
465         int ret = EXIT_FAILURE;
466
467         f = fopen(ofname, "wb");
468         if (f == NULL) {
469                 ERRS("could not open \"%s\" for writing", ofname);
470                 goto out;
471         }
472
473         errno = 0;
474         fwrite(data, len, 1, f);
475         if (errno) {
476                 ERRS("unable to write output file");
477                 goto out_flush;
478         }
479
480         DBG("firmware file \"%s\" completed", ofname);
481
482         ret = EXIT_SUCCESS;
483
484  out_flush:
485         fflush(f);
486         fclose(f);
487         if (ret != EXIT_SUCCESS) {
488                 unlink(ofname);
489         }
490  out:
491         return ret;
492 }
493
494 /*
495  * Build firmware file
496  */
497 static int build_fw(void)
498 {
499         int buflen;
500         uint8_t *buf, *p;
501         int ret = EXIT_FAILURE;
502         int writelen = 0;
503         uint16_t checksum;
504
505         buflen = layout->fw_max_len;
506
507         buf = (uint8_t *) malloc(buflen);
508         if (!buf) {
509                 ERR("no memory for buffer\n");
510                 goto out;
511         }
512
513         memset(buf, 0xff, buflen);
514         p = buf + sizeof (struct fw_header);
515         ret = read_to_buf(&firmware_info, p);
516         if (ret) {
517                 goto out_free_buf;
518         }
519         writelen = sizeof (struct fw_header) + firmware_len + 2;
520
521         /* Fill in header */
522         fill_header(buf);
523
524         /* Compute firmware checksum */
525         checksum = checksum_fw(buf + sizeof (struct fw_header), firmware_len);
526
527         /* Cannot use network order function because checksum is not word-aligned */
528         buf[writelen - 1] = checksum >> 8;
529         buf[writelen - 2] = checksum & 0xff;
530
531         /* XOR obfuscate firmware */
532         xor_fw(buf + sizeof (struct fw_header), firmware_len + 2);
533
534         /* Write firmware file */
535         ret = write_fw(buf, writelen);
536         if (ret) {
537                 goto out_free_buf;
538         }
539         ret = EXIT_SUCCESS;
540
541  out_free_buf:
542         free(buf);
543  out:
544         return ret;
545 }
546
547 /* Helper functions to inspect_fw() representing different output formats */
548 static inline void inspect_fw_pstr(char *label, char *str)
549 {
550         printf("%-23s: %s\n", label, str);
551 }
552
553 static inline void inspect_fw_phex(char *label, uint32_t val)
554 {
555         printf("%-23s: 0x%08x\n", label, val);
556 }
557
558 static inline void inspect_fw_phexpost(char *label,
559                                        uint32_t val, char *post)
560 {
561         printf("%-23s: 0x%08x (%s)\n", label, val, post);
562 }
563
564 static inline void inspect_fw_phexdef(char *label,
565                                       uint32_t val, uint32_t defval)
566 {
567         printf("%-23s: 0x%08x                  ", label, val);
568
569         if (val == defval) {
570                 printf("(== OpenWrt default)\n");
571         } else {
572                 printf("(OpenWrt default: 0x%08x)\n", defval);
573         }
574 }
575
576 static inline void inspect_fw_phexexp(char *label,
577                                       uint32_t val, uint32_t expval)
578 {
579         printf("%-23s: 0x%08x ", label, val);
580
581         if (val == expval) {
582                 printf("(ok)\n");
583         } else {
584                 printf("(expected: 0x%08x)\n", expval);
585         }
586 }
587
588 static inline void inspect_fw_phexdec(char *label, uint32_t val)
589 {
590         printf("%-23s: 0x%08x / %8u bytes\n", label, val, val);
591 }
592
593 static inline void inspect_fw_pchecksum(char *label,
594                                         uint16_t val, uint16_t expval)
595 {
596         printf("%-23s: 0x%04x     ", label, val);
597         if (val == expval) {
598                 printf("(ok)\n");
599         } else {
600                 printf("(expected: 0x%04x)\n", expval);
601         }
602 }
603
604 static int inspect_fw(void)
605 {
606         uint8_t *buf;
607         struct fw_header *hdr;
608         int ret = EXIT_FAILURE;
609         uint16_t computed_checksum, file_checksum;
610
611         buf = (uint8_t *) malloc(firmware_info.file_size);
612         if (!buf) {
613                 ERR("no memory for buffer!\n");
614                 goto out;
615         }
616
617         ret = read_to_buf(&firmware_info, buf);
618         if (ret) {
619                 goto out_free_buf;
620         }
621         hdr = (struct fw_header *)buf;
622
623         inspect_fw_pstr("File name", firmware_info.file_name);
624         inspect_fw_phexdec("File size", firmware_info.file_size);
625
626         printf("\n");
627
628         inspect_fw_phexdec("Header size", sizeof (struct fw_header));
629         board = find_board_by_hwid(LE32_TO_HOST(hdr->hw_id));
630         if (board) {
631                 layout = find_layout(board->layout_id);
632                 inspect_fw_phexpost("Hardware ID",
633                                     LE32_TO_HOST( hdr->hw_id), board->id);
634         } else {
635                 inspect_fw_phexpost("Hardware ID",
636                                     LE32_TO_HOST(hdr->hw_id), "unknown");
637         }
638         inspect_fw_phexdec("Firmware data length",
639                            LE32_TO_HOST(hdr->firmware_len));
640
641         inspect_fw_phexexp("Flags",
642                            LE32_TO_HOST(hdr->flags), HEADER_FLAGS);
643         printf("\n");
644
645         /* XOR unobfuscate firmware */
646         xor_fw(buf + sizeof (struct fw_header), LE32_TO_HOST(hdr->firmware_len) + 2);
647
648         /* Compute firmware checksum */
649         computed_checksum = checksum_fw(buf + sizeof (struct fw_header), LE32_TO_HOST(hdr->firmware_len));
650
651         /* Cannot use network order function because checksum is not word-aligned */
652         file_checksum = (buf[firmware_info.file_size - 1] << 8) | buf[firmware_info.file_size - 2];
653         inspect_fw_pchecksum("Firmware checksum", computed_checksum, file_checksum);
654
655         /* Verify checksum */
656         if (computed_checksum != file_checksum) {
657                 ret = -1;
658                 ERR("checksums do not match");
659                 goto out_free_buf;
660         }
661
662         printf("\n");
663
664         if (extract) {
665                 FILE *fp;
666                 char *filename;
667
668                 if (ofname == NULL) {
669                         filename = malloc(strlen(firmware_info.file_name) + 10);
670                         sprintf(filename, "%s-firmware", firmware_info.file_name);
671                 } else {
672                         filename = ofname;
673                 }
674                 printf("Extracting firmware to \"%s\"...\n", filename);
675                 fp = fopen(filename, "wb");
676                 if (fp) {
677                   if (!fwrite(buf + sizeof (struct fw_header),
678                                     LE32_TO_HOST(hdr->firmware_len), 1, fp)) {
679                                 ERRS("error in fwrite(): %s", strerror(errno));
680                         }
681                         fclose(fp);
682                 } else {
683                         ERRS("error in fopen(): %s", strerror(errno));
684                 }
685                 if (ofname == NULL) {
686                         free(filename);
687                 }
688                 printf("\n");
689         }
690
691  out_free_buf:
692         free(buf);
693  out:
694         return ret;
695 }
696
697 /*
698  * Main entry point
699  */
700 int main(int argc, char *argv[])
701 {
702         int ret = EXIT_FAILURE;
703
704         progname = basename(argv[0]);
705
706         int c;
707
708         while ((c = getopt(argc, argv, "B:H:F:f:o:ixh")) != -1) {
709                 switch (c) {
710                 case 'B':
711                         board_id = optarg;
712                         break;
713                 case 'H':
714                         opt_hw_id = optarg;
715                         break;
716                 case 'F':
717                         layout_id = optarg;
718                         break;
719                 case 'f':
720                         firmware_info.file_name = optarg;
721                         break;
722                 case 'o':
723                         ofname = optarg;
724                         break;
725                 case 'i':
726                         inspect = 1;
727                         break;
728                 case 'x':
729                         inspect = 1;
730                         extract = 1;
731                         break;
732                 case 'h':
733                         usage(EXIT_SUCCESS);
734                         break;
735                 default:
736                         usage(EXIT_FAILURE);
737                         break;
738                 }
739         }
740
741         ret = check_options();
742         if (ret) {
743                 goto out;
744         }
745         if (!inspect) {
746                 ret = build_fw();
747         } else {
748                 ret = inspect_fw();
749         }
750
751  out:
752         return ret;
753 }