ed785b48e562a02cb09fa6baf6d6bd2c68692a49
[openwrt/staging/ldir.git] / tools / firmware-utils / src / mktplinkfw.c
1 /*
2 * Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org>
3 *
4 * This tool was based on:
5 * TP-Link WR941 V2 firmware checksum fixing tool.
6 * Copyright (C) 2008,2009 Wang Jian <lark@linux.net.cn>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License version 2 as published
10 * by the Free Software Foundation.
11 *
12 */
13
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <stdint.h>
17 #include <string.h>
18 #include <unistd.h> /* for unlink() */
19 #include <libgen.h>
20 #include <getopt.h> /* for getopt() */
21 #include <stdarg.h>
22 #include <stdbool.h>
23 #include <endian.h>
24 #include <errno.h>
25 #include <sys/stat.h>
26
27 #include <arpa/inet.h>
28 #include <netinet/in.h>
29
30 #include "md5.h"
31 #include "mktplinkfw-lib.h"
32
33 #define HEADER_VERSION_V1 0x01000000
34 #define HEADER_VERSION_V2 0x02000000
35
36 struct fw_header {
37 uint32_t version; /* header version */
38 char vendor_name[24];
39 char fw_version[36];
40 uint32_t hw_id; /* hardware id */
41 uint32_t hw_rev; /* hardware revision */
42 uint32_t region_code; /* region code */
43 uint8_t md5sum1[MD5SUM_LEN];
44 uint32_t unk2;
45 uint8_t md5sum2[MD5SUM_LEN];
46 uint32_t unk3;
47 uint32_t kernel_la; /* kernel load address */
48 uint32_t kernel_ep; /* kernel entry point */
49 uint32_t fw_length; /* total length of the firmware */
50 uint32_t kernel_ofs; /* kernel data offset */
51 uint32_t kernel_len; /* kernel data length */
52 uint32_t rootfs_ofs; /* rootfs data offset */
53 uint32_t rootfs_len; /* rootfs data length */
54 uint32_t boot_ofs; /* bootloader data offset */
55 uint32_t boot_len; /* bootloader data length */
56 uint16_t ver_hi;
57 uint16_t ver_mid;
58 uint16_t ver_lo;
59 uint8_t pad[130];
60 char region_str1[32];
61 char region_str2[32];
62 uint8_t pad2[160];
63 } __attribute__ ((packed));
64
65 struct fw_region {
66 char name[4];
67 uint32_t code;
68 };
69
70
71 /*
72 * Globals
73 */
74 char *ofname;
75 char *progname;
76 static char *vendor = "TP-LINK Technologies";
77 static char *version = "ver. 1.0";
78 static char *fw_ver = "0.0.0";
79 static uint32_t hdr_ver = HEADER_VERSION_V1;
80
81 static char *layout_id;
82 struct flash_layout *layout;
83 static char *opt_hw_id;
84 static uint32_t hw_id;
85 static char *opt_hw_rev;
86 static uint32_t hw_rev;
87 static uint32_t opt_hdr_ver = 1;
88 static char *country;
89 static const struct fw_region *region;
90 static int fw_ver_lo;
91 static int fw_ver_mid;
92 static int fw_ver_hi;
93 struct file_info kernel_info;
94 static uint32_t kernel_la = 0;
95 static uint32_t kernel_ep = 0;
96 uint32_t kernel_len = 0;
97 struct file_info rootfs_info;
98 uint32_t rootfs_ofs = 0;
99 uint32_t rootfs_align;
100 static struct file_info boot_info;
101 int combined;
102 int strip_padding;
103 int add_jffs2_eof;
104 static uint32_t fw_max_len;
105 static uint32_t reserved_space;
106
107 static struct file_info inspect_info;
108 static int extract = 0;
109 static bool endian_swap = false;
110 static bool rootfs_ofs_calc = false;
111
112 static const char md5salt_normal[MD5SUM_LEN] = {
113 0xdc, 0xd7, 0x3a, 0xa5, 0xc3, 0x95, 0x98, 0xfb,
114 0xdd, 0xf9, 0xe7, 0xf4, 0x0e, 0xae, 0x47, 0x38,
115 };
116
117 static const char md5salt_boot[MD5SUM_LEN] = {
118 0x8c, 0xef, 0x33, 0x5b, 0xd5, 0xc5, 0xce, 0xfa,
119 0xa7, 0x9c, 0x28, 0xda, 0xb2, 0xe9, 0x0f, 0x42,
120 };
121
122 static struct flash_layout layouts[] = {
123 {
124 .id = "4M",
125 .fw_max_len = 0x3c0000,
126 .kernel_la = 0x80060000,
127 .kernel_ep = 0x80060000,
128 .rootfs_ofs = 0x140000,
129 }, {
130 .id = "4Mlzma",
131 .fw_max_len = 0x3c0000,
132 .kernel_la = 0x80060000,
133 .kernel_ep = 0x80060000,
134 .rootfs_ofs = 0x100000,
135 }, {
136 .id = "8M",
137 .fw_max_len = 0x7c0000,
138 .kernel_la = 0x80060000,
139 .kernel_ep = 0x80060000,
140 .rootfs_ofs = 0x140000,
141 }, {
142 .id = "8Mlzma",
143 .fw_max_len = 0x7c0000,
144 .kernel_la = 0x80060000,
145 .kernel_ep = 0x80060000,
146 .rootfs_ofs = 0x100000,
147 }, {
148 .id = "8Mmtk",
149 .fw_max_len = 0x7c0000,
150 .kernel_la = 0x80000000,
151 .kernel_ep = 0x8000c310,
152 .rootfs_ofs = 0x100000,
153 }, {
154 .id = "16M",
155 .fw_max_len = 0xf80000,
156 .kernel_la = 0x80060000,
157 .kernel_ep = 0x80060000,
158 .rootfs_ofs = 0x140000,
159 }, {
160 .id = "16Mlzma",
161 .fw_max_len = 0xf80000,
162 .kernel_la = 0x80060000,
163 .kernel_ep = 0x80060000,
164 .rootfs_ofs = 0x100000,
165 }, {
166 .id = "16Mppc",
167 .fw_max_len = 0xf80000,
168 .kernel_la = 0x00000000 ,
169 .kernel_ep = 0xc0000000,
170 .rootfs_ofs = 0x2a0000,
171 }, {
172 /* terminating entry */
173 }
174 };
175
176 static const struct fw_region regions[] = {
177 /* Default region (universal) uses code 0 as well */
178 {"US", 1},
179 {"EU", 0},
180 {"BR", 0},
181 };
182
183 static const struct fw_region * find_region(const char *country) {
184 size_t i;
185
186 for (i = 0; i < ARRAY_SIZE(regions); i++) {
187 if (strcasecmp(regions[i].name, country) == 0)
188 return &regions[i];
189 }
190
191 return NULL;
192 }
193
194 static void usage(int status)
195 {
196 fprintf(stderr, "Usage: %s [OPTIONS...]\n", progname);
197 fprintf(stderr,
198 "\n"
199 "Options:\n"
200 " -c use combined kernel image\n"
201 " -e swap endianness in kernel load address and entry point\n"
202 " -E <ep> overwrite kernel entry point with <ep> (hexval prefixed with 0x)\n"
203 " -L <la> overwrite kernel load address with <la> (hexval prefixed with 0x)\n"
204 " -H <hwid> use hardware id specified with <hwid>\n"
205 " -W <hwrev> use hardware revision specified with <hwrev>\n"
206 " -C <country> set region code to <country>\n"
207 " -F <id> use flash layout specified with <id>\n"
208 " -k <file> read kernel image from the file <file>\n"
209 " -r <file> read rootfs image from the file <file>\n"
210 " -a <align> align the rootfs start on an <align> bytes boundary\n"
211 " -R <offset> overwrite rootfs offset with <offset> (hexval prefixed with 0x)\n"
212 " -O calculate rootfs offset for combined images\n"
213 " -o <file> write output to the file <file>\n"
214 " -s strip padding from the end of the image\n"
215 " -j add jffs2 end-of-filesystem markers\n"
216 " -N <vendor> set image vendor to <vendor>\n"
217 " -V <version> set image version to <version>\n"
218 " -v <version> set firmware version to <version>\n"
219 " -m <version> set header version to <version>\n"
220 " -i <file> inspect given firmware file <file>\n"
221 " -x extract kernel and rootfs while inspecting (requires -i)\n"
222 " -X <size> reserve <size> bytes in the firmware image (hexval prefixed with 0x)\n"
223 " -h show this screen\n"
224 );
225
226 exit(status);
227 }
228
229 static int check_options(void)
230 {
231 int ret;
232 int exceed_bytes;
233
234 if (inspect_info.file_name) {
235 ret = get_file_stat(&inspect_info);
236 if (ret)
237 return ret;
238
239 return 0;
240 } else if (extract) {
241 ERR("no firmware for inspection specified");
242 return -1;
243 }
244
245 if (opt_hw_id == NULL) {
246 ERR("hardware id not specified");
247 return -1;
248 }
249 hw_id = strtoul(opt_hw_id, NULL, 0);
250
251 if (!combined && layout_id == NULL) {
252 ERR("flash layout is not specified");
253 return -1;
254 }
255
256 if (opt_hw_rev)
257 hw_rev = strtoul(opt_hw_rev, NULL, 0);
258 else
259 hw_rev = 1;
260
261 if (country) {
262 region = find_region(country);
263 if (!region) {
264 ERR("unknown region code \"%s\"", country);
265 return -1;
266 }
267 }
268
269 if (combined) {
270 if (!kernel_la || !kernel_ep) {
271 ERR("kernel loading address and entry point must be specified for combined image");
272 return -1;
273 }
274 } else {
275 layout = find_layout(layouts, layout_id);
276 if (layout == NULL) {
277 ERR("unknown flash layout \"%s\"", layout_id);
278 return -1;
279 }
280
281 if (!kernel_la)
282 kernel_la = layout->kernel_la;
283 if (!kernel_ep)
284 kernel_ep = layout->kernel_ep;
285 if (!rootfs_ofs)
286 rootfs_ofs = layout->rootfs_ofs;
287
288 if (reserved_space > layout->fw_max_len) {
289 ERR("reserved space is not valid");
290 return -1;
291 }
292 }
293
294 if (kernel_info.file_name == NULL) {
295 ERR("no kernel image specified");
296 return -1;
297 }
298
299 ret = get_file_stat(&kernel_info);
300 if (ret)
301 return ret;
302
303 kernel_len = kernel_info.file_size;
304
305 if (!combined) {
306 fw_max_len = layout->fw_max_len - reserved_space;
307
308 if (rootfs_info.file_name == NULL) {
309 ERR("no rootfs image specified");
310 return -1;
311 }
312
313 ret = get_file_stat(&rootfs_info);
314 if (ret)
315 return ret;
316
317 if (rootfs_align) {
318 kernel_len += sizeof(struct fw_header);
319 rootfs_ofs = ALIGN(kernel_len, rootfs_align);
320 kernel_len -= sizeof(struct fw_header);
321
322 DBG("rootfs offset aligned to 0x%u", rootfs_ofs);
323
324 exceed_bytes = kernel_len + rootfs_info.file_size - (fw_max_len - sizeof(struct fw_header));
325 if (exceed_bytes > 0) {
326 ERR("images are too big by %i bytes", exceed_bytes);
327 return -1;
328 }
329 } else {
330 exceed_bytes = kernel_info.file_size - (rootfs_ofs - sizeof(struct fw_header));
331 if (exceed_bytes > 0) {
332 ERR("kernel image is too big by %i bytes", exceed_bytes);
333 return -1;
334 }
335
336 exceed_bytes = rootfs_info.file_size - (fw_max_len - rootfs_ofs);
337 if (exceed_bytes > 0) {
338 ERR("rootfs image is too big by %i bytes", exceed_bytes);
339 return -1;
340 }
341 }
342 }
343
344 if (ofname == NULL) {
345 ERR("no output file specified");
346 return -1;
347 }
348
349 ret = sscanf(fw_ver, "%d.%d.%d", &fw_ver_hi, &fw_ver_mid, &fw_ver_lo);
350 if (ret != 3) {
351 ERR("invalid firmware version '%s'", fw_ver);
352 return -1;
353 }
354
355 if (opt_hdr_ver == 1) {
356 hdr_ver = HEADER_VERSION_V1;
357 } else if (opt_hdr_ver == 2) {
358 hdr_ver = HEADER_VERSION_V2;
359 } else {
360 ERR("invalid header version '%u'", opt_hdr_ver);
361 return -1;
362 }
363
364 return 0;
365 }
366
367 void fill_header(char *buf, int len)
368 {
369 struct fw_header *hdr = (struct fw_header *)buf;
370
371 memset(hdr, 0, sizeof(struct fw_header));
372
373 hdr->version = htonl(hdr_ver);
374 strncpy(hdr->vendor_name, vendor, sizeof(hdr->vendor_name));
375 strncpy(hdr->fw_version, version, sizeof(hdr->fw_version));
376 hdr->hw_id = htonl(hw_id);
377 hdr->hw_rev = htonl(hw_rev);
378
379 hdr->kernel_la = htonl(kernel_la);
380 hdr->kernel_ep = htonl(kernel_ep);
381 hdr->kernel_ofs = htonl(sizeof(struct fw_header));
382 hdr->kernel_len = htonl(kernel_len);
383
384 if (!combined) {
385 if (boot_info.file_size == 0)
386 memcpy(hdr->md5sum1, md5salt_normal, sizeof(hdr->md5sum1));
387 else
388 memcpy(hdr->md5sum1, md5salt_boot, sizeof(hdr->md5sum1));
389
390 hdr->fw_length = htonl(layout->fw_max_len);
391 hdr->rootfs_ofs = htonl(rootfs_ofs);
392 hdr->rootfs_len = htonl(rootfs_info.file_size);
393 }
394
395 if (combined && rootfs_ofs_calc) {
396 hdr->rootfs_ofs = htonl(sizeof(struct fw_header) + kernel_len);
397 }
398
399 hdr->ver_hi = htons(fw_ver_hi);
400 hdr->ver_mid = htons(fw_ver_mid);
401 hdr->ver_lo = htons(fw_ver_lo);
402
403 if (region) {
404 hdr->region_code = htonl(region->code);
405 snprintf(
406 hdr->region_str1, sizeof(hdr->region_str1), "00000000;%02X%02X%02X%02X;",
407 region->name[0], region->name[1], region->name[2], region->name[3]
408 );
409 snprintf(
410 hdr->region_str2, sizeof(hdr->region_str2), "%02X%02X%02X%02X",
411 region->name[0], region->name[1], region->name[2], region->name[3]
412 );
413 }
414
415 if (endian_swap) {
416 hdr->kernel_la = bswap_32(hdr->kernel_la);
417 hdr->kernel_ep = bswap_32(hdr->kernel_ep);
418 }
419
420 if (!combined)
421 get_md5(buf, len, hdr->md5sum1);
422 }
423
424 static int inspect_fw(void)
425 {
426 char *buf;
427 struct fw_header *hdr;
428 uint8_t md5sum[MD5SUM_LEN];
429 int ret = EXIT_FAILURE;
430
431 buf = malloc(inspect_info.file_size);
432 if (!buf) {
433 ERR("no memory for buffer!\n");
434 goto out;
435 }
436
437 ret = read_to_buf(&inspect_info, buf);
438 if (ret)
439 goto out_free_buf;
440 hdr = (struct fw_header *)buf;
441
442 inspect_fw_pstr("File name", inspect_info.file_name);
443 inspect_fw_phexdec("File size", inspect_info.file_size);
444
445 if ((ntohl(hdr->version) != HEADER_VERSION_V1) &&
446 (ntohl(hdr->version) != HEADER_VERSION_V2)) {
447 ERR("file does not seem to have V1/V2 header!\n");
448 goto out_free_buf;
449 }
450
451 inspect_fw_phexdec("Version 1 Header size", sizeof(struct fw_header));
452
453 memcpy(md5sum, hdr->md5sum1, sizeof(md5sum));
454 if (ntohl(hdr->boot_len) == 0)
455 memcpy(hdr->md5sum1, md5salt_normal, sizeof(md5sum));
456 else
457 memcpy(hdr->md5sum1, md5salt_boot, sizeof(md5sum));
458 get_md5(buf, inspect_info.file_size, hdr->md5sum1);
459
460 if (memcmp(md5sum, hdr->md5sum1, sizeof(md5sum))) {
461 inspect_fw_pmd5sum("Header MD5Sum1", md5sum, "(*ERROR*)");
462 inspect_fw_pmd5sum(" --> expected", hdr->md5sum1, "");
463 } else {
464 inspect_fw_pmd5sum("Header MD5Sum1", md5sum, "(ok)");
465 }
466 if (ntohl(hdr->unk2) != 0)
467 inspect_fw_phexdec("Unknown value 2", hdr->unk2);
468 inspect_fw_pmd5sum("Header MD5Sum2", hdr->md5sum2,
469 "(purpose yet unknown, unchecked here)");
470 if (ntohl(hdr->unk3) != 0)
471 inspect_fw_phexdec("Unknown value 3", hdr->unk3);
472
473 printf("\n");
474
475 inspect_fw_pstr("Vendor name", hdr->vendor_name);
476 inspect_fw_pstr("Firmware version", hdr->fw_version);
477 inspect_fw_phex("Hardware ID", ntohl(hdr->hw_id));
478 inspect_fw_phex("Hardware Revision", ntohl(hdr->hw_rev));
479 inspect_fw_phex("Region code", ntohl(hdr->region_code));
480
481 printf("\n");
482
483 inspect_fw_phexdec("Kernel data offset",
484 ntohl(hdr->kernel_ofs));
485 inspect_fw_phexdec("Kernel data length",
486 ntohl(hdr->kernel_len));
487 inspect_fw_phex("Kernel load address",
488 ntohl(hdr->kernel_la));
489 inspect_fw_phex("Kernel entry point",
490 ntohl(hdr->kernel_ep));
491 inspect_fw_phexdec("Rootfs data offset",
492 ntohl(hdr->rootfs_ofs));
493 inspect_fw_phexdec("Rootfs data length",
494 ntohl(hdr->rootfs_len));
495 inspect_fw_phexdec("Boot loader data offset",
496 ntohl(hdr->boot_ofs));
497 inspect_fw_phexdec("Boot loader data length",
498 ntohl(hdr->boot_len));
499 inspect_fw_phexdec("Total firmware length",
500 ntohl(hdr->fw_length));
501
502 if (extract) {
503 FILE *fp;
504 char *filename;
505
506 printf("\n");
507
508 filename = malloc(strlen(inspect_info.file_name) + 8);
509 sprintf(filename, "%s-kernel", inspect_info.file_name);
510 printf("Extracting kernel to \"%s\"...\n", filename);
511 fp = fopen(filename, "w");
512 if (fp) {
513 if (!fwrite(buf + ntohl(hdr->kernel_ofs),
514 ntohl(hdr->kernel_len), 1, fp)) {
515 ERR("error in fwrite(): %s", strerror(errno));
516 }
517 fclose(fp);
518 } else {
519 ERR("error in fopen(): %s", strerror(errno));
520 }
521 free(filename);
522
523 filename = malloc(strlen(inspect_info.file_name) + 8);
524 sprintf(filename, "%s-rootfs", inspect_info.file_name);
525 printf("Extracting rootfs to \"%s\"...\n", filename);
526 fp = fopen(filename, "w");
527 if (fp) {
528 if (!fwrite(buf + ntohl(hdr->rootfs_ofs),
529 ntohl(hdr->rootfs_len), 1, fp)) {
530 ERR("error in fwrite(): %s", strerror(errno));
531 }
532 fclose(fp);
533 } else {
534 ERR("error in fopen(): %s", strerror(errno));
535 }
536 free(filename);
537 }
538
539 out_free_buf:
540 free(buf);
541 out:
542 return ret;
543 }
544
545 int main(int argc, char *argv[])
546 {
547 int ret = EXIT_FAILURE;
548
549 progname = basename(argv[0]);
550
551 while ( 1 ) {
552 int c;
553
554 c = getopt(argc, argv, "a:H:E:F:L:m:V:N:W:C:ci:k:r:R:o:OxX:ehsjv:");
555 if (c == -1)
556 break;
557
558 switch (c) {
559 case 'a':
560 sscanf(optarg, "0x%x", &rootfs_align);
561 break;
562 case 'H':
563 opt_hw_id = optarg;
564 break;
565 case 'E':
566 sscanf(optarg, "0x%x", &kernel_ep);
567 break;
568 case 'F':
569 layout_id = optarg;
570 break;
571 case 'W':
572 opt_hw_rev = optarg;
573 break;
574 case 'C':
575 country = optarg;
576 break;
577 case 'L':
578 sscanf(optarg, "0x%x", &kernel_la);
579 break;
580 case 'm':
581 sscanf(optarg, "%u", &opt_hdr_ver);
582 break;
583 case 'V':
584 version = optarg;
585 break;
586 case 'v':
587 fw_ver = optarg;
588 break;
589 case 'N':
590 vendor = optarg;
591 break;
592 case 'c':
593 combined++;
594 break;
595 case 'k':
596 kernel_info.file_name = optarg;
597 break;
598 case 'r':
599 rootfs_info.file_name = optarg;
600 break;
601 case 'R':
602 sscanf(optarg, "0x%x", &rootfs_ofs);
603 break;
604 case 'o':
605 ofname = optarg;
606 break;
607 case 'O':
608 rootfs_ofs_calc = 1;
609 break;
610 case 's':
611 strip_padding = 1;
612 break;
613 case 'i':
614 inspect_info.file_name = optarg;
615 break;
616 case 'j':
617 add_jffs2_eof = 1;
618 break;
619 case 'x':
620 extract = 1;
621 break;
622 case 'e':
623 endian_swap = true;
624 break;
625 case 'h':
626 usage(EXIT_SUCCESS);
627 break;
628 case 'X':
629 sscanf(optarg, "0x%x", &reserved_space);
630 break;
631 default:
632 usage(EXIT_FAILURE);
633 break;
634 }
635 }
636
637 ret = check_options();
638 if (ret)
639 goto out;
640
641 if (!inspect_info.file_name)
642 ret = build_fw(sizeof(struct fw_header));
643 else
644 ret = inspect_fw();
645
646 out:
647 return ret;
648 }