firmware-utils: honor env SOURCE_DATE_EPOCH
[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 char *soft_ver;
79 const struct flash_partition_entry partitions[MAX_PARTITIONS+1];
80 const char *first_sysupgrade_partition;
81 const char *last_sysupgrade_partition;
82 };
83
84 /** The content of the soft-version structure */
85 struct __attribute__((__packed__)) soft_version {
86 uint32_t magic;
87 uint32_t zero;
88 uint8_t pad1;
89 uint8_t version_major;
90 uint8_t version_minor;
91 uint8_t version_patch;
92 uint8_t year_hi;
93 uint8_t year_lo;
94 uint8_t month;
95 uint8_t day;
96 uint32_t rev;
97 uint8_t pad2;
98 };
99
100
101 static const uint8_t jffs2_eof_mark[4] = {0xde, 0xad, 0xc0, 0xde};
102
103
104 /**
105 Salt for the MD5 hash
106
107 Fortunately, TP-LINK seems to use the same salt for most devices which use
108 the new image format.
109 */
110 static const uint8_t md5_salt[16] = {
111 0x7a, 0x2b, 0x15, 0xed,
112 0x9b, 0x98, 0x59, 0x6d,
113 0xe5, 0x04, 0xab, 0x44,
114 0xac, 0x2a, 0x9f, 0x4e,
115 };
116
117
118 /** Firmware layout table */
119 static struct device_info boards[] = {
120 /** Firmware layout for the CPE210/220 */
121 {
122 .id = "CPE210",
123 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
124 .support_list =
125 "SupportList:\r\n"
126 "CPE210(TP-LINK|UN|N300-2):1.0\r\n"
127 "CPE210(TP-LINK|UN|N300-2):1.1\r\n"
128 "CPE210(TP-LINK|US|N300-2):1.1\r\n"
129 "CPE210(TP-LINK|EU|N300-2):1.1\r\n"
130 "CPE220(TP-LINK|UN|N300-2):1.1\r\n"
131 "CPE220(TP-LINK|US|N300-2):1.1\r\n"
132 "CPE220(TP-LINK|EU|N300-2):1.1\r\n",
133 .support_trail = '\xff',
134 .soft_ver = NULL,
135
136 .partitions = {
137 {"fs-uboot", 0x00000, 0x20000},
138 {"partition-table", 0x20000, 0x02000},
139 {"default-mac", 0x30000, 0x00020},
140 {"product-info", 0x31100, 0x00100},
141 {"signature", 0x32000, 0x00400},
142 {"os-image", 0x40000, 0x170000},
143 {"soft-version", 0x1b0000, 0x00100},
144 {"support-list", 0x1b1000, 0x00400},
145 {"file-system", 0x1c0000, 0x600000},
146 {"user-config", 0x7c0000, 0x10000},
147 {"default-config", 0x7d0000, 0x10000},
148 {"log", 0x7e0000, 0x10000},
149 {"radio", 0x7f0000, 0x10000},
150 {NULL, 0, 0}
151 },
152
153 .first_sysupgrade_partition = "os-image",
154 .last_sysupgrade_partition = "file-system",
155 },
156
157 /** Firmware layout for the CPE510/520 */
158 {
159 .id = "CPE510",
160 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
161 .support_list =
162 "SupportList:\r\n"
163 "CPE510(TP-LINK|UN|N300-5):1.0\r\n"
164 "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
165 "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
166 "CPE510(TP-LINK|US|N300-5):1.1\r\n"
167 "CPE510(TP-LINK|EU|N300-5):1.1\r\n"
168 "CPE520(TP-LINK|UN|N300-5):1.1\r\n"
169 "CPE520(TP-LINK|US|N300-5):1.1\r\n"
170 "CPE520(TP-LINK|EU|N300-5):1.1\r\n",
171 .support_trail = '\xff',
172 .soft_ver = NULL,
173
174 .partitions = {
175 {"fs-uboot", 0x00000, 0x20000},
176 {"partition-table", 0x20000, 0x02000},
177 {"default-mac", 0x30000, 0x00020},
178 {"product-info", 0x31100, 0x00100},
179 {"signature", 0x32000, 0x00400},
180 {"os-image", 0x40000, 0x170000},
181 {"soft-version", 0x1b0000, 0x00100},
182 {"support-list", 0x1b1000, 0x00400},
183 {"file-system", 0x1c0000, 0x600000},
184 {"user-config", 0x7c0000, 0x10000},
185 {"default-config", 0x7d0000, 0x10000},
186 {"log", 0x7e0000, 0x10000},
187 {"radio", 0x7f0000, 0x10000},
188 {NULL, 0, 0}
189 },
190
191 .first_sysupgrade_partition = "os-image",
192 .last_sysupgrade_partition = "file-system",
193 },
194
195 {
196 .id = "WBS210",
197 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
198 .support_list =
199 "SupportList:\r\n"
200 "WBS210(TP-LINK|UN|N300-2):1.20\r\n"
201 "WBS210(TP-LINK|US|N300-2):1.20\r\n"
202 "WBS210(TP-LINK|EU|N300-2):1.20\r\n",
203 .support_trail = '\xff',
204 .soft_ver = NULL,
205
206 .partitions = {
207 {"fs-uboot", 0x00000, 0x20000},
208 {"partition-table", 0x20000, 0x02000},
209 {"default-mac", 0x30000, 0x00020},
210 {"product-info", 0x31100, 0x00100},
211 {"signature", 0x32000, 0x00400},
212 {"os-image", 0x40000, 0x170000},
213 {"soft-version", 0x1b0000, 0x00100},
214 {"support-list", 0x1b1000, 0x00400},
215 {"file-system", 0x1c0000, 0x600000},
216 {"user-config", 0x7c0000, 0x10000},
217 {"default-config", 0x7d0000, 0x10000},
218 {"log", 0x7e0000, 0x10000},
219 {"radio", 0x7f0000, 0x10000},
220 {NULL, 0, 0}
221 },
222
223 .first_sysupgrade_partition = "os-image",
224 .last_sysupgrade_partition = "file-system",
225 },
226
227 {
228 .id = "WBS510",
229 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
230 .support_list =
231 "SupportList:\r\n"
232 "WBS510(TP-LINK|UN|N300-5):1.20\r\n"
233 "WBS510(TP-LINK|US|N300-5):1.20\r\n"
234 "WBS510(TP-LINK|EU|N300-5):1.20\r\n",
235 .support_trail = '\xff',
236 .soft_ver = NULL,
237
238 .partitions = {
239 {"fs-uboot", 0x00000, 0x20000},
240 {"partition-table", 0x20000, 0x02000},
241 {"default-mac", 0x30000, 0x00020},
242 {"product-info", 0x31100, 0x00100},
243 {"signature", 0x32000, 0x00400},
244 {"os-image", 0x40000, 0x170000},
245 {"soft-version", 0x1b0000, 0x00100},
246 {"support-list", 0x1b1000, 0x00400},
247 {"file-system", 0x1c0000, 0x600000},
248 {"user-config", 0x7c0000, 0x10000},
249 {"default-config", 0x7d0000, 0x10000},
250 {"log", 0x7e0000, 0x10000},
251 {"radio", 0x7f0000, 0x10000},
252 {NULL, 0, 0}
253 },
254
255 .first_sysupgrade_partition = "os-image",
256 .last_sysupgrade_partition = "file-system",
257 },
258
259 /** Firmware layout for the C2600 */
260 {
261 .id = "C2600",
262 .vendor = "",
263 .support_list =
264 "SupportList:\r\n"
265 "{product_name:Archer C2600,product_ver:1.0.0,special_id:00000000}\r\n",
266 .support_trail = '\x00',
267 .soft_ver = NULL,
268
269 .partitions = {
270 {"SBL1", 0x00000, 0x20000},
271 {"MIBIB", 0x20000, 0x20000},
272 {"SBL2", 0x40000, 0x20000},
273 {"SBL3", 0x60000, 0x30000},
274 {"DDRCONFIG", 0x90000, 0x10000},
275 {"SSD", 0xa0000, 0x10000},
276 {"TZ", 0xb0000, 0x30000},
277 {"RPM", 0xe0000, 0x20000},
278 {"fs-uboot", 0x100000, 0x70000},
279 {"uboot-env", 0x170000, 0x40000},
280 {"radio", 0x1b0000, 0x40000},
281 {"os-image", 0x1f0000, 0x200000},
282 {"file-system", 0x3f0000, 0x1b00000},
283 {"default-mac", 0x1ef0000, 0x00200},
284 {"pin", 0x1ef0200, 0x00200},
285 {"product-info", 0x1ef0400, 0x0fc00},
286 {"partition-table", 0x1f00000, 0x10000},
287 {"soft-version", 0x1f10000, 0x10000},
288 {"support-list", 0x1f20000, 0x10000},
289 {"profile", 0x1f30000, 0x10000},
290 {"default-config", 0x1f40000, 0x10000},
291 {"user-config", 0x1f50000, 0x40000},
292 {"qos-db", 0x1f90000, 0x40000},
293 {"usb-config", 0x1fd0000, 0x10000},
294 {"log", 0x1fe0000, 0x20000},
295 {NULL, 0, 0}
296 },
297
298 .first_sysupgrade_partition = "os-image",
299 .last_sysupgrade_partition = "file-system"
300 },
301
302 /** Firmware layout for the C25v1 */
303 {
304 .id = "ARCHER-C25-V1",
305 .support_list =
306 "SupportList:\n"
307 "{product_name:ArcherC25,product_ver:1.0.0,special_id:00000000}\n"
308 "{product_name:ArcherC25,product_ver:1.0.0,special_id:55530000}\n"
309 "{product_name:ArcherC25,product_ver:1.0.0,special_id:45550000}\n",
310 .support_trail = '\x00',
311 .soft_ver = "soft_ver:1.0.0\n",
312
313 /**
314 We use a bigger os-image partition than the stock images (and thus
315 smaller file-system), as our kernel doesn't fit in the stock firmware's
316 1MB os-image.
317 */
318 .partitions = {
319 {"factory-boot", 0x00000, 0x20000},
320 {"fs-uboot", 0x20000, 0x10000},
321 {"os-image", 0x30000, 0x180000}, /* Stock: base 0x30000 size 0x100000 */
322 {"file-system", 0x1b0000, 0x620000}, /* Stock: base 0x130000 size 0x6a0000 */
323 {"user-config", 0x7d0000, 0x04000},
324 {"default-mac", 0x7e0000, 0x00100},
325 {"device-id", 0x7e0100, 0x00100},
326 {"extra-para", 0x7e0200, 0x00100},
327 {"pin", 0x7e0300, 0x00100},
328 {"support-list", 0x7e0400, 0x00400},
329 {"soft-version", 0x7e0800, 0x00400},
330 {"product-info", 0x7e0c00, 0x01400},
331 {"partition-table", 0x7e2000, 0x01000},
332 {"profile", 0x7e3000, 0x01000},
333 {"default-config", 0x7e4000, 0x04000},
334 {"merge-config", 0x7ec000, 0x02000},
335 {"qos-db", 0x7ee000, 0x02000},
336 {"radio", 0x7f0000, 0x10000},
337 {NULL, 0, 0}
338 },
339
340 .first_sysupgrade_partition = "os-image",
341 .last_sysupgrade_partition = "file-system",
342 },
343
344 /** Firmware layout for the C59v1 */
345 {
346 .id = "ARCHER-C59-V1",
347 .vendor = "",
348 .support_list =
349 "SupportList:\r\n"
350 "{product_name:Archer C59,product_ver:1.0.0,special_id:00000000}\r\n"
351 "{product_name:Archer C59,product_ver:1.0.0,special_id:45550000}\r\n"
352 "{product_name:Archer C59,product_ver:1.0.0,special_id:55530000}\r\n",
353 .support_trail = '\x00',
354 .soft_ver = "soft_ver:1.0.0\n",
355
356 .partitions = {
357 {"fs-uboot", 0x00000, 0x10000},
358 {"default-mac", 0x10000, 0x00200},
359 {"pin", 0x10200, 0x00200},
360 {"device-id", 0x10400, 0x00100},
361 {"product-info", 0x10500, 0x0fb00},
362 {"os-image", 0x20000, 0x180000},
363 {"file-system", 0x1a0000, 0xcb0000},
364 {"partition-table", 0xe50000, 0x10000},
365 {"soft-version", 0xe60000, 0x10000},
366 {"support-list", 0xe70000, 0x10000},
367 {"profile", 0xe80000, 0x10000},
368 {"default-config", 0xe90000, 0x10000},
369 {"user-config", 0xea0000, 0x40000},
370 {"usb-config", 0xee0000, 0x10000},
371 {"certificate", 0xef0000, 0x10000},
372 {"qos-db", 0xf00000, 0x40000},
373 {"log", 0xfe0000, 0x10000},
374 {"radio", 0xff0000, 0x10000},
375 {NULL, 0, 0}
376 },
377
378 .first_sysupgrade_partition = "os-image",
379 .last_sysupgrade_partition = "file-system",
380 },
381
382 /** Firmware layout for the C60v1 */
383 {
384 .id = "ARCHER-C60-V1",
385 .vendor = "",
386 .support_list =
387 "SupportList:\r\n"
388 "{product_name:Archer C60,product_ver:1.0.0,special_id:00000000}\r\n"
389 "{product_name:Archer C60,product_ver:1.0.0,special_id:45550000}\r\n"
390 "{product_name:Archer C60,product_ver:1.0.0,special_id:55530000}\r\n",
391 .support_trail = '\x00',
392 .soft_ver = "soft_ver:1.0.0\n",
393
394 .partitions = {
395 {"fs-uboot", 0x00000, 0x10000},
396 {"default-mac", 0x10000, 0x00200},
397 {"pin", 0x10200, 0x00200},
398 {"product-info", 0x10400, 0x00100},
399 {"partition-table", 0x10500, 0x00800},
400 {"soft-version", 0x11300, 0x00200},
401 {"support-list", 0x11500, 0x00100},
402 {"device-id", 0x11600, 0x00100},
403 {"profile", 0x11700, 0x03900},
404 {"default-config", 0x15000, 0x04000},
405 {"user-config", 0x19000, 0x04000},
406 {"os-image", 0x20000, 0x150000},
407 {"file-system", 0x170000, 0x678000},
408 {"certyficate", 0x7e8000, 0x08000},
409 {"radio", 0x7f0000, 0x10000},
410 {NULL, 0, 0}
411 },
412
413 .first_sysupgrade_partition = "os-image",
414 .last_sysupgrade_partition = "file-system",
415 },
416
417 /** Firmware layout for the C5 */
418 {
419 .id = "ARCHER-C5-V2",
420 .vendor = "",
421 .support_list =
422 "SupportList:\r\n"
423 "{product_name:ArcherC5,"
424 "product_ver:2.0.0,"
425 "special_id:00000000}\r\n",
426 .support_trail = '\x00',
427 .soft_ver = NULL,
428
429 .partitions = {
430 {"fs-uboot", 0x00000, 0x40000},
431 {"os-image", 0x40000, 0x200000},
432 {"file-system", 0x240000, 0xc00000},
433 {"default-mac", 0xe40000, 0x00200},
434 {"pin", 0xe40200, 0x00200},
435 {"product-info", 0xe40400, 0x00200},
436 {"partition-table", 0xe50000, 0x10000},
437 {"soft-version", 0xe60000, 0x00200},
438 {"support-list", 0xe61000, 0x0f000},
439 {"profile", 0xe70000, 0x10000},
440 {"default-config", 0xe80000, 0x10000},
441 {"user-config", 0xe90000, 0x50000},
442 {"log", 0xee0000, 0x100000},
443 {"radio_bk", 0xfe0000, 0x10000},
444 {"radio", 0xff0000, 0x10000},
445 {NULL, 0, 0}
446 },
447
448 .first_sysupgrade_partition = "os-image",
449 .last_sysupgrade_partition = "file-system"
450 },
451
452 /** Firmware layout for the C9 */
453 {
454 .id = "ARCHERC9",
455 .vendor = "",
456 .support_list =
457 "SupportList:\n"
458 "{product_name:ArcherC9,"
459 "product_ver:1.0.0,"
460 "special_id:00000000}\n",
461 .support_trail = '\x00',
462 .soft_ver = NULL,
463
464 .partitions = {
465 {"fs-uboot", 0x00000, 0x40000},
466 {"os-image", 0x40000, 0x200000},
467 {"file-system", 0x240000, 0xc00000},
468 {"default-mac", 0xe40000, 0x00200},
469 {"pin", 0xe40200, 0x00200},
470 {"product-info", 0xe40400, 0x00200},
471 {"partition-table", 0xe50000, 0x10000},
472 {"soft-version", 0xe60000, 0x00200},
473 {"support-list", 0xe61000, 0x0f000},
474 {"profile", 0xe70000, 0x10000},
475 {"default-config", 0xe80000, 0x10000},
476 {"user-config", 0xe90000, 0x50000},
477 {"log", 0xee0000, 0x100000},
478 {"radio_bk", 0xfe0000, 0x10000},
479 {"radio", 0xff0000, 0x10000},
480 {NULL, 0, 0}
481 },
482
483 .first_sysupgrade_partition = "os-image",
484 .last_sysupgrade_partition = "file-system"
485 },
486
487 /** Firmware layout for the EAP120 */
488 {
489 .id = "EAP120",
490 .vendor = "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
491 .support_list =
492 "SupportList:\r\n"
493 "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
494 .support_trail = '\xff',
495 .soft_ver = NULL,
496
497 .partitions = {
498 {"fs-uboot", 0x00000, 0x20000},
499 {"partition-table", 0x20000, 0x02000},
500 {"default-mac", 0x30000, 0x00020},
501 {"support-list", 0x31000, 0x00100},
502 {"product-info", 0x31100, 0x00100},
503 {"soft-version", 0x32000, 0x00100},
504 {"os-image", 0x40000, 0x180000},
505 {"file-system", 0x1c0000, 0x600000},
506 {"user-config", 0x7c0000, 0x10000},
507 {"backup-config", 0x7d0000, 0x10000},
508 {"log", 0x7e0000, 0x10000},
509 {"radio", 0x7f0000, 0x10000},
510 {NULL, 0, 0}
511 },
512
513 .first_sysupgrade_partition = "os-image",
514 .last_sysupgrade_partition = "file-system"
515 },
516
517 /** Firmware layout for the TL-WA850RE v2 */
518 {
519 .id = "TLWA850REV2",
520 .vendor = "",
521 .support_list =
522 "SupportList:\n"
523 "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:55530000}\n"
524 "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:00000000}\n"
525 "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:55534100}\n"
526 "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:45550000}\n"
527 "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:4B520000}\n"
528 "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:42520000}\n"
529 "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:4A500000}\n"
530 "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:43410000}\n"
531 "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:41550000}\n"
532 "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:52550000}\n",
533 .support_trail = '\x00',
534 .soft_ver = NULL,
535
536 /**
537 576KB were moved from file-system to os-image
538 in comparison to the stock image
539 */
540 .partitions = {
541 {"fs-uboot", 0x00000, 0x20000},
542 {"os-image", 0x20000, 0x150000},
543 {"file-system", 0x170000, 0x240000},
544 {"partition-table", 0x3b0000, 0x02000},
545 {"default-mac", 0x3c0000, 0x00020},
546 {"pin", 0x3c0100, 0x00020},
547 {"product-info", 0x3c1000, 0x01000},
548 {"soft-version", 0x3c2000, 0x00100},
549 {"support-list", 0x3c3000, 0x01000},
550 {"profile", 0x3c4000, 0x08000},
551 {"user-config", 0x3d0000, 0x10000},
552 {"default-config", 0x3e0000, 0x10000},
553 {"radio", 0x3f0000, 0x10000},
554 {NULL, 0, 0}
555 },
556
557 .first_sysupgrade_partition = "os-image",
558 .last_sysupgrade_partition = "file-system"
559 },
560
561 /** Firmware layout for the TL-WR1043 v4 */
562 {
563 .id = "TLWR1043NDV4",
564 .vendor = "",
565 .support_list =
566 "SupportList:\n"
567 "{product_name:TL-WR1043ND,product_ver:4.0.0,special_id:45550000}\n",
568 .support_trail = '\x00',
569 .soft_ver = NULL,
570
571 /**
572 We use a bigger os-image partition than the stock images (and thus
573 smaller file-system), as our kernel doesn't fit in the stock firmware's
574 1MB os-image.
575 */
576 .partitions = {
577 {"fs-uboot", 0x00000, 0x20000},
578 {"os-image", 0x20000, 0x180000},
579 {"file-system", 0x1a0000, 0xdb0000},
580 {"default-mac", 0xf50000, 0x00200},
581 {"pin", 0xf50200, 0x00200},
582 {"product-info", 0xf50400, 0x0fc00},
583 {"soft-version", 0xf60000, 0x0b000},
584 {"support-list", 0xf6b000, 0x04000},
585 {"profile", 0xf70000, 0x04000},
586 {"default-config", 0xf74000, 0x0b000},
587 {"user-config", 0xf80000, 0x40000},
588 {"partition-table", 0xfc0000, 0x10000},
589 {"log", 0xfd0000, 0x20000},
590 {"radio", 0xff0000, 0x10000},
591 {NULL, 0, 0}
592 },
593
594 .first_sysupgrade_partition = "os-image",
595 .last_sysupgrade_partition = "file-system"
596 },
597
598 /** Firmware layout for the TL-WR942N V1 */
599 {
600 .id = "TLWR942NV1",
601 .vendor = "",
602 .support_list =
603 "SupportList:\r\n"
604 "{product_name:TL-WR942N,product_ver:1.0.0,special_id:00000000}\r\n"
605 "{product_name:TL-WR942N,product_ver:1.0.0,special_id:52550000}\r\n",
606 .support_trail = '\x00',
607 .soft_ver = NULL,
608
609 .partitions = {
610 {"fs-uboot", 0x00000, 0x20000},
611 {"os-image", 0x20000, 0x150000},
612 {"file-system", 0x170000, 0xcd0000},
613 {"default-mac", 0xe40000, 0x00200},
614 {"pin", 0xe40200, 0x00200},
615 {"product-info", 0xe40400, 0x0fc00},
616 {"partition-table", 0xe50000, 0x10000},
617 {"soft-version", 0xe60000, 0x10000},
618 {"support-list", 0xe70000, 0x10000},
619 {"profile", 0xe80000, 0x10000},
620 {"default-config", 0xe90000, 0x10000},
621 {"user-config", 0xea0000, 0x40000},
622 {"qos-db", 0xee0000, 0x40000},
623 {"certificate", 0xf20000, 0x10000},
624 {"usb-config", 0xfb0000, 0x10000},
625 {"log", 0xfc0000, 0x20000},
626 {"radio-bk", 0xfe0000, 0x10000},
627 {"radio", 0xff0000, 0x10000},
628 {NULL, 0, 0}
629 },
630
631 .first_sysupgrade_partition = "os-image",
632 .last_sysupgrade_partition = "file-system",
633 },
634
635 /** Firmware layout for the RE450 */
636 {
637 .id = "RE450",
638 .vendor = "",
639 .support_list =
640 "SupportList:\r\n"
641 "{product_name:RE450,product_ver:1.0.0,special_id:00000000}\r\n"
642 "{product_name:RE450,product_ver:1.0.0,special_id:55530000}\r\n"
643 "{product_name:RE450,product_ver:1.0.0,special_id:45550000}\r\n"
644 "{product_name:RE450,product_ver:1.0.0,special_id:4A500000}\r\n"
645 "{product_name:RE450,product_ver:1.0.0,special_id:43410000}\r\n"
646 "{product_name:RE450,product_ver:1.0.0,special_id:41550000}\r\n"
647 "{product_name:RE450,product_ver:1.0.0,special_id:4B520000}\r\n"
648 "{product_name:RE450,product_ver:1.0.0,special_id:55534100}\r\n",
649 .support_trail = '\x00',
650 .soft_ver = NULL,
651
652 /**
653 The flash partition table for RE450;
654 it is almost the same as the one used by the stock images,
655 576KB were moved from file-system to os-image.
656 */
657 .partitions = {
658 {"fs-uboot", 0x00000, 0x20000},
659 {"os-image", 0x20000, 0x150000},
660 {"file-system", 0x170000, 0x4a0000},
661 {"partition-table", 0x600000, 0x02000},
662 {"default-mac", 0x610000, 0x00020},
663 {"pin", 0x610100, 0x00020},
664 {"product-info", 0x611100, 0x01000},
665 {"soft-version", 0x620000, 0x01000},
666 {"support-list", 0x621000, 0x01000},
667 {"profile", 0x622000, 0x08000},
668 {"user-config", 0x630000, 0x10000},
669 {"default-config", 0x640000, 0x10000},
670 {"radio", 0x7f0000, 0x10000},
671 {NULL, 0, 0}
672 },
673
674 .first_sysupgrade_partition = "os-image",
675 .last_sysupgrade_partition = "file-system"
676 },
677
678 {}
679 };
680
681 #define error(_ret, _errno, _str, ...) \
682 do { \
683 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__, \
684 strerror(_errno)); \
685 if (_ret) \
686 exit(_ret); \
687 } while (0)
688
689
690 /** Stores a uint32 as big endian */
691 static inline void put32(uint8_t *buf, uint32_t val) {
692 buf[0] = val >> 24;
693 buf[1] = val >> 16;
694 buf[2] = val >> 8;
695 buf[3] = val;
696 }
697
698 /** Allocates a new image partition */
699 static struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
700 struct image_partition_entry entry = {name, len, malloc(len)};
701 if (!entry.data)
702 error(1, errno, "malloc");
703
704 return entry;
705 }
706
707 /** Frees an image partition */
708 static void free_image_partition(struct image_partition_entry entry) {
709 free(entry.data);
710 }
711
712 static time_t source_date_epoch = -1;
713 static void set_source_date_epoch() {
714 char *env = getenv("SOURCE_DATE_EPOCH");
715 char *endptr = env;
716 errno = 0;
717 if (env && *env) {
718 source_date_epoch = strtoull(env, &endptr, 10);
719 if (errno || (endptr && *endptr != '\0')) {
720 fprintf(stderr, "Invalid SOURCE_DATE_EPOCH");
721 exit(1);
722 }
723 }
724 }
725
726 /** Generates the partition-table partition */
727 static struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
728 struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
729
730 char *s = (char *)entry.data, *end = (char *)(s+entry.size);
731
732 *(s++) = 0x00;
733 *(s++) = 0x04;
734 *(s++) = 0x00;
735 *(s++) = 0x00;
736
737 size_t i;
738 for (i = 0; p[i].name; i++) {
739 size_t len = end-s;
740 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
741
742 if (w > len-1)
743 error(1, 0, "flash partition table overflow?");
744
745 s += w;
746 }
747
748 s++;
749
750 memset(s, 0xff, end-s);
751
752 return entry;
753 }
754
755
756 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
757 static inline uint8_t bcd(uint8_t v) {
758 return 0x10 * (v/10) + v%10;
759 }
760
761
762 /** Generates the soft-version partition */
763 static struct image_partition_entry make_soft_version(uint32_t rev) {
764 struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
765 struct soft_version *s = (struct soft_version *)entry.data;
766
767 time_t t;
768
769 if (source_date_epoch != -1)
770 t = source_date_epoch;
771 else if (time(&t) == (time_t)(-1))
772 error(1, errno, "time");
773
774 struct tm *tm = localtime(&t);
775
776 s->magic = htonl(0x0000000c);
777 s->zero = 0;
778 s->pad1 = 0xff;
779
780 s->version_major = 0;
781 s->version_minor = 0;
782 s->version_patch = 0;
783
784 s->year_hi = bcd((1900+tm->tm_year)/100);
785 s->year_lo = bcd(tm->tm_year%100);
786 s->month = bcd(tm->tm_mon+1);
787 s->day = bcd(tm->tm_mday);
788 s->rev = htonl(rev);
789
790 s->pad2 = 0xff;
791
792 return entry;
793 }
794
795 static struct image_partition_entry make_soft_version_from_string(const char *soft_ver) {
796 /** String length _including_ the terminating zero byte */
797 uint32_t ver_len = strlen(soft_ver) + 1;
798 /** Partition contains 64 bit header, the version string, and one additional null byte */
799 size_t partition_len = 2*sizeof(uint32_t) + ver_len + 1;
800 struct image_partition_entry entry = alloc_image_partition("soft-version", partition_len);
801
802 uint32_t *len = (uint32_t *)entry.data;
803 len[0] = htonl(ver_len);
804 len[1] = 0;
805 memcpy(&len[2], soft_ver, ver_len);
806
807 entry.data[partition_len - 1] = 0;
808
809 return entry;
810 }
811
812 /** Generates the support-list partition */
813 static struct image_partition_entry make_support_list(const struct device_info *info) {
814 size_t len = strlen(info->support_list);
815 struct image_partition_entry entry = alloc_image_partition("support-list", len + 9);
816
817 put32(entry.data, len);
818 memset(entry.data+4, 0, 4);
819 memcpy(entry.data+8, info->support_list, len);
820 entry.data[len+8] = info->support_trail;
821
822 return entry;
823 }
824
825 /** Creates a new image partition with an arbitrary name from a file */
826 static struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
827 struct stat statbuf;
828
829 if (stat(filename, &statbuf) < 0)
830 error(1, errno, "unable to stat file `%s'", filename);
831
832 size_t len = statbuf.st_size;
833
834 if (add_jffs2_eof)
835 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
836
837 struct image_partition_entry entry = alloc_image_partition(part_name, len);
838
839 FILE *file = fopen(filename, "rb");
840 if (!file)
841 error(1, errno, "unable to open file `%s'", filename);
842
843 if (fread(entry.data, statbuf.st_size, 1, file) != 1)
844 error(1, errno, "unable to read file `%s'", filename);
845
846 if (add_jffs2_eof) {
847 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
848
849 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
850 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
851 }
852
853 fclose(file);
854
855 return entry;
856 }
857
858 /** Creates a new image partition from arbitrary data */
859 static struct image_partition_entry put_data(const char *part_name, const char *datain, size_t len) {
860
861 struct image_partition_entry entry = alloc_image_partition(part_name, len);
862
863 memcpy(entry.data, datain, len);
864
865 return entry;
866 }
867
868 /**
869 Copies a list of image partitions into an image buffer and generates the image partition table while doing so
870
871 Example image partition table:
872
873 fwup-ptn partition-table base 0x00800 size 0x00800
874 fwup-ptn os-image base 0x01000 size 0x113b45
875 fwup-ptn file-system base 0x114b45 size 0x1d0004
876 fwup-ptn support-list base 0x2e4b49 size 0x000d1
877
878 Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
879 the end of the partition table is marked with a zero byte.
880
881 The firmware image must contain at least the partition-table and support-list partitions
882 to be accepted. There aren't any alignment constraints for the image partitions.
883
884 The partition-table partition contains the actual flash layout; partitions
885 from the image partition table are mapped to the corresponding flash partitions during
886 the firmware upgrade. The support-list partition contains a list of devices supported by
887 the firmware image.
888
889 The base offsets in the firmware partition table are relative to the end
890 of the vendor information block, so the partition-table partition will
891 actually start at offset 0x1814 of the image.
892
893 I think partition-table must be the first partition in the firmware image.
894 */
895 static void put_partitions(uint8_t *buffer, const struct flash_partition_entry *flash_parts, const struct image_partition_entry *parts) {
896 size_t i, j;
897 char *image_pt = (char *)buffer, *end = image_pt + 0x800;
898
899 size_t base = 0x800;
900 for (i = 0; parts[i].name; i++) {
901 for (j = 0; flash_parts[j].name; j++) {
902 if (!strcmp(flash_parts[j].name, parts[i].name)) {
903 if (parts[i].size > flash_parts[j].size)
904 error(1, 0, "%s partition too big (more than %u bytes)", flash_parts[j].name, (unsigned)flash_parts[j].size);
905 break;
906 }
907 }
908
909 assert(flash_parts[j].name);
910
911 memcpy(buffer + base, parts[i].data, parts[i].size);
912
913 size_t len = end-image_pt;
914 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);
915
916 if (w > len-1)
917 error(1, 0, "image partition table overflow?");
918
919 image_pt += w;
920
921 base += parts[i].size;
922 }
923 }
924
925 /** Generates and writes the image MD5 checksum */
926 static void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
927 MD5_CTX ctx;
928
929 MD5_Init(&ctx);
930 MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
931 MD5_Update(&ctx, buffer, len);
932 MD5_Final(md5, &ctx);
933 }
934
935
936 /**
937 Generates the firmware image in factory format
938
939 Image format:
940
941 Bytes (hex) Usage
942 ----------- -----
943 0000-0003 Image size (4 bytes, big endian)
944 0004-0013 MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
945 0014-0017 Vendor information length (without padding) (4 bytes, big endian)
946 0018-1013 Vendor information (4092 bytes, padded with 0xff; there seem to be older
947 (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
948 1014-1813 Image partition table (2048 bytes, padded with 0xff)
949 1814-xxxx Firmware partitions
950 */
951 static void * generate_factory_image(const struct device_info *info, const struct image_partition_entry *parts, size_t *len) {
952 *len = 0x1814;
953
954 size_t i;
955 for (i = 0; parts[i].name; i++)
956 *len += parts[i].size;
957
958 uint8_t *image = malloc(*len);
959 if (!image)
960 error(1, errno, "malloc");
961
962 memset(image, 0xff, *len);
963 put32(image, *len);
964
965 if (info->vendor) {
966 size_t vendor_len = strlen(info->vendor);
967 put32(image+0x14, vendor_len);
968 memcpy(image+0x18, info->vendor, vendor_len);
969 }
970
971 put_partitions(image + 0x1014, info->partitions, parts);
972 put_md5(image+0x04, image+0x14, *len-0x14);
973
974 return image;
975 }
976
977 /**
978 Generates the firmware image in sysupgrade format
979
980 This makes some assumptions about the provided flash and image partition tables and
981 should be generalized when TP-LINK starts building its safeloader into hardware with
982 different flash layouts.
983 */
984 static void * generate_sysupgrade_image(const struct device_info *info, const struct image_partition_entry *image_parts, size_t *len) {
985 size_t i, j;
986 size_t flash_first_partition_index = 0;
987 size_t flash_last_partition_index = 0;
988 const struct flash_partition_entry *flash_first_partition = NULL;
989 const struct flash_partition_entry *flash_last_partition = NULL;
990 const struct image_partition_entry *image_last_partition = NULL;
991
992 /** Find first and last partitions */
993 for (i = 0; info->partitions[i].name; i++) {
994 if (!strcmp(info->partitions[i].name, info->first_sysupgrade_partition)) {
995 flash_first_partition = &info->partitions[i];
996 flash_first_partition_index = i;
997 } else if (!strcmp(info->partitions[i].name, info->last_sysupgrade_partition)) {
998 flash_last_partition = &info->partitions[i];
999 flash_last_partition_index = i;
1000 }
1001 }
1002
1003 assert(flash_first_partition && flash_last_partition);
1004 assert(flash_first_partition_index < flash_last_partition_index);
1005
1006 /** Find last partition from image to calculate needed size */
1007 for (i = 0; image_parts[i].name; i++) {
1008 if (!strcmp(image_parts[i].name, info->last_sysupgrade_partition)) {
1009 image_last_partition = &image_parts[i];
1010 break;
1011 }
1012 }
1013
1014 assert(image_last_partition);
1015
1016 *len = flash_last_partition->base - flash_first_partition->base + image_last_partition->size;
1017
1018 uint8_t *image = malloc(*len);
1019 if (!image)
1020 error(1, errno, "malloc");
1021
1022 memset(image, 0xff, *len);
1023
1024 for (i = flash_first_partition_index; i <= flash_last_partition_index; i++) {
1025 for (j = 0; image_parts[j].name; j++) {
1026 if (!strcmp(info->partitions[i].name, image_parts[j].name)) {
1027 if (image_parts[j].size > info->partitions[i].size)
1028 error(1, 0, "%s partition too big (more than %u bytes)", info->partitions[i].name, (unsigned)info->partitions[i].size);
1029 memcpy(image + info->partitions[i].base - flash_first_partition->base, image_parts[j].data, image_parts[j].size);
1030 break;
1031 }
1032
1033 assert(image_parts[j].name);
1034 }
1035 }
1036
1037 return image;
1038 }
1039
1040 /** Generates an image according to a given layout and writes it to a file */
1041 static void build_image(const char *output,
1042 const char *kernel_image,
1043 const char *rootfs_image,
1044 uint32_t rev,
1045 bool add_jffs2_eof,
1046 bool sysupgrade,
1047 const struct device_info *info) {
1048
1049 struct image_partition_entry parts[7] = {};
1050
1051 parts[0] = make_partition_table(info->partitions);
1052 if (info->soft_ver)
1053 parts[1] = make_soft_version_from_string(info->soft_ver);
1054 else
1055 parts[1] = make_soft_version(rev);
1056
1057 parts[2] = make_support_list(info);
1058 parts[3] = read_file("os-image", kernel_image, false);
1059 parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
1060
1061 if (strcasecmp(info->id, "ARCHER-C25-V1") == 0) {
1062 const char mdat[11] = {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00};
1063 parts[5] = put_data("extra-para", mdat, 11);
1064 }
1065
1066 size_t len;
1067 void *image;
1068 if (sysupgrade)
1069 image = generate_sysupgrade_image(info, parts, &len);
1070 else
1071 image = generate_factory_image(info, parts, &len);
1072
1073 FILE *file = fopen(output, "wb");
1074 if (!file)
1075 error(1, errno, "unable to open output file");
1076
1077 if (fwrite(image, len, 1, file) != 1)
1078 error(1, 0, "unable to write output file");
1079
1080 fclose(file);
1081
1082 free(image);
1083
1084 size_t i;
1085 for (i = 0; parts[i].name; i++)
1086 free_image_partition(parts[i]);
1087 }
1088
1089 /** Usage output */
1090 static void usage(const char *argv0) {
1091 fprintf(stderr,
1092 "Usage: %s [OPTIONS...]\n"
1093 "\n"
1094 "Options:\n"
1095 " -B <board> create image for the board specified with <board>\n"
1096 " -k <file> read kernel image from the file <file>\n"
1097 " -r <file> read rootfs image from the file <file>\n"
1098 " -o <file> write output to the file <file>\n"
1099 " -V <rev> sets the revision number to <rev>\n"
1100 " -j add jffs2 end-of-filesystem markers\n"
1101 " -S create sysupgrade instead of factory image\n"
1102 " -h show this help\n",
1103 argv0
1104 );
1105 };
1106
1107
1108 static const struct device_info *find_board(const char *id)
1109 {
1110 struct device_info *board = NULL;
1111
1112 for (board = boards; board->id != NULL; board++)
1113 if (strcasecmp(id, board->id) == 0)
1114 return board;
1115
1116 return NULL;
1117 }
1118
1119 int main(int argc, char *argv[]) {
1120 const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
1121 bool add_jffs2_eof = false, sysupgrade = false;
1122 unsigned rev = 0;
1123 const struct device_info *info;
1124 set_source_date_epoch();
1125
1126 while (true) {
1127 int c;
1128
1129 c = getopt(argc, argv, "B:k:r:o:V:jSh");
1130 if (c == -1)
1131 break;
1132
1133 switch (c) {
1134 case 'B':
1135 board = optarg;
1136 break;
1137
1138 case 'k':
1139 kernel_image = optarg;
1140 break;
1141
1142 case 'r':
1143 rootfs_image = optarg;
1144 break;
1145
1146 case 'o':
1147 output = optarg;
1148 break;
1149
1150 case 'V':
1151 sscanf(optarg, "r%u", &rev);
1152 break;
1153
1154 case 'j':
1155 add_jffs2_eof = true;
1156 break;
1157
1158 case 'S':
1159 sysupgrade = true;
1160 break;
1161
1162 case 'h':
1163 usage(argv[0]);
1164 return 0;
1165
1166 default:
1167 usage(argv[0]);
1168 return 1;
1169 }
1170 }
1171
1172 if (!board)
1173 error(1, 0, "no board has been specified");
1174 if (!kernel_image)
1175 error(1, 0, "no kernel image has been specified");
1176 if (!rootfs_image)
1177 error(1, 0, "no rootfs image has been specified");
1178 if (!output)
1179 error(1, 0, "no output filename has been specified");
1180
1181 info = find_board(board);
1182
1183 if (info == NULL)
1184 error(1, 0, "unsupported board %s", board);
1185
1186 build_image(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade, info);
1187
1188 return 0;
1189 }