2 Copyright (c) 2014, Matthias Schiffer <mschiffer@universe-factory.net>
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
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.
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.
30 Image generation tool for the TP-LINK SafeLoader as seen on
31 TP-LINK Pharos devices (CPE210/220/510/520)
45 #include <arpa/inet.h>
47 #include <sys/types.h>
53 #define ALIGN(x,a) ({ typeof(a) __a = (a); (((x) + __a - 1) & ~(__a - 1)); })
56 /** An image partition table entry */
57 struct image_partition_entry
{
63 /** A flash partition table entry */
64 struct flash_partition_entry
{
71 /** The content of the soft-version structure */
72 struct __attribute__((__packed__
)) soft_version
{
76 uint8_t version_major
;
77 uint8_t version_minor
;
78 uint8_t version_patch
;
88 static const uint8_t jffs2_eof_mark
[4] = {0xde, 0xad, 0xc0, 0xde};
94 Fortunately, TP-LINK seems to use the same salt for most devices which use
97 static const uint8_t md5_salt
[16] = {
98 0x7a, 0x2b, 0x15, 0xed,
99 0x9b, 0x98, 0x59, 0x6d,
100 0xe5, 0x04, 0xab, 0x44,
101 0xac, 0x2a, 0x9f, 0x4e,
105 /** Vendor information for CPE210/220/510/520 */
106 static const char cpe510_vendor
[] = "CPE510(TP-LINK|UN|N300-5):1.0\r\n";
108 /** Vendor information for C2600 */
109 static const char c2600_vendor
[] = "";
112 The flash partition table for CPE210/220/510/520;
113 it is the same as the one used by the stock images.
115 static const struct flash_partition_entry cpe510_partitions
[] = {
116 {"fs-uboot", 0x00000, 0x20000},
117 {"partition-table", 0x20000, 0x02000},
118 {"default-mac", 0x30000, 0x00020},
119 {"product-info", 0x31100, 0x00100},
120 {"signature", 0x32000, 0x00400},
121 {"os-image", 0x40000, 0x170000},
122 {"soft-version", 0x1b0000, 0x00100},
123 {"support-list", 0x1b1000, 0x00400},
124 {"file-system", 0x1c0000, 0x600000},
125 {"user-config", 0x7c0000, 0x10000},
126 {"default-config", 0x7d0000, 0x10000},
127 {"log", 0x7e0000, 0x10000},
128 {"radio", 0x7f0000, 0x10000},
133 The flash partition table for C2600;
134 it is the same as the one used by the stock images.
136 static const struct flash_partition_entry c2600_partitions
[] = {
137 {"SBL1", 0x00000, 0x20000},
138 {"MIBIB", 0x20000, 0x20000},
139 {"SBL2", 0x40000, 0x20000},
140 {"SBL3", 0x60000, 0x30000},
141 {"DDRCONFIG", 0x90000, 0x10000},
142 {"SSD", 0xa0000, 0x10000},
143 {"TZ", 0xb0000, 0x30000},
144 {"RPM", 0xe0000, 0x20000},
145 {"fs-uboot", 0x100000, 0x70000},
146 {"uboot-env", 0x170000, 0x40000},
147 {"radio", 0x1b0000, 0x40000},
148 {"os-image", 0x1f0000, 0x200000},
149 {"file-system", 0x3f0000, 0x1b00000},
150 {"default-mac", 0x1ef0000, 0x00200},
151 {"pin", 0x1ef0200, 0x00200},
152 {"product-info", 0x1ef0400, 0x0fc00},
153 {"partition-table", 0x1f00000, 0x10000},
154 {"soft-version", 0x1f10000, 0x10000},
155 {"support-list", 0x1f20000, 0x10000},
156 {"profile", 0x1f30000, 0x10000},
157 {"default-config", 0x1f40000, 0x10000},
158 {"user-config", 0x1f50000, 0x40000},
159 {"qos-db", 0x1f90000, 0x40000},
160 {"usb-config", 0x1fd0000, 0x10000},
161 {"log", 0x1fe0000, 0x20000},
166 The support list for CPE210/220/510/520
168 static const char cpe510_support_list
[] =
170 "CPE510(TP-LINK|UN|N300-5):1.0\r\n"
171 "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
172 "CPE520(TP-LINK|UN|N300-5):1.0\r\n"
173 "CPE520(TP-LINK|UN|N300-5):1.1\r\n"
174 "CPE210(TP-LINK|UN|N300-2):1.0\r\n"
175 "CPE210(TP-LINK|UN|N300-2):1.1\r\n"
176 "CPE220(TP-LINK|UN|N300-2):1.0\r\n"
177 "CPE220(TP-LINK|UN|N300-2):1.1\r\n";
180 The support list for C2600
182 static const char c2600_support_list
[] =
184 "{product_name:Archer C2600,product_ver:1.0.0,special_id:00000000}\r\n";
186 #define error(_ret, _errno, _str, ...) \
188 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__, \
195 /** Stores a uint32 as big endian */
196 static inline void put32(uint8_t *buf
, uint32_t val
) {
203 /** Allocates a new image partition */
204 static struct image_partition_entry
alloc_image_partition(const char *name
, size_t len
) {
205 struct image_partition_entry entry
= {name
, len
, malloc(len
)};
207 error(1, errno
, "malloc");
212 /** Frees an image partition */
213 static void free_image_partition(struct image_partition_entry entry
) {
217 /** Generates the partition-table partition */
218 static struct image_partition_entry
make_partition_table(const struct flash_partition_entry
*p
) {
219 struct image_partition_entry entry
= alloc_image_partition("partition-table", 0x800);
221 char *s
= (char *)entry
.data
, *end
= (char *)(s
+entry
.size
);
229 for (i
= 0; p
[i
].name
; i
++) {
231 size_t w
= snprintf(s
, len
, "partition %s base 0x%05x size 0x%05x\n", p
[i
].name
, p
[i
].base
, p
[i
].size
);
234 error(1, 0, "flash partition table overflow?");
241 memset(s
, 0xff, end
-s
);
247 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
248 static inline uint8_t bcd(uint8_t v
) {
249 return 0x10 * (v
/10) + v
%10;
253 /** Generates the soft-version partition */
254 static struct image_partition_entry
make_soft_version(uint32_t rev
) {
255 struct image_partition_entry entry
= alloc_image_partition("soft-version", sizeof(struct soft_version
));
256 struct soft_version
*s
= (struct soft_version
*)entry
.data
;
260 if (time(&t
) == (time_t)(-1))
261 error(1, errno
, "time");
263 struct tm
*tm
= localtime(&t
);
265 s
->magic
= htonl(0x0000000c);
269 s
->version_major
= 0;
270 s
->version_minor
= 0;
271 s
->version_patch
= 0;
273 s
->year_hi
= bcd((1900+tm
->tm_year
)/100);
274 s
->year_lo
= bcd(tm
->tm_year
%100);
275 s
->month
= bcd(tm
->tm_mon
+1);
276 s
->day
= bcd(tm
->tm_mday
);
284 /** Generates the support-list partition */
285 static struct image_partition_entry
make_support_list(const char *support_list
, bool trailzero
) {
286 size_t len
= strlen(support_list
);
287 struct image_partition_entry entry
= alloc_image_partition("support-list", len
+ 9);
289 put32(entry
.data
, len
);
290 memset(entry
.data
+4, 0, 4);
291 memcpy(entry
.data
+8, support_list
, len
);
292 entry
.data
[len
+8] = trailzero
? '\x00' : '\xff';
297 /** Creates a new image partition with an arbitrary name from a file */
298 static struct image_partition_entry
read_file(const char *part_name
, const char *filename
, bool add_jffs2_eof
) {
301 if (stat(filename
, &statbuf
) < 0)
302 error(1, errno
, "unable to stat file `%s'", filename
);
304 size_t len
= statbuf
.st_size
;
307 len
= ALIGN(len
, 0x10000) + sizeof(jffs2_eof_mark
);
309 struct image_partition_entry entry
= alloc_image_partition(part_name
, len
);
311 FILE *file
= fopen(filename
, "rb");
313 error(1, errno
, "unable to open file `%s'", filename
);
315 if (fread(entry
.data
, statbuf
.st_size
, 1, file
) != 1)
316 error(1, errno
, "unable to read file `%s'", filename
);
319 uint8_t *eof
= entry
.data
+ statbuf
.st_size
, *end
= entry
.data
+entry
.size
;
321 memset(eof
, 0xff, end
- eof
- sizeof(jffs2_eof_mark
));
322 memcpy(end
- sizeof(jffs2_eof_mark
), jffs2_eof_mark
, sizeof(jffs2_eof_mark
));
332 Copies a list of image partitions into an image buffer and generates the image partition table while doing so
334 Example image partition table:
336 fwup-ptn partition-table base 0x00800 size 0x00800
337 fwup-ptn os-image base 0x01000 size 0x113b45
338 fwup-ptn file-system base 0x114b45 size 0x1d0004
339 fwup-ptn support-list base 0x2e4b49 size 0x000d1
341 Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
342 the end of the partition table is marked with a zero byte.
344 The firmware image must contain at least the partition-table and support-list partitions
345 to be accepted. There aren't any alignment constraints for the image partitions.
347 The partition-table partition contains the actual flash layout; partitions
348 from the image partition table are mapped to the corresponding flash partitions during
349 the firmware upgrade. The support-list partition contains a list of devices supported by
352 The base offsets in the firmware partition table are relative to the end
353 of the vendor information block, so the partition-table partition will
354 actually start at offset 0x1814 of the image.
356 I think partition-table must be the first partition in the firmware image.
358 static void put_partitions(uint8_t *buffer
, const struct image_partition_entry
*parts
) {
360 char *image_pt
= (char *)buffer
, *end
= image_pt
+ 0x800;
363 for (i
= 0; parts
[i
].name
; i
++) {
364 memcpy(buffer
+ base
, parts
[i
].data
, parts
[i
].size
);
366 size_t len
= end
-image_pt
;
367 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
);
370 error(1, 0, "image partition table overflow?");
374 base
+= parts
[i
].size
;
379 memset(image_pt
, 0xff, end
-image_pt
);
382 /** Generates and writes the image MD5 checksum */
383 static void put_md5(uint8_t *md5
, uint8_t *buffer
, unsigned int len
) {
387 MD5_Update(&ctx
, md5_salt
, (unsigned int)sizeof(md5_salt
));
388 MD5_Update(&ctx
, buffer
, len
);
389 MD5_Final(md5
, &ctx
);
394 Generates the firmware image in factory format
400 0000-0003 Image size (4 bytes, big endian)
401 0004-0013 MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
402 0014-0017 Vendor information length (without padding) (4 bytes, big endian)
403 0018-1013 Vendor information (4092 bytes, padded with 0xff; there seem to be older
404 (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
405 1014-1813 Image partition table (2048 bytes, padded with 0xff)
406 1814-xxxx Firmware partitions
408 static void * generate_factory_image(const char *vendor
, const struct image_partition_entry
*parts
, size_t *len
) {
412 for (i
= 0; parts
[i
].name
; i
++)
413 *len
+= parts
[i
].size
;
415 uint8_t *image
= malloc(*len
);
417 error(1, errno
, "malloc");
421 size_t vendor_len
= strlen(vendor
);
422 put32(image
+0x14, vendor_len
);
423 memcpy(image
+0x18, vendor
, vendor_len
);
424 memset(image
+0x18+vendor_len
, 0xff, 4092-vendor_len
);
426 put_partitions(image
+ 0x1014, parts
);
427 put_md5(image
+0x04, image
+0x14, *len
-0x14);
433 Generates the firmware image in sysupgrade format
435 This makes some assumptions about the provided flash and image partition tables and
436 should be generalized when TP-LINK starts building its safeloader into hardware with
437 different flash layouts.
439 static void * generate_sysupgrade_image(const struct flash_partition_entry
*flash_parts
, const struct image_partition_entry
*image_parts
, size_t *len
) {
440 const struct flash_partition_entry
*flash_os_image
= &flash_parts
[5];
441 const struct flash_partition_entry
*flash_soft_version
= &flash_parts
[6];
442 const struct flash_partition_entry
*flash_support_list
= &flash_parts
[7];
443 const struct flash_partition_entry
*flash_file_system
= &flash_parts
[8];
445 const struct image_partition_entry
*image_os_image
= &image_parts
[3];
446 const struct image_partition_entry
*image_soft_version
= &image_parts
[1];
447 const struct image_partition_entry
*image_support_list
= &image_parts
[2];
448 const struct image_partition_entry
*image_file_system
= &image_parts
[4];
450 assert(strcmp(flash_os_image
->name
, "os-image") == 0);
451 assert(strcmp(flash_soft_version
->name
, "soft-version") == 0);
452 assert(strcmp(flash_support_list
->name
, "support-list") == 0);
453 assert(strcmp(flash_file_system
->name
, "file-system") == 0);
455 assert(strcmp(image_os_image
->name
, "os-image") == 0);
456 assert(strcmp(image_soft_version
->name
, "soft-version") == 0);
457 assert(strcmp(image_support_list
->name
, "support-list") == 0);
458 assert(strcmp(image_file_system
->name
, "file-system") == 0);
460 if (image_os_image
->size
> flash_os_image
->size
)
461 error(1, 0, "kernel image too big (more than %u bytes)", (unsigned)flash_os_image
->size
);
462 if (image_file_system
->size
> flash_file_system
->size
)
463 error(1, 0, "rootfs image too big (more than %u bytes)", (unsigned)flash_file_system
->size
);
465 *len
= flash_file_system
->base
- flash_os_image
->base
+ image_file_system
->size
;
467 uint8_t *image
= malloc(*len
);
469 error(1, errno
, "malloc");
471 memset(image
, 0xff, *len
);
473 memcpy(image
, image_os_image
->data
, image_os_image
->size
);
474 memcpy(image
+ flash_soft_version
->base
- flash_os_image
->base
, image_soft_version
->data
, image_soft_version
->size
);
475 memcpy(image
+ flash_support_list
->base
- flash_os_image
->base
, image_support_list
->data
, image_support_list
->size
);
476 memcpy(image
+ flash_file_system
->base
- flash_os_image
->base
, image_file_system
->data
, image_file_system
->size
);
481 static void * generate_sysupgrade_image_c2600(const struct flash_partition_entry
*flash_parts
, const struct image_partition_entry
*image_parts
, size_t *len
) {
482 const struct flash_partition_entry
*flash_os_image
= &flash_parts
[11];
483 const struct flash_partition_entry
*flash_file_system
= &flash_parts
[12];
485 const struct image_partition_entry
*image_os_image
= &image_parts
[3];
486 const struct image_partition_entry
*image_file_system
= &image_parts
[4];
488 assert(strcmp(flash_os_image
->name
, "os-image") == 0);
489 assert(strcmp(flash_file_system
->name
, "file-system") == 0);
491 assert(strcmp(image_os_image
->name
, "os-image") == 0);
492 assert(strcmp(image_file_system
->name
, "file-system") == 0);
494 if (image_os_image
->size
> flash_os_image
->size
)
495 error(1, 0, "kernel image too big (more than %u bytes)", (unsigned)flash_os_image
->size
);
496 if (image_file_system
->size
> flash_file_system
->size
)
497 error(1, 0, "rootfs image too big (more than %u bytes)", (unsigned)flash_file_system
->size
);
499 *len
= flash_file_system
->base
- flash_os_image
->base
+ image_file_system
->size
;
501 uint8_t *image
= malloc(*len
);
503 error(1, errno
, "malloc");
505 memset(image
, 0xff, *len
);
507 memcpy(image
, image_os_image
->data
, image_os_image
->size
);
508 memcpy(image
+ flash_file_system
->base
- flash_os_image
->base
, image_file_system
->data
, image_file_system
->size
);
513 /** Generates an image for CPE210/220/510/520 and writes it to a file */
514 static void do_cpe510(const char *output
, const char *kernel_image
, const char *rootfs_image
, uint32_t rev
, bool add_jffs2_eof
, bool sysupgrade
) {
515 struct image_partition_entry parts
[6] = {};
517 parts
[0] = make_partition_table(cpe510_partitions
);
518 parts
[1] = make_soft_version(rev
);
519 parts
[2] = make_support_list(cpe510_support_list
,false);
520 parts
[3] = read_file("os-image", kernel_image
, false);
521 parts
[4] = read_file("file-system", rootfs_image
, add_jffs2_eof
);
526 image
= generate_sysupgrade_image(cpe510_partitions
, parts
, &len
);
528 image
= generate_factory_image(cpe510_vendor
, parts
, &len
);
530 FILE *file
= fopen(output
, "wb");
532 error(1, errno
, "unable to open output file");
534 if (fwrite(image
, len
, 1, file
) != 1)
535 error(1, 0, "unable to write output file");
542 for (i
= 0; parts
[i
].name
; i
++)
543 free_image_partition(parts
[i
]);
546 /** Generates an image for C2600 and writes it to a file */
547 static void do_c2600(const char *output
, const char *kernel_image
, const char *rootfs_image
, uint32_t rev
, bool add_jffs2_eof
, bool sysupgrade
) {
548 struct image_partition_entry parts
[6] = {};
550 parts
[0] = make_partition_table(c2600_partitions
);
551 parts
[1] = make_soft_version(rev
);
552 parts
[2] = make_support_list(c2600_support_list
,true);
553 parts
[3] = read_file("os-image", kernel_image
, false);
554 parts
[4] = read_file("file-system", rootfs_image
, add_jffs2_eof
);
559 image
= generate_sysupgrade_image_c2600(c2600_partitions
, parts
, &len
);
561 image
= generate_factory_image(c2600_vendor
, parts
, &len
);
563 FILE *file
= fopen(output
, "wb");
565 error(1, errno
, "unable to open output file");
567 if (fwrite(image
, len
, 1, file
) != 1)
568 error(1, 0, "unable to write output file");
575 for (i
= 0; parts
[i
].name
; i
++)
576 free_image_partition(parts
[i
]);
581 static void usage(const char *argv0
) {
583 "Usage: %s [OPTIONS...]\n"
586 " -B <board> create image for the board specified with <board>\n"
587 " -k <file> read kernel image from the file <file>\n"
588 " -r <file> read rootfs image from the file <file>\n"
589 " -o <file> write output to the file <file>\n"
590 " -V <rev> sets the revision number to <rev>\n"
591 " -j add jffs2 end-of-filesystem markers\n"
592 " -S create sysupgrade instead of factory image\n"
593 " -h show this help\n",
599 int main(int argc
, char *argv
[]) {
600 const char *board
= NULL
, *kernel_image
= NULL
, *rootfs_image
= NULL
, *output
= NULL
;
601 bool add_jffs2_eof
= false, sysupgrade
= false;
607 c
= getopt(argc
, argv
, "B:k:r:o:V:jSh");
617 kernel_image
= optarg
;
621 rootfs_image
= optarg
;
629 sscanf(optarg
, "r%u", &rev
);
633 add_jffs2_eof
= true;
651 error(1, 0, "no board has been specified");
653 error(1, 0, "no kernel image has been specified");
655 error(1, 0, "no rootfs image has been specified");
657 error(1, 0, "no output filename has been specified");
659 if (strcmp(board
, "CPE510") == 0)
660 do_cpe510(output
, kernel_image
, rootfs_image
, rev
, add_jffs2_eof
, sysupgrade
);
661 else if (strcmp(board
, "C2600") == 0)
662 do_c2600(output
, kernel_image
, rootfs_image
, rev
, add_jffs2_eof
, sysupgrade
);
664 error(1, 0, "unsupported board %s", board
);