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