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