firmware-utils: tplink-safeloader: add support for Archer C5 V2
[openwrt/openwrt.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 C5 */
297 {
298 .id = "ARCHER-C5-V2",
299 .vendor = "",
300 .support_list =
301 "SupportList:\r\n"
302 "{product_name:ArcherC5,"
303 "product_ver:2.0.0,"
304 "special_id:00000000}\r\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 C9 */
331 {
332 .id = "ARCHERC9",
333 .vendor = "",
334 .support_list =
335 "SupportList:\n"
336 "{product_name:ArcherC9,"
337 "product_ver:1.0.0,"
338 "special_id:00000000}\n",
339 .support_trail = '\x00',
340
341 .partitions = {
342 {"fs-uboot", 0x00000, 0x40000},
343 {"os-image", 0x40000, 0x200000},
344 {"file-system", 0x240000, 0xc00000},
345 {"default-mac", 0xe40000, 0x00200},
346 {"pin", 0xe40200, 0x00200},
347 {"product-info", 0xe40400, 0x00200},
348 {"partition-table", 0xe50000, 0x10000},
349 {"soft-version", 0xe60000, 0x00200},
350 {"support-list", 0xe61000, 0x0f000},
351 {"profile", 0xe70000, 0x10000},
352 {"default-config", 0xe80000, 0x10000},
353 {"user-config", 0xe90000, 0x50000},
354 {"log", 0xee0000, 0x100000},
355 {"radio_bk", 0xfe0000, 0x10000},
356 {"radio", 0xff0000, 0x10000},
357 {NULL, 0, 0}
358 },
359
360 .first_sysupgrade_partition = "os-image",
361 .last_sysupgrade_partition = "file-system"
362 },
363
364 /** Firmware layout for the EAP120 */
365 {
366 .id = "EAP120",
367 .vendor = "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
368 .support_list =
369 "SupportList:\r\n"
370 "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
371 .support_trail = '\xff',
372
373 .partitions = {
374 {"fs-uboot", 0x00000, 0x20000},
375 {"partition-table", 0x20000, 0x02000},
376 {"default-mac", 0x30000, 0x00020},
377 {"support-list", 0x31000, 0x00100},
378 {"product-info", 0x31100, 0x00100},
379 {"soft-version", 0x32000, 0x00100},
380 {"os-image", 0x40000, 0x180000},
381 {"file-system", 0x1c0000, 0x600000},
382 {"user-config", 0x7c0000, 0x10000},
383 {"backup-config", 0x7d0000, 0x10000},
384 {"log", 0x7e0000, 0x10000},
385 {"radio", 0x7f0000, 0x10000},
386 {NULL, 0, 0}
387 },
388
389 .first_sysupgrade_partition = "os-image",
390 .last_sysupgrade_partition = "file-system"
391 },
392
393 /** Firmware layout for the TL-WR1043 v4 */
394 {
395 .id = "TLWR1043NDV4",
396 .vendor = "",
397 .support_list =
398 "SupportList:\n"
399 "{product_name:TL-WR1043ND,product_ver:4.0.0,special_id:45550000}\n",
400 .support_trail = '\x00',
401
402 /**
403 We use a bigger os-image partition than the stock images (and thus
404 smaller file-system), as our kernel doesn't fit in the stock firmware's
405 1MB os-image.
406 */
407 .partitions = {
408 {"fs-uboot", 0x00000, 0x20000},
409 {"os-image", 0x20000, 0x180000},
410 {"file-system", 0x1a0000, 0xdb0000},
411 {"default-mac", 0xf50000, 0x00200},
412 {"pin", 0xf50200, 0x00200},
413 {"product-info", 0xf50400, 0x0fc00},
414 {"soft-version", 0xf60000, 0x0b000},
415 {"support-list", 0xf6b000, 0x04000},
416 {"profile", 0xf70000, 0x04000},
417 {"default-config", 0xf74000, 0x0b000},
418 {"user-config", 0xf80000, 0x40000},
419 {"partition-table", 0xfc0000, 0x10000},
420 {"log", 0xfd0000, 0x20000},
421 {"radio", 0xff0000, 0x10000},
422 {NULL, 0, 0}
423 },
424
425 .first_sysupgrade_partition = "os-image",
426 .last_sysupgrade_partition = "file-system"
427 },
428
429 /** Firmware layout for the RE450 */
430 {
431 .id = "RE450",
432 .vendor = "",
433 .support_list =
434 "SupportList:\r\n"
435 "{product_name:RE450,product_ver:1.0.0,special_id:00000000}\r\n"
436 "{product_name:RE450,product_ver:1.0.0,special_id:55530000}\r\n"
437 "{product_name:RE450,product_ver:1.0.0,special_id:45550000}\r\n"
438 "{product_name:RE450,product_ver:1.0.0,special_id:4A500000}\r\n"
439 "{product_name:RE450,product_ver:1.0.0,special_id:43410000}\r\n"
440 "{product_name:RE450,product_ver:1.0.0,special_id:41550000}\r\n"
441 "{product_name:RE450,product_ver:1.0.0,special_id:4B520000}\r\n"
442 "{product_name:RE450,product_ver:1.0.0,special_id:55534100}\r\n",
443 .support_trail = '\x00',
444
445 /**
446 The flash partition table for RE450;
447 it is almost the same as the one used by the stock images,
448 576KB were moved from file-system to os-image.
449 */
450 .partitions = {
451 {"fs-uboot", 0x00000, 0x20000},
452 {"os-image", 0x20000, 0x150000},
453 {"file-system", 0x170000, 0x4a0000},
454 {"partition-table", 0x600000, 0x02000},
455 {"default-mac", 0x610000, 0x00020},
456 {"pin", 0x610100, 0x00020},
457 {"product-info", 0x611100, 0x01000},
458 {"soft-version", 0x620000, 0x01000},
459 {"support-list", 0x621000, 0x01000},
460 {"profile", 0x622000, 0x08000},
461 {"user-config", 0x630000, 0x10000},
462 {"default-config", 0x640000, 0x10000},
463 {"radio", 0x7f0000, 0x10000},
464 {NULL, 0, 0}
465 },
466
467 .first_sysupgrade_partition = "os-image",
468 .last_sysupgrade_partition = "file-system"
469 },
470
471 {}
472 };
473
474 #define error(_ret, _errno, _str, ...) \
475 do { \
476 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__, \
477 strerror(_errno)); \
478 if (_ret) \
479 exit(_ret); \
480 } while (0)
481
482
483 /** Stores a uint32 as big endian */
484 static inline void put32(uint8_t *buf, uint32_t val) {
485 buf[0] = val >> 24;
486 buf[1] = val >> 16;
487 buf[2] = val >> 8;
488 buf[3] = val;
489 }
490
491 /** Allocates a new image partition */
492 static struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
493 struct image_partition_entry entry = {name, len, malloc(len)};
494 if (!entry.data)
495 error(1, errno, "malloc");
496
497 return entry;
498 }
499
500 /** Frees an image partition */
501 static void free_image_partition(struct image_partition_entry entry) {
502 free(entry.data);
503 }
504
505 /** Generates the partition-table partition */
506 static struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
507 struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
508
509 char *s = (char *)entry.data, *end = (char *)(s+entry.size);
510
511 *(s++) = 0x00;
512 *(s++) = 0x04;
513 *(s++) = 0x00;
514 *(s++) = 0x00;
515
516 size_t i;
517 for (i = 0; p[i].name; i++) {
518 size_t len = end-s;
519 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
520
521 if (w > len-1)
522 error(1, 0, "flash partition table overflow?");
523
524 s += w;
525 }
526
527 s++;
528
529 memset(s, 0xff, end-s);
530
531 return entry;
532 }
533
534
535 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
536 static inline uint8_t bcd(uint8_t v) {
537 return 0x10 * (v/10) + v%10;
538 }
539
540
541 /** Generates the soft-version partition */
542 static struct image_partition_entry make_soft_version(uint32_t rev) {
543 struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
544 struct soft_version *s = (struct soft_version *)entry.data;
545
546 time_t t;
547
548 if (time(&t) == (time_t)(-1))
549 error(1, errno, "time");
550
551 struct tm *tm = localtime(&t);
552
553 s->magic = htonl(0x0000000c);
554 s->zero = 0;
555 s->pad1 = 0xff;
556
557 s->version_major = 0;
558 s->version_minor = 0;
559 s->version_patch = 0;
560
561 s->year_hi = bcd((1900+tm->tm_year)/100);
562 s->year_lo = bcd(tm->tm_year%100);
563 s->month = bcd(tm->tm_mon+1);
564 s->day = bcd(tm->tm_mday);
565 s->rev = htonl(rev);
566
567 s->pad2 = 0xff;
568
569 return entry;
570 }
571
572 /** Generates the support-list partition */
573 static struct image_partition_entry make_support_list(const struct device_info *info) {
574 size_t len = strlen(info->support_list);
575 struct image_partition_entry entry = alloc_image_partition("support-list", len + 9);
576
577 put32(entry.data, len);
578 memset(entry.data+4, 0, 4);
579 memcpy(entry.data+8, info->support_list, len);
580 entry.data[len+8] = info->support_trail;
581
582 return entry;
583 }
584
585 /** Creates a new image partition with an arbitrary name from a file */
586 static struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
587 struct stat statbuf;
588
589 if (stat(filename, &statbuf) < 0)
590 error(1, errno, "unable to stat file `%s'", filename);
591
592 size_t len = statbuf.st_size;
593
594 if (add_jffs2_eof)
595 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
596
597 struct image_partition_entry entry = alloc_image_partition(part_name, len);
598
599 FILE *file = fopen(filename, "rb");
600 if (!file)
601 error(1, errno, "unable to open file `%s'", filename);
602
603 if (fread(entry.data, statbuf.st_size, 1, file) != 1)
604 error(1, errno, "unable to read file `%s'", filename);
605
606 if (add_jffs2_eof) {
607 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
608
609 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
610 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
611 }
612
613 fclose(file);
614
615 return entry;
616 }
617
618
619 /**
620 Copies a list of image partitions into an image buffer and generates the image partition table while doing so
621
622 Example image partition table:
623
624 fwup-ptn partition-table base 0x00800 size 0x00800
625 fwup-ptn os-image base 0x01000 size 0x113b45
626 fwup-ptn file-system base 0x114b45 size 0x1d0004
627 fwup-ptn support-list base 0x2e4b49 size 0x000d1
628
629 Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
630 the end of the partition table is marked with a zero byte.
631
632 The firmware image must contain at least the partition-table and support-list partitions
633 to be accepted. There aren't any alignment constraints for the image partitions.
634
635 The partition-table partition contains the actual flash layout; partitions
636 from the image partition table are mapped to the corresponding flash partitions during
637 the firmware upgrade. The support-list partition contains a list of devices supported by
638 the firmware image.
639
640 The base offsets in the firmware partition table are relative to the end
641 of the vendor information block, so the partition-table partition will
642 actually start at offset 0x1814 of the image.
643
644 I think partition-table must be the first partition in the firmware image.
645 */
646 static void put_partitions(uint8_t *buffer, const struct flash_partition_entry *flash_parts, const struct image_partition_entry *parts) {
647 size_t i, j;
648 char *image_pt = (char *)buffer, *end = image_pt + 0x800;
649
650 size_t base = 0x800;
651 for (i = 0; parts[i].name; i++) {
652 for (j = 0; flash_parts[j].name; j++) {
653 if (!strcmp(flash_parts[j].name, parts[i].name)) {
654 if (parts[i].size > flash_parts[j].size)
655 error(1, 0, "%s partition too big (more than %u bytes)", flash_parts[j].name, (unsigned)flash_parts[j].size);
656 break;
657 }
658 }
659
660 assert(flash_parts[j].name);
661
662 memcpy(buffer + base, parts[i].data, parts[i].size);
663
664 size_t len = end-image_pt;
665 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);
666
667 if (w > len-1)
668 error(1, 0, "image partition table overflow?");
669
670 image_pt += w;
671
672 base += parts[i].size;
673 }
674 }
675
676 /** Generates and writes the image MD5 checksum */
677 static void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
678 MD5_CTX ctx;
679
680 MD5_Init(&ctx);
681 MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
682 MD5_Update(&ctx, buffer, len);
683 MD5_Final(md5, &ctx);
684 }
685
686
687 /**
688 Generates the firmware image in factory format
689
690 Image format:
691
692 Bytes (hex) Usage
693 ----------- -----
694 0000-0003 Image size (4 bytes, big endian)
695 0004-0013 MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
696 0014-0017 Vendor information length (without padding) (4 bytes, big endian)
697 0018-1013 Vendor information (4092 bytes, padded with 0xff; there seem to be older
698 (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
699 1014-1813 Image partition table (2048 bytes, padded with 0xff)
700 1814-xxxx Firmware partitions
701 */
702 static void * generate_factory_image(const struct device_info *info, const struct image_partition_entry *parts, size_t *len) {
703 *len = 0x1814;
704
705 size_t i;
706 for (i = 0; parts[i].name; i++)
707 *len += parts[i].size;
708
709 uint8_t *image = malloc(*len);
710 if (!image)
711 error(1, errno, "malloc");
712
713 memset(image, 0xff, *len);
714 put32(image, *len);
715
716 if (info->vendor) {
717 size_t vendor_len = strlen(info->vendor);
718 put32(image+0x14, vendor_len);
719 memcpy(image+0x18, info->vendor, vendor_len);
720 }
721
722 put_partitions(image + 0x1014, info->partitions, parts);
723 put_md5(image+0x04, image+0x14, *len-0x14);
724
725 return image;
726 }
727
728 /**
729 Generates the firmware image in sysupgrade format
730
731 This makes some assumptions about the provided flash and image partition tables and
732 should be generalized when TP-LINK starts building its safeloader into hardware with
733 different flash layouts.
734 */
735 static void * generate_sysupgrade_image(const struct device_info *info, const struct image_partition_entry *image_parts, size_t *len) {
736 size_t i, j;
737 size_t flash_first_partition_index = 0;
738 size_t flash_last_partition_index = 0;
739 const struct flash_partition_entry *flash_first_partition = NULL;
740 const struct flash_partition_entry *flash_last_partition = NULL;
741 const struct image_partition_entry *image_last_partition = NULL;
742
743 /** Find first and last partitions */
744 for (i = 0; info->partitions[i].name; i++) {
745 if (!strcmp(info->partitions[i].name, info->first_sysupgrade_partition)) {
746 flash_first_partition = &info->partitions[i];
747 flash_first_partition_index = i;
748 } else if (!strcmp(info->partitions[i].name, info->last_sysupgrade_partition)) {
749 flash_last_partition = &info->partitions[i];
750 flash_last_partition_index = i;
751 }
752 }
753
754 assert(flash_first_partition && flash_last_partition);
755 assert(flash_first_partition_index < flash_last_partition_index);
756
757 /** Find last partition from image to calculate needed size */
758 for (i = 0; image_parts[i].name; i++) {
759 if (!strcmp(image_parts[i].name, info->last_sysupgrade_partition)) {
760 image_last_partition = &image_parts[i];
761 break;
762 }
763 }
764
765 assert(image_last_partition);
766
767 *len = flash_last_partition->base - flash_first_partition->base + image_last_partition->size;
768
769 uint8_t *image = malloc(*len);
770 if (!image)
771 error(1, errno, "malloc");
772
773 memset(image, 0xff, *len);
774
775 for (i = flash_first_partition_index; i <= flash_last_partition_index; i++) {
776 for (j = 0; image_parts[j].name; j++) {
777 if (!strcmp(info->partitions[i].name, image_parts[j].name)) {
778 if (image_parts[j].size > info->partitions[i].size)
779 error(1, 0, "%s partition too big (more than %u bytes)", info->partitions[i].name, (unsigned)info->partitions[i].size);
780 memcpy(image + info->partitions[i].base - flash_first_partition->base, image_parts[j].data, image_parts[j].size);
781 break;
782 }
783
784 assert(image_parts[j].name);
785 }
786 }
787
788 return image;
789 }
790
791 /** Generates an image according to a given layout and writes it to a file */
792 static void build_image(const char *output,
793 const char *kernel_image,
794 const char *rootfs_image,
795 uint32_t rev,
796 bool add_jffs2_eof,
797 bool sysupgrade,
798 const struct device_info *info) {
799 struct image_partition_entry parts[6] = {};
800
801 parts[0] = make_partition_table(info->partitions);
802 parts[1] = make_soft_version(rev);
803 parts[2] = make_support_list(info);
804 parts[3] = read_file("os-image", kernel_image, false);
805 parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
806
807 size_t len;
808 void *image;
809 if (sysupgrade)
810 image = generate_sysupgrade_image(info, parts, &len);
811 else
812 image = generate_factory_image(info, parts, &len);
813
814 FILE *file = fopen(output, "wb");
815 if (!file)
816 error(1, errno, "unable to open output file");
817
818 if (fwrite(image, len, 1, file) != 1)
819 error(1, 0, "unable to write output file");
820
821 fclose(file);
822
823 free(image);
824
825 size_t i;
826 for (i = 0; parts[i].name; i++)
827 free_image_partition(parts[i]);
828 }
829
830 /** Usage output */
831 static void usage(const char *argv0) {
832 fprintf(stderr,
833 "Usage: %s [OPTIONS...]\n"
834 "\n"
835 "Options:\n"
836 " -B <board> create image for the board specified with <board>\n"
837 " -k <file> read kernel image from the file <file>\n"
838 " -r <file> read rootfs image from the file <file>\n"
839 " -o <file> write output to the file <file>\n"
840 " -V <rev> sets the revision number to <rev>\n"
841 " -j add jffs2 end-of-filesystem markers\n"
842 " -S create sysupgrade instead of factory image\n"
843 " -h show this help\n",
844 argv0
845 );
846 };
847
848
849 static const struct device_info *find_board(const char *id)
850 {
851 struct device_info *board = NULL;
852
853 for (board = boards; board->id != NULL; board++)
854 if (strcasecmp(id, board->id) == 0)
855 return board;
856
857 return NULL;
858 }
859
860 int main(int argc, char *argv[]) {
861 const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
862 bool add_jffs2_eof = false, sysupgrade = false;
863 unsigned rev = 0;
864 const struct device_info *info;
865
866 while (true) {
867 int c;
868
869 c = getopt(argc, argv, "B:k:r:o:V:jSh");
870 if (c == -1)
871 break;
872
873 switch (c) {
874 case 'B':
875 board = optarg;
876 break;
877
878 case 'k':
879 kernel_image = optarg;
880 break;
881
882 case 'r':
883 rootfs_image = optarg;
884 break;
885
886 case 'o':
887 output = optarg;
888 break;
889
890 case 'V':
891 sscanf(optarg, "r%u", &rev);
892 break;
893
894 case 'j':
895 add_jffs2_eof = true;
896 break;
897
898 case 'S':
899 sysupgrade = true;
900 break;
901
902 case 'h':
903 usage(argv[0]);
904 return 0;
905
906 default:
907 usage(argv[0]);
908 return 1;
909 }
910 }
911
912 if (!board)
913 error(1, 0, "no board has been specified");
914 if (!kernel_image)
915 error(1, 0, "no kernel image has been specified");
916 if (!rootfs_image)
917 error(1, 0, "no rootfs image has been specified");
918 if (!output)
919 error(1, 0, "no output filename has been specified");
920
921 info = find_board(board);
922
923 if (info == NULL)
924 error(1, 0, "unsupported board %s", board);
925
926 build_image(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade, info);
927
928 return 0;
929 }