fd4c2ab70d731447b27229357192c448b5e8c1df
[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 /** Generates the partition-table partition */
713 static struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
714 struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
715
716 char *s = (char *)entry.data, *end = (char *)(s+entry.size);
717
718 *(s++) = 0x00;
719 *(s++) = 0x04;
720 *(s++) = 0x00;
721 *(s++) = 0x00;
722
723 size_t i;
724 for (i = 0; p[i].name; i++) {
725 size_t len = end-s;
726 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
727
728 if (w > len-1)
729 error(1, 0, "flash partition table overflow?");
730
731 s += w;
732 }
733
734 s++;
735
736 memset(s, 0xff, end-s);
737
738 return entry;
739 }
740
741
742 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
743 static inline uint8_t bcd(uint8_t v) {
744 return 0x10 * (v/10) + v%10;
745 }
746
747
748 /** Generates the soft-version partition */
749 static struct image_partition_entry make_soft_version(uint32_t rev) {
750 struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
751 struct soft_version *s = (struct soft_version *)entry.data;
752
753 time_t t;
754
755 if (time(&t) == (time_t)(-1))
756 error(1, errno, "time");
757
758 struct tm *tm = localtime(&t);
759
760 s->magic = htonl(0x0000000c);
761 s->zero = 0;
762 s->pad1 = 0xff;
763
764 s->version_major = 0;
765 s->version_minor = 0;
766 s->version_patch = 0;
767
768 s->year_hi = bcd((1900+tm->tm_year)/100);
769 s->year_lo = bcd(tm->tm_year%100);
770 s->month = bcd(tm->tm_mon+1);
771 s->day = bcd(tm->tm_mday);
772 s->rev = htonl(rev);
773
774 s->pad2 = 0xff;
775
776 return entry;
777 }
778
779 static struct image_partition_entry make_soft_version_from_string(const char *soft_ver) {
780 /** String length _including_ the terminating zero byte */
781 uint32_t ver_len = strlen(soft_ver) + 1;
782 /** Partition contains 64 bit header, the version string, and one additional null byte */
783 size_t partition_len = 2*sizeof(uint32_t) + ver_len + 1;
784 struct image_partition_entry entry = alloc_image_partition("soft-version", partition_len);
785
786 uint32_t *len = (uint32_t *)entry.data;
787 len[0] = htonl(ver_len);
788 len[1] = 0;
789 memcpy(&len[2], soft_ver, ver_len);
790
791 entry.data[partition_len - 1] = 0;
792
793 return entry;
794 }
795
796 /** Generates the support-list partition */
797 static struct image_partition_entry make_support_list(const struct device_info *info) {
798 size_t len = strlen(info->support_list);
799 struct image_partition_entry entry = alloc_image_partition("support-list", len + 9);
800
801 put32(entry.data, len);
802 memset(entry.data+4, 0, 4);
803 memcpy(entry.data+8, info->support_list, len);
804 entry.data[len+8] = info->support_trail;
805
806 return entry;
807 }
808
809 /** Creates a new image partition with an arbitrary name from a file */
810 static struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
811 struct stat statbuf;
812
813 if (stat(filename, &statbuf) < 0)
814 error(1, errno, "unable to stat file `%s'", filename);
815
816 size_t len = statbuf.st_size;
817
818 if (add_jffs2_eof)
819 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
820
821 struct image_partition_entry entry = alloc_image_partition(part_name, len);
822
823 FILE *file = fopen(filename, "rb");
824 if (!file)
825 error(1, errno, "unable to open file `%s'", filename);
826
827 if (fread(entry.data, statbuf.st_size, 1, file) != 1)
828 error(1, errno, "unable to read file `%s'", filename);
829
830 if (add_jffs2_eof) {
831 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
832
833 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
834 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
835 }
836
837 fclose(file);
838
839 return entry;
840 }
841
842 /** Creates a new image partition from arbitrary data */
843 static struct image_partition_entry put_data(const char *part_name, const char *datain, size_t len) {
844
845 struct image_partition_entry entry = alloc_image_partition(part_name, len);
846
847 memcpy(entry.data, datain, len);
848
849 return entry;
850 }
851
852 /**
853 Copies a list of image partitions into an image buffer and generates the image partition table while doing so
854
855 Example image partition table:
856
857 fwup-ptn partition-table base 0x00800 size 0x00800
858 fwup-ptn os-image base 0x01000 size 0x113b45
859 fwup-ptn file-system base 0x114b45 size 0x1d0004
860 fwup-ptn support-list base 0x2e4b49 size 0x000d1
861
862 Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
863 the end of the partition table is marked with a zero byte.
864
865 The firmware image must contain at least the partition-table and support-list partitions
866 to be accepted. There aren't any alignment constraints for the image partitions.
867
868 The partition-table partition contains the actual flash layout; partitions
869 from the image partition table are mapped to the corresponding flash partitions during
870 the firmware upgrade. The support-list partition contains a list of devices supported by
871 the firmware image.
872
873 The base offsets in the firmware partition table are relative to the end
874 of the vendor information block, so the partition-table partition will
875 actually start at offset 0x1814 of the image.
876
877 I think partition-table must be the first partition in the firmware image.
878 */
879 static void put_partitions(uint8_t *buffer, const struct flash_partition_entry *flash_parts, const struct image_partition_entry *parts) {
880 size_t i, j;
881 char *image_pt = (char *)buffer, *end = image_pt + 0x800;
882
883 size_t base = 0x800;
884 for (i = 0; parts[i].name; i++) {
885 for (j = 0; flash_parts[j].name; j++) {
886 if (!strcmp(flash_parts[j].name, parts[i].name)) {
887 if (parts[i].size > flash_parts[j].size)
888 error(1, 0, "%s partition too big (more than %u bytes)", flash_parts[j].name, (unsigned)flash_parts[j].size);
889 break;
890 }
891 }
892
893 assert(flash_parts[j].name);
894
895 memcpy(buffer + base, parts[i].data, parts[i].size);
896
897 size_t len = end-image_pt;
898 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);
899
900 if (w > len-1)
901 error(1, 0, "image partition table overflow?");
902
903 image_pt += w;
904
905 base += parts[i].size;
906 }
907 }
908
909 /** Generates and writes the image MD5 checksum */
910 static void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
911 MD5_CTX ctx;
912
913 MD5_Init(&ctx);
914 MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
915 MD5_Update(&ctx, buffer, len);
916 MD5_Final(md5, &ctx);
917 }
918
919
920 /**
921 Generates the firmware image in factory format
922
923 Image format:
924
925 Bytes (hex) Usage
926 ----------- -----
927 0000-0003 Image size (4 bytes, big endian)
928 0004-0013 MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
929 0014-0017 Vendor information length (without padding) (4 bytes, big endian)
930 0018-1013 Vendor information (4092 bytes, padded with 0xff; there seem to be older
931 (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
932 1014-1813 Image partition table (2048 bytes, padded with 0xff)
933 1814-xxxx Firmware partitions
934 */
935 static void * generate_factory_image(const struct device_info *info, const struct image_partition_entry *parts, size_t *len) {
936 *len = 0x1814;
937
938 size_t i;
939 for (i = 0; parts[i].name; i++)
940 *len += parts[i].size;
941
942 uint8_t *image = malloc(*len);
943 if (!image)
944 error(1, errno, "malloc");
945
946 memset(image, 0xff, *len);
947 put32(image, *len);
948
949 if (info->vendor) {
950 size_t vendor_len = strlen(info->vendor);
951 put32(image+0x14, vendor_len);
952 memcpy(image+0x18, info->vendor, vendor_len);
953 }
954
955 put_partitions(image + 0x1014, info->partitions, parts);
956 put_md5(image+0x04, image+0x14, *len-0x14);
957
958 return image;
959 }
960
961 /**
962 Generates the firmware image in sysupgrade format
963
964 This makes some assumptions about the provided flash and image partition tables and
965 should be generalized when TP-LINK starts building its safeloader into hardware with
966 different flash layouts.
967 */
968 static void * generate_sysupgrade_image(const struct device_info *info, const struct image_partition_entry *image_parts, size_t *len) {
969 size_t i, j;
970 size_t flash_first_partition_index = 0;
971 size_t flash_last_partition_index = 0;
972 const struct flash_partition_entry *flash_first_partition = NULL;
973 const struct flash_partition_entry *flash_last_partition = NULL;
974 const struct image_partition_entry *image_last_partition = NULL;
975
976 /** Find first and last partitions */
977 for (i = 0; info->partitions[i].name; i++) {
978 if (!strcmp(info->partitions[i].name, info->first_sysupgrade_partition)) {
979 flash_first_partition = &info->partitions[i];
980 flash_first_partition_index = i;
981 } else if (!strcmp(info->partitions[i].name, info->last_sysupgrade_partition)) {
982 flash_last_partition = &info->partitions[i];
983 flash_last_partition_index = i;
984 }
985 }
986
987 assert(flash_first_partition && flash_last_partition);
988 assert(flash_first_partition_index < flash_last_partition_index);
989
990 /** Find last partition from image to calculate needed size */
991 for (i = 0; image_parts[i].name; i++) {
992 if (!strcmp(image_parts[i].name, info->last_sysupgrade_partition)) {
993 image_last_partition = &image_parts[i];
994 break;
995 }
996 }
997
998 assert(image_last_partition);
999
1000 *len = flash_last_partition->base - flash_first_partition->base + image_last_partition->size;
1001
1002 uint8_t *image = malloc(*len);
1003 if (!image)
1004 error(1, errno, "malloc");
1005
1006 memset(image, 0xff, *len);
1007
1008 for (i = flash_first_partition_index; i <= flash_last_partition_index; i++) {
1009 for (j = 0; image_parts[j].name; j++) {
1010 if (!strcmp(info->partitions[i].name, image_parts[j].name)) {
1011 if (image_parts[j].size > info->partitions[i].size)
1012 error(1, 0, "%s partition too big (more than %u bytes)", info->partitions[i].name, (unsigned)info->partitions[i].size);
1013 memcpy(image + info->partitions[i].base - flash_first_partition->base, image_parts[j].data, image_parts[j].size);
1014 break;
1015 }
1016
1017 assert(image_parts[j].name);
1018 }
1019 }
1020
1021 return image;
1022 }
1023
1024 /** Generates an image according to a given layout and writes it to a file */
1025 static void build_image(const char *output,
1026 const char *kernel_image,
1027 const char *rootfs_image,
1028 uint32_t rev,
1029 bool add_jffs2_eof,
1030 bool sysupgrade,
1031 const struct device_info *info) {
1032
1033 struct image_partition_entry parts[7] = {};
1034
1035 parts[0] = make_partition_table(info->partitions);
1036 if (info->soft_ver)
1037 parts[1] = make_soft_version_from_string(info->soft_ver);
1038 else
1039 parts[1] = make_soft_version(rev);
1040
1041 parts[2] = make_support_list(info);
1042 parts[3] = read_file("os-image", kernel_image, false);
1043 parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
1044
1045 if (strcasecmp(info->id, "ARCHER-C25-V1") == 0) {
1046 const char mdat[11] = {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00};
1047 parts[5] = put_data("extra-para", mdat, 11);
1048 }
1049
1050 size_t len;
1051 void *image;
1052 if (sysupgrade)
1053 image = generate_sysupgrade_image(info, parts, &len);
1054 else
1055 image = generate_factory_image(info, parts, &len);
1056
1057 FILE *file = fopen(output, "wb");
1058 if (!file)
1059 error(1, errno, "unable to open output file");
1060
1061 if (fwrite(image, len, 1, file) != 1)
1062 error(1, 0, "unable to write output file");
1063
1064 fclose(file);
1065
1066 free(image);
1067
1068 size_t i;
1069 for (i = 0; parts[i].name; i++)
1070 free_image_partition(parts[i]);
1071 }
1072
1073 /** Usage output */
1074 static void usage(const char *argv0) {
1075 fprintf(stderr,
1076 "Usage: %s [OPTIONS...]\n"
1077 "\n"
1078 "Options:\n"
1079 " -B <board> create image for the board specified with <board>\n"
1080 " -k <file> read kernel image from the file <file>\n"
1081 " -r <file> read rootfs image from the file <file>\n"
1082 " -o <file> write output to the file <file>\n"
1083 " -V <rev> sets the revision number to <rev>\n"
1084 " -j add jffs2 end-of-filesystem markers\n"
1085 " -S create sysupgrade instead of factory image\n"
1086 " -h show this help\n",
1087 argv0
1088 );
1089 };
1090
1091
1092 static const struct device_info *find_board(const char *id)
1093 {
1094 struct device_info *board = NULL;
1095
1096 for (board = boards; board->id != NULL; board++)
1097 if (strcasecmp(id, board->id) == 0)
1098 return board;
1099
1100 return NULL;
1101 }
1102
1103 int main(int argc, char *argv[]) {
1104 const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
1105 bool add_jffs2_eof = false, sysupgrade = false;
1106 unsigned rev = 0;
1107 const struct device_info *info;
1108
1109 while (true) {
1110 int c;
1111
1112 c = getopt(argc, argv, "B:k:r:o:V:jSh");
1113 if (c == -1)
1114 break;
1115
1116 switch (c) {
1117 case 'B':
1118 board = optarg;
1119 break;
1120
1121 case 'k':
1122 kernel_image = optarg;
1123 break;
1124
1125 case 'r':
1126 rootfs_image = optarg;
1127 break;
1128
1129 case 'o':
1130 output = optarg;
1131 break;
1132
1133 case 'V':
1134 sscanf(optarg, "r%u", &rev);
1135 break;
1136
1137 case 'j':
1138 add_jffs2_eof = true;
1139 break;
1140
1141 case 'S':
1142 sysupgrade = true;
1143 break;
1144
1145 case 'h':
1146 usage(argv[0]);
1147 return 0;
1148
1149 default:
1150 usage(argv[0]);
1151 return 1;
1152 }
1153 }
1154
1155 if (!board)
1156 error(1, 0, "no board has been specified");
1157 if (!kernel_image)
1158 error(1, 0, "no kernel image has been specified");
1159 if (!rootfs_image)
1160 error(1, 0, "no rootfs image has been specified");
1161 if (!output)
1162 error(1, 0, "no output filename has been specified");
1163
1164 info = find_board(board);
1165
1166 if (info == NULL)
1167 error(1, 0, "unsupported board %s", board);
1168
1169 build_image(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade, info);
1170
1171 return 0;
1172 }