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 unsigned char cpe510_vendor[] = "\x00\x00\x00\x1f""CPE510(TP-LINK|UN|N300-5):1.0\r\n";
110 The flash partition table for CPE210/220/510/520;
111 it is the same as the one used by the stock images.
113 static const struct flash_partition_entry cpe510_partitions[] = {
114 {"fs-uboot", 0x00000, 0x20000},
115 {"partition-table", 0x20000, 0x02000},
116 {"default-mac", 0x30000, 0x00020},
117 {"product-info", 0x31100, 0x00100},
118 {"signature", 0x32000, 0x00400},
119 {"os-image", 0x40000, 0x170000},
120 {"soft-version", 0x1b0000, 0x00100},
121 {"support-list", 0x1b1000, 0x00400},
122 {"file-system", 0x1c0000, 0x600000},
123 {"user-config", 0x7c0000, 0x10000},
124 {"default-config", 0x7d0000, 0x10000},
125 {"log", 0x7e0000, 0x10000},
126 {"radio", 0x7f0000, 0x10000},
131 The support list for CPE210/220/510/520
133 The stock images also contain strings for two more devices: BS510 and BS210.
134 At the moment, there exists no public information about these devices.
136 static const unsigned char cpe510_support_list[] =
137 "\x00\x00\x00\xc8\x00\x00\x00\x00"
139 "CPE510(TP-LINK|UN|N300-5):1.0\r\n"
140 "CPE520(TP-LINK|UN|N300-5):1.0\r\n"
141 "CPE210(TP-LINK|UN|N300-2):1.0\r\n"
142 "CPE220(TP-LINK|UN|N300-2):1.0\r\n"
145 #define error(_ret, _errno, _str, ...) \
147 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__, \
154 /** Allocates a new image partition */
155 struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
156 struct image_partition_entry entry = {name, len, malloc(len)};
158 error(1, errno, "malloc");
163 /** Frees an image partition */
164 void free_image_partition(struct image_partition_entry entry) {
168 /** Generates the partition-table partition */
169 struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
170 struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
172 char *s = (char *)entry.data, *end = (char *)(s+entry.size);
180 for (i = 0; p[i].name; i++) {
182 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
185 error(1, 0, "flash partition table overflow?");
192 memset(s, 0xff, end-s);
198 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
199 static inline uint8_t bcd(uint8_t v) {
200 return 0x10 * (v/10) + v%10;
204 /** Generates the soft-version partition */
205 struct image_partition_entry make_soft_version(uint32_t rev) {
206 struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
207 struct soft_version *s = (struct soft_version *)entry.data;
211 if (time(&t) == (time_t)(-1))
212 error(1, errno, "time");
214 struct tm *tm = localtime(&t);
216 s->magic = htonl(0x0000000c);
220 s->version_major = 0;
221 s->version_minor = 0;
222 s->version_patch = 0;
224 s->year_hi = bcd((1900+tm->tm_year)/100);
225 s->year_lo = bcd(tm->tm_year%100);
226 s->month = bcd(tm->tm_mon+1);
227 s->day = bcd(tm->tm_mday);
235 /** Generates the support-list partition */
236 struct image_partition_entry make_support_list(const unsigned char *support_list, size_t len) {
237 struct image_partition_entry entry = alloc_image_partition("support-list", len);
238 memcpy(entry.data, support_list, len);
242 /** Creates a new image partition with an arbitrary name from a file */
243 struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
246 if (stat(filename, &statbuf) < 0)
247 error(1, errno, "unable to stat file `%s'", filename);
249 size_t len = statbuf.st_size;
252 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
254 struct image_partition_entry entry = alloc_image_partition(part_name, len);
256 FILE *file = fopen(filename, "rb");
258 error(1, errno, "unable to open file `%s'", filename);
260 if (fread(entry.data, statbuf.st_size, 1, file) != 1)
261 error(1, errno, "unable to read file `%s'", filename);
264 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
266 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
267 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
277 Copies a list of image partitions into an image buffer and generates the image partition table while doing so
279 Example image partition table:
281 fwup-ptn partition-table base 0x00800 size 0x00800
282 fwup-ptn os-image base 0x01000 size 0x113b45
283 fwup-ptn file-system base 0x114b45 size 0x1d0004
284 fwup-ptn support-list base 0x2e4b49 size 0x000d1
286 Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
287 the end of the partition table is marked with a zero byte.
289 The firmware image must contain at least the partition-table and support-list partitions
290 to be accepted. There aren't any alignment constraints for the image partitions.
292 The partition-table partition contains the actual flash layout; partitions
293 from the image partition table are mapped to the corresponding flash partitions during
294 the firmware upgrade. The support-list partition contains a list of devices supported by
297 The base offsets in the firmware partition table are relative to the end
298 of the vendor information block, so the partition-table partition will
299 actually start at offset 0x1814 of the image.
301 I think partition-table must be the first partition in the firmware image.
303 void put_partitions(uint8_t *buffer, const struct image_partition_entry *parts) {
305 char *image_pt = (char *)buffer, *end = image_pt + 0x800;
308 for (i = 0; parts[i].name; i++) {
309 memcpy(buffer + base, parts[i].data, parts[i].size);
311 size_t len = end-image_pt;
312 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);
315 error(1, 0, "image partition table overflow?");
319 base += parts[i].size;
324 memset(image_pt, 0xff, end-image_pt);
327 /** Generates and writes the image MD5 checksum */
328 void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
332 MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
333 MD5_Update(&ctx, buffer, len);
334 MD5_Final(md5, &ctx);
339 Generates the firmware image in factory format
345 0000-0003 Image size (4 bytes, big endian)
346 0004-0013 MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
347 0014-1013 Vendor information (4096 bytes, padded with 0xff; there seem to be older
348 (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
349 1014-1813 Image partition table (2048 bytes, padded with 0xff)
350 1814-xxxx Firmware partitions
352 void * generate_factory_image(const unsigned char *vendor, size_t vendor_len, const struct image_partition_entry *parts, size_t *len) {
356 for (i = 0; parts[i].name; i++)
357 *len += parts[i].size;
359 uint8_t *image = malloc(*len);
361 error(1, errno, "malloc");
363 image[0] = *len >> 24;
364 image[1] = *len >> 16;
365 image[2] = *len >> 8;
368 memcpy(image+0x14, vendor, vendor_len);
369 memset(image+0x14+vendor_len, 0xff, 4096-vendor_len);
371 put_partitions(image + 0x1014, parts);
372 put_md5(image+0x04, image+0x14, *len-0x14);
378 Generates the firmware image in sysupgrade format
380 This makes some assumptions about the provided flash and image partition tables and
381 should be generalized when TP-LINK starts building its safeloader into hardware with
382 different flash layouts.
384 void * generate_sysupgrade_image(const struct flash_partition_entry *flash_parts, const struct image_partition_entry *image_parts, size_t *len) {
385 const struct flash_partition_entry *flash_os_image = &flash_parts[5];
386 const struct flash_partition_entry *flash_soft_version = &flash_parts[6];
387 const struct flash_partition_entry *flash_support_list = &flash_parts[7];
388 const struct flash_partition_entry *flash_file_system = &flash_parts[8];
390 const struct image_partition_entry *image_os_image = &image_parts[3];
391 const struct image_partition_entry *image_soft_version = &image_parts[1];
392 const struct image_partition_entry *image_support_list = &image_parts[2];
393 const struct image_partition_entry *image_file_system = &image_parts[4];
395 assert(strcmp(flash_os_image->name, "os-image") == 0);
396 assert(strcmp(flash_soft_version->name, "soft-version") == 0);
397 assert(strcmp(flash_support_list->name, "support-list") == 0);
398 assert(strcmp(flash_file_system->name, "file-system") == 0);
400 assert(strcmp(image_os_image->name, "os-image") == 0);
401 assert(strcmp(image_soft_version->name, "soft-version") == 0);
402 assert(strcmp(image_support_list->name, "support-list") == 0);
403 assert(strcmp(image_file_system->name, "file-system") == 0);
405 if (image_os_image->size > flash_os_image->size)
406 error(1, 0, "kernel image too big (more than %u bytes)", (unsigned)flash_os_image->size);
407 if (image_file_system->size > flash_file_system->size)
408 error(1, 0, "rootfs image too big (more than %u bytes)", (unsigned)flash_file_system->size);
410 *len = flash_file_system->base - flash_os_image->base + image_file_system->size;
412 uint8_t *image = malloc(*len);
414 error(1, errno, "malloc");
416 memset(image, 0xff, *len);
418 memcpy(image, image_os_image->data, image_os_image->size);
419 memcpy(image + flash_soft_version->base - flash_os_image->base, image_soft_version->data, image_soft_version->size);
420 memcpy(image + flash_support_list->base - flash_os_image->base, image_support_list->data, image_support_list->size);
421 memcpy(image + flash_file_system->base - flash_os_image->base, image_file_system->data, image_file_system->size);
427 /** Generates an image for CPE210/220/510/520 and writes it to a file */
428 static void do_cpe510(const char *output, const char *kernel_image, const char *rootfs_image, uint32_t rev, bool add_jffs2_eof, bool sysupgrade) {
429 struct image_partition_entry parts[6] = {};
431 parts[0] = make_partition_table(cpe510_partitions);
432 parts[1] = make_soft_version(rev);
433 parts[2] = make_support_list(cpe510_support_list, sizeof(cpe510_support_list)-1);
434 parts[3] = read_file("os-image", kernel_image, false);
435 parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
440 image = generate_sysupgrade_image(cpe510_partitions, parts, &len);
442 image = generate_factory_image(cpe510_vendor, sizeof(cpe510_vendor)-1, parts, &len);
444 FILE *file = fopen(output, "wb");
446 error(1, errno, "unable to open output file");
448 if (fwrite(image, len, 1, file) != 1)
449 error(1, 0, "unable to write output file");
456 for (i = 0; parts[i].name; i++)
457 free_image_partition(parts[i]);
462 void usage(const char *argv0) {
464 "Usage: %s [OPTIONS...]\n"
467 " -B <board> create image for the board specified with <board>\n"
468 " -k <file> read kernel image from the file <file>\n"
469 " -r <file> read rootfs image from the file <file>\n"
470 " -o <file> write output to the file <file>\n"
471 " -V <rev> sets the revision number to <rev>\n"
472 " -j add jffs2 end-of-filesystem markers\n"
473 " -S create sysupgrade instead of factory image\n"
474 " -h show this help\n",
480 int main(int argc, char *argv[]) {
481 const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
482 bool add_jffs2_eof = false, sysupgrade = false;
488 c = getopt(argc, argv, "B:k:r:o:V:jSh");
498 kernel_image = optarg;
502 rootfs_image = optarg;
510 sscanf(optarg, "r%u", &rev);
514 add_jffs2_eof = true;
532 error(1, 0, "no board has been specified");
534 error(1, 0, "no kernel image has been specified");
536 error(1, 0, "no rootfs image has been specified");
538 error(1, 0, "no output filename has been specified");
540 if (strcmp(board, "CPE510") == 0)
541 do_cpe510(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade);
543 error(1, 0, "unsupported board %s", board);