firmware-utils: tplink-safeloader: add support for TP-Link WBS210/510 1.2
[openwrt/staging/wigyori.git] / tools / firmware-utils / src / tplink-safeloader.c
1 /*
2 Copyright (c) 2014, Matthias Schiffer <mschiffer@universe-factory.net>
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8 1. Redistributions of source code must retain the above copyright notice,
9 this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26
27 /*
28 tplink-safeloader
29
30 Image generation tool for the TP-LINK SafeLoader as seen on
31 TP-LINK Pharos devices (CPE210/220/510/520)
32 */
33
34
35 #include <assert.h>
36 #include <errno.h>
37 #include <stdbool.h>
38 #include <stdio.h>
39 #include <stdint.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <time.h>
43 #include <unistd.h>
44
45 #include <arpa/inet.h>
46
47 #include <sys/types.h>
48 #include <sys/stat.h>
49
50 #include "md5.h"
51
52
53 #define ALIGN(x,a) ({ typeof(a) __a = (a); (((x) + __a - 1) & ~(__a - 1)); })
54
55
56 #define MAX_PARTITIONS 32
57
58 /** An image partition table entry */
59 struct image_partition_entry {
60 const char *name;
61 size_t size;
62 uint8_t *data;
63 };
64
65 /** A flash partition table entry */
66 struct flash_partition_entry {
67 const char *name;
68 uint32_t base;
69 uint32_t size;
70 };
71
72 /** Firmware layout description */
73 struct device_info {
74 const char *id;
75 const char *vendor;
76 const char *support_list;
77 char support_trail;
78 const struct flash_partition_entry partitions[MAX_PARTITIONS+1];
79 const char *first_sysupgrade_partition;
80 const char *last_sysupgrade_partition;
81 };
82
83 /** The content of the soft-version structure */
84 struct __attribute__((__packed__)) soft_version {
85 uint32_t magic;
86 uint32_t zero;
87 uint8_t pad1;
88 uint8_t version_major;
89 uint8_t version_minor;
90 uint8_t version_patch;
91 uint8_t year_hi;
92 uint8_t year_lo;
93 uint8_t month;
94 uint8_t day;
95 uint32_t rev;
96 uint8_t pad2;
97 };
98
99
100 static const uint8_t jffs2_eof_mark[4] = {0xde, 0xad, 0xc0, 0xde};
101
102
103 /**
104 Salt for the MD5 hash
105
106 Fortunately, TP-LINK seems to use the same salt for most devices which use
107 the new image format.
108 */
109 static const uint8_t md5_salt[16] = {
110 0x7a, 0x2b, 0x15, 0xed,
111 0x9b, 0x98, 0x59, 0x6d,
112 0xe5, 0x04, 0xab, 0x44,
113 0xac, 0x2a, 0x9f, 0x4e,
114 };
115
116
117 /** Firmware layout table */
118 static struct device_info boards[] = {
119 /** Firmware layout for the CPE210/220 */
120 {
121 .id = "CPE210",
122 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
123 .support_list =
124 "SupportList:\r\n"
125 "CPE210(TP-LINK|UN|N300-2):1.0\r\n"
126 "CPE210(TP-LINK|UN|N300-2):1.1\r\n"
127 "CPE210(TP-LINK|US|N300-2):1.1\r\n"
128 "CPE210(TP-LINK|EU|N300-2):1.1\r\n"
129 "CPE220(TP-LINK|UN|N300-2):1.1\r\n"
130 "CPE220(TP-LINK|US|N300-2):1.1\r\n"
131 "CPE220(TP-LINK|EU|N300-2):1.1\r\n",
132 .support_trail = '\xff',
133
134 .partitions = {
135 {"fs-uboot", 0x00000, 0x20000},
136 {"partition-table", 0x20000, 0x02000},
137 {"default-mac", 0x30000, 0x00020},
138 {"product-info", 0x31100, 0x00100},
139 {"signature", 0x32000, 0x00400},
140 {"os-image", 0x40000, 0x170000},
141 {"soft-version", 0x1b0000, 0x00100},
142 {"support-list", 0x1b1000, 0x00400},
143 {"file-system", 0x1c0000, 0x600000},
144 {"user-config", 0x7c0000, 0x10000},
145 {"default-config", 0x7d0000, 0x10000},
146 {"log", 0x7e0000, 0x10000},
147 {"radio", 0x7f0000, 0x10000},
148 {NULL, 0, 0}
149 },
150
151 .first_sysupgrade_partition = "os-image",
152 .last_sysupgrade_partition = "file-system",
153 },
154
155 /** Firmware layout for the CPE510/520 */
156 {
157 .id = "CPE510",
158 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
159 .support_list =
160 "SupportList:\r\n"
161 "CPE510(TP-LINK|UN|N300-5):1.0\r\n"
162 "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
163 "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
164 "CPE510(TP-LINK|US|N300-5):1.1\r\n"
165 "CPE510(TP-LINK|EU|N300-5):1.1\r\n"
166 "CPE520(TP-LINK|UN|N300-5):1.1\r\n"
167 "CPE520(TP-LINK|US|N300-5):1.1\r\n"
168 "CPE520(TP-LINK|EU|N300-5):1.1\r\n",
169 .support_trail = '\xff',
170
171 .partitions = {
172 {"fs-uboot", 0x00000, 0x20000},
173 {"partition-table", 0x20000, 0x02000},
174 {"default-mac", 0x30000, 0x00020},
175 {"product-info", 0x31100, 0x00100},
176 {"signature", 0x32000, 0x00400},
177 {"os-image", 0x40000, 0x170000},
178 {"soft-version", 0x1b0000, 0x00100},
179 {"support-list", 0x1b1000, 0x00400},
180 {"file-system", 0x1c0000, 0x600000},
181 {"user-config", 0x7c0000, 0x10000},
182 {"default-config", 0x7d0000, 0x10000},
183 {"log", 0x7e0000, 0x10000},
184 {"radio", 0x7f0000, 0x10000},
185 {NULL, 0, 0}
186 },
187
188 .first_sysupgrade_partition = "os-image",
189 .last_sysupgrade_partition = "file-system",
190 },
191
192 {
193 .id = "WBS210",
194 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
195 .support_list =
196 "SupportList:\r\n"
197 "WBS210(TP-LINK|UN|N300-2):1.20\r\n"
198 "WBS210(TP-LINK|US|N300-2):1.20\r\n"
199 "WBS210(TP-LINK|EU|N300-2):1.20\r\n",
200 .support_trail = '\xff',
201
202 .partitions = {
203 {"fs-uboot", 0x00000, 0x20000},
204 {"partition-table", 0x20000, 0x02000},
205 {"default-mac", 0x30000, 0x00020},
206 {"product-info", 0x31100, 0x00100},
207 {"signature", 0x32000, 0x00400},
208 {"os-image", 0x40000, 0x170000},
209 {"soft-version", 0x1b0000, 0x00100},
210 {"support-list", 0x1b1000, 0x00400},
211 {"file-system", 0x1c0000, 0x600000},
212 {"user-config", 0x7c0000, 0x10000},
213 {"default-config", 0x7d0000, 0x10000},
214 {"log", 0x7e0000, 0x10000},
215 {"radio", 0x7f0000, 0x10000},
216 {NULL, 0, 0}
217 },
218
219 .first_sysupgrade_partition = "os-image",
220 .last_sysupgrade_partition = "file-system",
221 },
222
223 {
224 .id = "WBS510",
225 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
226 .support_list =
227 "SupportList:\r\n"
228 "WBS510(TP-LINK|UN|N300-5):1.20\r\n"
229 "WBS510(TP-LINK|US|N300-5):1.20\r\n"
230 "WBS510(TP-LINK|EU|N300-5):1.20\r\n",
231 .support_trail = '\xff',
232
233 .partitions = {
234 {"fs-uboot", 0x00000, 0x20000},
235 {"partition-table", 0x20000, 0x02000},
236 {"default-mac", 0x30000, 0x00020},
237 {"product-info", 0x31100, 0x00100},
238 {"signature", 0x32000, 0x00400},
239 {"os-image", 0x40000, 0x170000},
240 {"soft-version", 0x1b0000, 0x00100},
241 {"support-list", 0x1b1000, 0x00400},
242 {"file-system", 0x1c0000, 0x600000},
243 {"user-config", 0x7c0000, 0x10000},
244 {"default-config", 0x7d0000, 0x10000},
245 {"log", 0x7e0000, 0x10000},
246 {"radio", 0x7f0000, 0x10000},
247 {NULL, 0, 0}
248 },
249
250 .first_sysupgrade_partition = "os-image",
251 .last_sysupgrade_partition = "file-system",
252 },
253
254 /** Firmware layout for the C2600 */
255 {
256 .id = "C2600",
257 .vendor = "",
258 .support_list =
259 "SupportList:\r\n"
260 "{product_name:Archer C2600,product_ver:1.0.0,special_id:00000000}\r\n",
261 .support_trail = '\x00',
262
263 .partitions = {
264 {"SBL1", 0x00000, 0x20000},
265 {"MIBIB", 0x20000, 0x20000},
266 {"SBL2", 0x40000, 0x20000},
267 {"SBL3", 0x60000, 0x30000},
268 {"DDRCONFIG", 0x90000, 0x10000},
269 {"SSD", 0xa0000, 0x10000},
270 {"TZ", 0xb0000, 0x30000},
271 {"RPM", 0xe0000, 0x20000},
272 {"fs-uboot", 0x100000, 0x70000},
273 {"uboot-env", 0x170000, 0x40000},
274 {"radio", 0x1b0000, 0x40000},
275 {"os-image", 0x1f0000, 0x200000},
276 {"file-system", 0x3f0000, 0x1b00000},
277 {"default-mac", 0x1ef0000, 0x00200},
278 {"pin", 0x1ef0200, 0x00200},
279 {"product-info", 0x1ef0400, 0x0fc00},
280 {"partition-table", 0x1f00000, 0x10000},
281 {"soft-version", 0x1f10000, 0x10000},
282 {"support-list", 0x1f20000, 0x10000},
283 {"profile", 0x1f30000, 0x10000},
284 {"default-config", 0x1f40000, 0x10000},
285 {"user-config", 0x1f50000, 0x40000},
286 {"qos-db", 0x1f90000, 0x40000},
287 {"usb-config", 0x1fd0000, 0x10000},
288 {"log", 0x1fe0000, 0x20000},
289 {NULL, 0, 0}
290 },
291
292 .first_sysupgrade_partition = "os-image",
293 .last_sysupgrade_partition = "file-system"
294 },
295
296 /** Firmware layout for the C9 */
297 {
298 .id = "ARCHERC9",
299 .vendor = "",
300 .support_list =
301 "SupportList:\n"
302 "{product_name:ArcherC9,"
303 "product_ver:1.0.0,"
304 "special_id:00000000}\n",
305 .support_trail = '\x00',
306
307 .partitions = {
308 {"fs-uboot", 0x00000, 0x40000},
309 {"os-image", 0x40000, 0x200000},
310 {"file-system", 0x240000, 0xc00000},
311 {"default-mac", 0xe40000, 0x00200},
312 {"pin", 0xe40200, 0x00200},
313 {"product-info", 0xe40400, 0x00200},
314 {"partition-table", 0xe50000, 0x10000},
315 {"soft-version", 0xe60000, 0x00200},
316 {"support-list", 0xe61000, 0x0f000},
317 {"profile", 0xe70000, 0x10000},
318 {"default-config", 0xe80000, 0x10000},
319 {"user-config", 0xe90000, 0x50000},
320 {"log", 0xee0000, 0x100000},
321 {"radio_bk", 0xfe0000, 0x10000},
322 {"radio", 0xff0000, 0x10000},
323 {NULL, 0, 0}
324 },
325
326 .first_sysupgrade_partition = "os-image",
327 .last_sysupgrade_partition = "file-system"
328 },
329
330 /** Firmware layout for the EAP120 */
331 {
332 .id = "EAP120",
333 .vendor = "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
334 .support_list =
335 "SupportList:\r\n"
336 "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
337 .support_trail = '\xff',
338
339 .partitions = {
340 {"fs-uboot", 0x00000, 0x20000},
341 {"partition-table", 0x20000, 0x02000},
342 {"default-mac", 0x30000, 0x00020},
343 {"support-list", 0x31000, 0x00100},
344 {"product-info", 0x31100, 0x00100},
345 {"soft-version", 0x32000, 0x00100},
346 {"os-image", 0x40000, 0x180000},
347 {"file-system", 0x1c0000, 0x600000},
348 {"user-config", 0x7c0000, 0x10000},
349 {"backup-config", 0x7d0000, 0x10000},
350 {"log", 0x7e0000, 0x10000},
351 {"radio", 0x7f0000, 0x10000},
352 {NULL, 0, 0}
353 },
354
355 .first_sysupgrade_partition = "os-image",
356 .last_sysupgrade_partition = "file-system"
357 },
358
359 /** Firmware layout for the TL-WR1043 v4 */
360 {
361 .id = "TLWR1043NDV4",
362 .vendor = "",
363 .support_list =
364 "SupportList:\n"
365 "{product_name:TL-WR1043ND,product_ver:4.0.0,special_id:45550000}\n",
366 .support_trail = '\x00',
367
368 /**
369 We use a bigger os-image partition than the stock images (and thus
370 smaller file-system), as our kernel doesn't fit in the stock firmware's
371 1MB os-image.
372 */
373 .partitions = {
374 {"fs-uboot", 0x00000, 0x20000},
375 {"os-image", 0x20000, 0x180000},
376 {"file-system", 0x1a0000, 0xdb0000},
377 {"default-mac", 0xf50000, 0x00200},
378 {"pin", 0xf50200, 0x00200},
379 {"product-info", 0xf50400, 0x0fc00},
380 {"soft-version", 0xf60000, 0x0b000},
381 {"support-list", 0xf6b000, 0x04000},
382 {"profile", 0xf70000, 0x04000},
383 {"default-config", 0xf74000, 0x0b000},
384 {"user-config", 0xf80000, 0x40000},
385 {"partition-table", 0xfc0000, 0x10000},
386 {"log", 0xfd0000, 0x20000},
387 {"radio", 0xff0000, 0x10000},
388 {NULL, 0, 0}
389 },
390
391 .first_sysupgrade_partition = "os-image",
392 .last_sysupgrade_partition = "file-system"
393 },
394
395 /** Firmware layout for the RE450 */
396 {
397 .id = "RE450",
398 .vendor = "",
399 .support_list =
400 "SupportList:\r\n"
401 "{product_name:RE450,product_ver:1.0.0,special_id:00000000}\r\n"
402 "{product_name:RE450,product_ver:1.0.0,special_id:55530000}\r\n"
403 "{product_name:RE450,product_ver:1.0.0,special_id:45550000}\r\n"
404 "{product_name:RE450,product_ver:1.0.0,special_id:4A500000}\r\n"
405 "{product_name:RE450,product_ver:1.0.0,special_id:43410000}\r\n"
406 "{product_name:RE450,product_ver:1.0.0,special_id:41550000}\r\n"
407 "{product_name:RE450,product_ver:1.0.0,special_id:4B520000}\r\n"
408 "{product_name:RE450,product_ver:1.0.0,special_id:55534100}\r\n",
409 .support_trail = '\x00',
410
411 /**
412 The flash partition table for RE450;
413 it is almost the same as the one used by the stock images,
414 576KB were moved from file-system to os-image.
415 */
416 .partitions = {
417 {"fs-uboot", 0x00000, 0x20000},
418 {"os-image", 0x20000, 0x150000},
419 {"file-system", 0x170000, 0x4a0000},
420 {"partition-table", 0x600000, 0x02000},
421 {"default-mac", 0x610000, 0x00020},
422 {"pin", 0x610100, 0x00020},
423 {"product-info", 0x611100, 0x01000},
424 {"soft-version", 0x620000, 0x01000},
425 {"support-list", 0x621000, 0x01000},
426 {"profile", 0x622000, 0x08000},
427 {"user-config", 0x630000, 0x10000},
428 {"default-config", 0x640000, 0x10000},
429 {"radio", 0x7f0000, 0x10000},
430 {NULL, 0, 0}
431 },
432
433 .first_sysupgrade_partition = "os-image",
434 .last_sysupgrade_partition = "file-system"
435 },
436
437 {}
438 };
439
440 #define error(_ret, _errno, _str, ...) \
441 do { \
442 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__, \
443 strerror(_errno)); \
444 if (_ret) \
445 exit(_ret); \
446 } while (0)
447
448
449 /** Stores a uint32 as big endian */
450 static inline void put32(uint8_t *buf, uint32_t val) {
451 buf[0] = val >> 24;
452 buf[1] = val >> 16;
453 buf[2] = val >> 8;
454 buf[3] = val;
455 }
456
457 /** Allocates a new image partition */
458 static struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
459 struct image_partition_entry entry = {name, len, malloc(len)};
460 if (!entry.data)
461 error(1, errno, "malloc");
462
463 return entry;
464 }
465
466 /** Frees an image partition */
467 static void free_image_partition(struct image_partition_entry entry) {
468 free(entry.data);
469 }
470
471 /** Generates the partition-table partition */
472 static struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
473 struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
474
475 char *s = (char *)entry.data, *end = (char *)(s+entry.size);
476
477 *(s++) = 0x00;
478 *(s++) = 0x04;
479 *(s++) = 0x00;
480 *(s++) = 0x00;
481
482 size_t i;
483 for (i = 0; p[i].name; i++) {
484 size_t len = end-s;
485 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
486
487 if (w > len-1)
488 error(1, 0, "flash partition table overflow?");
489
490 s += w;
491 }
492
493 s++;
494
495 memset(s, 0xff, end-s);
496
497 return entry;
498 }
499
500
501 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
502 static inline uint8_t bcd(uint8_t v) {
503 return 0x10 * (v/10) + v%10;
504 }
505
506
507 /** Generates the soft-version partition */
508 static struct image_partition_entry make_soft_version(uint32_t rev) {
509 struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
510 struct soft_version *s = (struct soft_version *)entry.data;
511
512 time_t t;
513
514 if (time(&t) == (time_t)(-1))
515 error(1, errno, "time");
516
517 struct tm *tm = localtime(&t);
518
519 s->magic = htonl(0x0000000c);
520 s->zero = 0;
521 s->pad1 = 0xff;
522
523 s->version_major = 0;
524 s->version_minor = 0;
525 s->version_patch = 0;
526
527 s->year_hi = bcd((1900+tm->tm_year)/100);
528 s->year_lo = bcd(tm->tm_year%100);
529 s->month = bcd(tm->tm_mon+1);
530 s->day = bcd(tm->tm_mday);
531 s->rev = htonl(rev);
532
533 s->pad2 = 0xff;
534
535 return entry;
536 }
537
538 /** Generates the support-list partition */
539 static struct image_partition_entry make_support_list(const struct device_info *info) {
540 size_t len = strlen(info->support_list);
541 struct image_partition_entry entry = alloc_image_partition("support-list", len + 9);
542
543 put32(entry.data, len);
544 memset(entry.data+4, 0, 4);
545 memcpy(entry.data+8, info->support_list, len);
546 entry.data[len+8] = info->support_trail;
547
548 return entry;
549 }
550
551 /** Creates a new image partition with an arbitrary name from a file */
552 static struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
553 struct stat statbuf;
554
555 if (stat(filename, &statbuf) < 0)
556 error(1, errno, "unable to stat file `%s'", filename);
557
558 size_t len = statbuf.st_size;
559
560 if (add_jffs2_eof)
561 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
562
563 struct image_partition_entry entry = alloc_image_partition(part_name, len);
564
565 FILE *file = fopen(filename, "rb");
566 if (!file)
567 error(1, errno, "unable to open file `%s'", filename);
568
569 if (fread(entry.data, statbuf.st_size, 1, file) != 1)
570 error(1, errno, "unable to read file `%s'", filename);
571
572 if (add_jffs2_eof) {
573 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
574
575 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
576 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
577 }
578
579 fclose(file);
580
581 return entry;
582 }
583
584
585 /**
586 Copies a list of image partitions into an image buffer and generates the image partition table while doing so
587
588 Example image partition table:
589
590 fwup-ptn partition-table base 0x00800 size 0x00800
591 fwup-ptn os-image base 0x01000 size 0x113b45
592 fwup-ptn file-system base 0x114b45 size 0x1d0004
593 fwup-ptn support-list base 0x2e4b49 size 0x000d1
594
595 Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
596 the end of the partition table is marked with a zero byte.
597
598 The firmware image must contain at least the partition-table and support-list partitions
599 to be accepted. There aren't any alignment constraints for the image partitions.
600
601 The partition-table partition contains the actual flash layout; partitions
602 from the image partition table are mapped to the corresponding flash partitions during
603 the firmware upgrade. The support-list partition contains a list of devices supported by
604 the firmware image.
605
606 The base offsets in the firmware partition table are relative to the end
607 of the vendor information block, so the partition-table partition will
608 actually start at offset 0x1814 of the image.
609
610 I think partition-table must be the first partition in the firmware image.
611 */
612 static void put_partitions(uint8_t *buffer, const struct flash_partition_entry *flash_parts, const struct image_partition_entry *parts) {
613 size_t i, j;
614 char *image_pt = (char *)buffer, *end = image_pt + 0x800;
615
616 size_t base = 0x800;
617 for (i = 0; parts[i].name; i++) {
618 for (j = 0; flash_parts[j].name; j++) {
619 if (!strcmp(flash_parts[j].name, parts[i].name)) {
620 if (parts[i].size > flash_parts[j].size)
621 error(1, 0, "%s partition too big (more than %u bytes)", flash_parts[j].name, (unsigned)flash_parts[j].size);
622 break;
623 }
624 }
625
626 assert(flash_parts[j].name);
627
628 memcpy(buffer + base, parts[i].data, parts[i].size);
629
630 size_t len = end-image_pt;
631 size_t w = snprintf(image_pt, len, "fwup-ptn %s base 0x%05x size 0x%05x\t\r\n", parts[i].name, (unsigned)base, (unsigned)parts[i].size);
632
633 if (w > len-1)
634 error(1, 0, "image partition table overflow?");
635
636 image_pt += w;
637
638 base += parts[i].size;
639 }
640 }
641
642 /** Generates and writes the image MD5 checksum */
643 static void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
644 MD5_CTX ctx;
645
646 MD5_Init(&ctx);
647 MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
648 MD5_Update(&ctx, buffer, len);
649 MD5_Final(md5, &ctx);
650 }
651
652
653 /**
654 Generates the firmware image in factory format
655
656 Image format:
657
658 Bytes (hex) Usage
659 ----------- -----
660 0000-0003 Image size (4 bytes, big endian)
661 0004-0013 MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
662 0014-0017 Vendor information length (without padding) (4 bytes, big endian)
663 0018-1013 Vendor information (4092 bytes, padded with 0xff; there seem to be older
664 (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
665 1014-1813 Image partition table (2048 bytes, padded with 0xff)
666 1814-xxxx Firmware partitions
667 */
668 static void * generate_factory_image(const struct device_info *info, const struct image_partition_entry *parts, size_t *len) {
669 *len = 0x1814;
670
671 size_t i;
672 for (i = 0; parts[i].name; i++)
673 *len += parts[i].size;
674
675 uint8_t *image = malloc(*len);
676 if (!image)
677 error(1, errno, "malloc");
678
679 memset(image, 0xff, *len);
680 put32(image, *len);
681
682 if (info->vendor) {
683 size_t vendor_len = strlen(info->vendor);
684 put32(image+0x14, vendor_len);
685 memcpy(image+0x18, info->vendor, vendor_len);
686 }
687
688 put_partitions(image + 0x1014, info->partitions, parts);
689 put_md5(image+0x04, image+0x14, *len-0x14);
690
691 return image;
692 }
693
694 /**
695 Generates the firmware image in sysupgrade format
696
697 This makes some assumptions about the provided flash and image partition tables and
698 should be generalized when TP-LINK starts building its safeloader into hardware with
699 different flash layouts.
700 */
701 static void * generate_sysupgrade_image(const struct device_info *info, const struct image_partition_entry *image_parts, size_t *len) {
702 size_t i, j;
703 size_t flash_first_partition_index = 0;
704 size_t flash_last_partition_index = 0;
705 const struct flash_partition_entry *flash_first_partition = NULL;
706 const struct flash_partition_entry *flash_last_partition = NULL;
707 const struct image_partition_entry *image_last_partition = NULL;
708
709 /** Find first and last partitions */
710 for (i = 0; info->partitions[i].name; i++) {
711 if (!strcmp(info->partitions[i].name, info->first_sysupgrade_partition)) {
712 flash_first_partition = &info->partitions[i];
713 flash_first_partition_index = i;
714 } else if (!strcmp(info->partitions[i].name, info->last_sysupgrade_partition)) {
715 flash_last_partition = &info->partitions[i];
716 flash_last_partition_index = i;
717 }
718 }
719
720 assert(flash_first_partition && flash_last_partition);
721 assert(flash_first_partition_index < flash_last_partition_index);
722
723 /** Find last partition from image to calculate needed size */
724 for (i = 0; image_parts[i].name; i++) {
725 if (!strcmp(image_parts[i].name, info->last_sysupgrade_partition)) {
726 image_last_partition = &image_parts[i];
727 break;
728 }
729 }
730
731 assert(image_last_partition);
732
733 *len = flash_last_partition->base - flash_first_partition->base + image_last_partition->size;
734
735 uint8_t *image = malloc(*len);
736 if (!image)
737 error(1, errno, "malloc");
738
739 memset(image, 0xff, *len);
740
741 for (i = flash_first_partition_index; i <= flash_last_partition_index; i++) {
742 for (j = 0; image_parts[j].name; j++) {
743 if (!strcmp(info->partitions[i].name, image_parts[j].name)) {
744 if (image_parts[j].size > info->partitions[i].size)
745 error(1, 0, "%s partition too big (more than %u bytes)", info->partitions[i].name, (unsigned)info->partitions[i].size);
746 memcpy(image + info->partitions[i].base - flash_first_partition->base, image_parts[j].data, image_parts[j].size);
747 break;
748 }
749
750 assert(image_parts[j].name);
751 }
752 }
753
754 return image;
755 }
756
757 /** Generates an image according to a given layout and writes it to a file */
758 static void build_image(const char *output,
759 const char *kernel_image,
760 const char *rootfs_image,
761 uint32_t rev,
762 bool add_jffs2_eof,
763 bool sysupgrade,
764 const struct device_info *info) {
765 struct image_partition_entry parts[6] = {};
766
767 parts[0] = make_partition_table(info->partitions);
768 parts[1] = make_soft_version(rev);
769 parts[2] = make_support_list(info);
770 parts[3] = read_file("os-image", kernel_image, false);
771 parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
772
773 size_t len;
774 void *image;
775 if (sysupgrade)
776 image = generate_sysupgrade_image(info, parts, &len);
777 else
778 image = generate_factory_image(info, parts, &len);
779
780 FILE *file = fopen(output, "wb");
781 if (!file)
782 error(1, errno, "unable to open output file");
783
784 if (fwrite(image, len, 1, file) != 1)
785 error(1, 0, "unable to write output file");
786
787 fclose(file);
788
789 free(image);
790
791 size_t i;
792 for (i = 0; parts[i].name; i++)
793 free_image_partition(parts[i]);
794 }
795
796 /** Usage output */
797 static void usage(const char *argv0) {
798 fprintf(stderr,
799 "Usage: %s [OPTIONS...]\n"
800 "\n"
801 "Options:\n"
802 " -B <board> create image for the board specified with <board>\n"
803 " -k <file> read kernel image from the file <file>\n"
804 " -r <file> read rootfs image from the file <file>\n"
805 " -o <file> write output to the file <file>\n"
806 " -V <rev> sets the revision number to <rev>\n"
807 " -j add jffs2 end-of-filesystem markers\n"
808 " -S create sysupgrade instead of factory image\n"
809 " -h show this help\n",
810 argv0
811 );
812 };
813
814
815 static const struct device_info *find_board(const char *id)
816 {
817 struct device_info *board = NULL;
818
819 for (board = boards; board->id != NULL; board++)
820 if (strcasecmp(id, board->id) == 0)
821 return board;
822
823 return NULL;
824 }
825
826 int main(int argc, char *argv[]) {
827 const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
828 bool add_jffs2_eof = false, sysupgrade = false;
829 unsigned rev = 0;
830 const struct device_info *info;
831
832 while (true) {
833 int c;
834
835 c = getopt(argc, argv, "B:k:r:o:V:jSh");
836 if (c == -1)
837 break;
838
839 switch (c) {
840 case 'B':
841 board = optarg;
842 break;
843
844 case 'k':
845 kernel_image = optarg;
846 break;
847
848 case 'r':
849 rootfs_image = optarg;
850 break;
851
852 case 'o':
853 output = optarg;
854 break;
855
856 case 'V':
857 sscanf(optarg, "r%u", &rev);
858 break;
859
860 case 'j':
861 add_jffs2_eof = true;
862 break;
863
864 case 'S':
865 sysupgrade = true;
866 break;
867
868 case 'h':
869 usage(argv[0]);
870 return 0;
871
872 default:
873 usage(argv[0]);
874 return 1;
875 }
876 }
877
878 if (!board)
879 error(1, 0, "no board has been specified");
880 if (!kernel_image)
881 error(1, 0, "no kernel image has been specified");
882 if (!rootfs_image)
883 error(1, 0, "no rootfs image has been specified");
884 if (!output)
885 error(1, 0, "no output filename has been specified");
886
887 info = find_board(board);
888
889 if (info == NULL)
890 error(1, 0, "unsupported board %s", board);
891
892 build_image(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade, info);
893
894 return 0;
895 }