firmware-utils: add new tool tplink-safeloader for the new TP-LINK Pharos devices...
[librecmc/librecmc.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 <error.h>
38 #include <stdbool.h>
39 #include <stdio.h>
40 #include <stdint.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <time.h>
44 #include <unistd.h>
45
46 #include <arpa/inet.h>
47
48 #include <sys/types.h>
49 #include <sys/stat.h>
50
51 #include "md5.h"
52
53
54 #define ALIGN(x,a) ({ typeof(a) __a = (a); (((x) + __a - 1) & ~(__a - 1)); })
55
56
57 /** An image partition table entry */
58 struct image_partition_entry {
59         const char *name;
60         size_t size;
61         uint8_t *data;
62 };
63
64 /** A flash partition table entry */
65 struct flash_partition_entry {
66         const char *name;
67         uint32_t base;
68         uint32_t size;
69 };
70
71
72 /** The content of the soft-version structure */
73 struct __attribute__((__packed__)) soft_version {
74         uint32_t magic;
75         uint32_t zero;
76         uint8_t pad1;
77         uint8_t version_major;
78         uint8_t version_minor;
79         uint8_t version_patch;
80         uint8_t year_hi;
81         uint8_t year_lo;
82         uint8_t month;
83         uint8_t day;
84         uint32_t rev;
85         uint8_t pad2;
86 };
87
88
89 static const uint8_t jffs2_eof_mark[4] = {0xde, 0xad, 0xc0, 0xde};
90
91
92 /**
93    Salt for the MD5 hash
94
95    Fortunately, TP-LINK seems to use the same salt for most devices which use
96    the new image format.
97 */
98 static const uint8_t md5_salt[16] = {
99         0x7a, 0x2b, 0x15, 0xed,
100         0x9b, 0x98, 0x59, 0x6d,
101         0xe5, 0x04, 0xab, 0x44,
102         0xac, 0x2a, 0x9f, 0x4e,
103 };
104
105
106 /** Vendor information for CPE210/220/510/520 */
107 static const unsigned char cpe510_vendor[] = "\x00\x00\x00\x1f""CPE510(TP-LINK|UN|N300-5):1.0\r\n";
108
109
110 /**
111     The flash partition table for CPE210/220/510/520;
112     it is the same as the one used by the stock images.
113 */
114 static const struct flash_partition_entry cpe510_partitions[] = {
115         {"fs-uboot", 0x00000, 0x20000},
116         {"partition-table", 0x20000, 0x02000},
117         {"default-mac", 0x30000, 0x00020},
118         {"product-info", 0x31100, 0x00100},
119         {"signature", 0x32000, 0x00400},
120         {"os-image", 0x40000, 0x170000},
121         {"soft-version", 0x1b0000, 0x00100},
122         {"support-list", 0x1b1000, 0x00400},
123         {"file-system", 0x1c0000, 0x600000},
124         {"user-config", 0x7c0000, 0x10000},
125         {"default-config", 0x7d0000, 0x10000},
126         {"log", 0x7e0000, 0x10000},
127         {"radio", 0x7f0000, 0x10000},
128         {NULL, 0, 0}
129 };
130
131 /**
132    The support list for CPE210/220/510/520
133
134    The stock images also contain strings for two more devices: BS510 and BS210.
135    At the moment, there exists no public information about these devices.
136 */
137 static const unsigned char cpe510_support_list[] =
138         "\x00\x00\x00\xc8\x00\x00\x00\x00"
139         "SupportList:\r\n"
140         "CPE510(TP-LINK|UN|N300-5):1.0\r\n"
141         "CPE520(TP-LINK|UN|N300-5):1.0\r\n"
142         "CPE210(TP-LINK|UN|N300-2):1.0\r\n"
143         "CPE220(TP-LINK|UN|N300-2):1.0\r\n"
144         "\r\n\xff";
145
146
147 /** Allocates a new image partition */
148 struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
149         struct image_partition_entry entry = {name, len, malloc(len)};
150         if (!entry.data)
151                 error(1, errno, "malloc");
152
153         return entry;
154 }
155
156 /** Frees an image partition */
157 void free_image_partition(struct image_partition_entry entry) {
158         free(entry.data);
159 }
160
161 /** Generates the partition-table partition */
162 struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
163         struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
164
165         char *s = (char *)entry.data, *end = (char *)(s+entry.size);
166
167         *(s++) = 0x00;
168         *(s++) = 0x04;
169         *(s++) = 0x00;
170         *(s++) = 0x00;
171
172         size_t i;
173         for (i = 0; p[i].name; i++) {
174                 size_t len = end-s;
175                 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
176
177                 if (w > len-1)
178                         error(1, 0, "flash partition table overflow?");
179
180                 s += w;
181         }
182
183         s++;
184
185         memset(s, 0xff, end-s);
186
187         return entry;
188 }
189
190
191 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
192 static inline uint8_t bcd(uint8_t v) {
193         return 0x10 * (v/10) + v%10;
194 }
195
196
197 /** Generates the soft-version partition */
198 struct image_partition_entry make_soft_version(uint32_t rev) {
199         struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
200         struct soft_version *s = (struct soft_version *)entry.data;
201
202         time_t t;
203         if (time(&t) == (time_t)(-1))
204                 error(1, errno, "time");
205
206         struct tm *tm = localtime(&t);
207
208         s->magic = htonl(0x0000000c);
209         s->zero = 0;
210         s->pad1 = 0xff;
211
212         s->version_major = 0;
213         s->version_minor = 0;
214         s->version_patch = 0;
215
216         s->year_hi = bcd((1900+tm->tm_year)/100);
217         s->year_lo = bcd(tm->tm_year%100);
218         s->month = bcd(tm->tm_mon+1);
219         s->day = bcd(tm->tm_mday);
220         s->rev = htonl(rev);
221
222         s->pad2 = 0xff;
223
224         return entry;
225 }
226
227 /** Generates the support-list partition */
228 struct image_partition_entry make_support_list(const unsigned char *support_list, size_t len) {
229         struct image_partition_entry entry = alloc_image_partition("support-list", len);
230         memcpy(entry.data, support_list, len);
231         return entry;
232 }
233
234 /** Creates a new image partition with an arbitrary name from a file */
235 struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
236         struct stat statbuf;
237
238         if (stat(filename, &statbuf) < 0)
239                 error(1, errno, "unable to stat file `%s'", filename);
240
241         size_t len = statbuf.st_size;
242
243         if (add_jffs2_eof)
244                 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
245
246         struct image_partition_entry entry = alloc_image_partition(part_name, len);
247
248         FILE *file = fopen(filename, "rb");
249         if (!file)
250                 error(1, errno, "unable to open file `%s'", filename);
251
252         if (fread(entry.data, statbuf.st_size, 1, file) != 1)
253                 error(1, errno, "unable to read file `%s'", filename);
254
255         if (add_jffs2_eof) {
256                 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
257
258                 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
259                 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
260         }
261
262         fclose(file);
263
264         return entry;
265 }
266
267
268 /**
269    Copies a list of image partitions into an image buffer and generates the image partition table while doing so
270
271    Example image partition table:
272
273      fwup-ptn partition-table base 0x00800 size 0x00800
274      fwup-ptn os-image base 0x01000 size 0x113b45
275      fwup-ptn file-system base 0x114b45 size 0x1d0004
276      fwup-ptn support-list base 0x2e4b49 size 0x000d1
277
278    Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
279    the end of the partition table is marked with a zero byte.
280
281    The firmware image must contain at least the partition-table and support-list partitions
282    to be accepted. There aren't any alignment constraints for the image partitions.
283
284    The partition-table partition contains the actual flash layout; partitions
285    from the image partition table are mapped to the corresponding flash partitions during
286    the firmware upgrade. The support-list partition contains a list of devices supported by
287    the firmware image.
288
289    The base offsets in the firmware partition table are relative to the end
290    of the vendor information block, so the partition-table partition will
291    actually start at offset 0x1814 of the image.
292
293    I think partition-table must be the first partition in the firmware image.
294 */
295 void put_partitions(uint8_t *buffer, const struct image_partition_entry *parts) {
296         size_t i;
297         char *image_pt = (char *)buffer, *end = image_pt + 0x800;
298
299         size_t base = 0x800;
300         for (i = 0; parts[i].name; i++) {
301                 memcpy(buffer + base, parts[i].data, parts[i].size);
302
303                 size_t len = end-image_pt;
304                 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);
305
306                 if (w > len-1)
307                         error(1, 0, "image partition table overflow?");
308
309                 image_pt += w;
310
311                 base += parts[i].size;
312         }
313
314         image_pt++;
315
316         memset(image_pt, 0xff, end-image_pt);
317 }
318
319 /** Generates and writes the image MD5 checksum */
320 void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
321         MD5_CTX ctx;
322
323         MD5_Init(&ctx);
324         MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
325         MD5_Update(&ctx, buffer, len);
326         MD5_Final(md5, &ctx);
327 }
328
329
330 /**
331    Generates the firmware image in factory format
332
333    Image format:
334
335      Bytes (hex)  Usage
336      -----------  -----
337      0000-0003    Image size (4 bytes, big endian)
338      0004-0013    MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
339      0014-1013    Vendor information (4096 bytes, padded with 0xff; there seem to be older
340                   (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
341      1014-1813    Image partition table (2048 bytes, padded with 0xff)
342      1814-xxxx    Firmware partitions
343 */
344 void * generate_factory_image(const unsigned char *vendor, size_t vendor_len, const struct image_partition_entry *parts, size_t *len) {
345         *len = 0x1814;
346
347         size_t i;
348         for (i = 0; parts[i].name; i++)
349                 *len += parts[i].size;
350
351         uint8_t *image = malloc(*len);
352         if (!image)
353                 error(1, errno, "malloc");
354
355         image[0] = *len >> 24;
356         image[1] = *len >> 16;
357         image[2] = *len >> 8;
358         image[3] = *len;
359
360         memcpy(image+0x14, vendor, vendor_len);
361         memset(image+0x14+vendor_len, 0xff, 4096-vendor_len);
362
363         put_partitions(image + 0x1014, parts);
364         put_md5(image+0x04, image+0x14, *len-0x14);
365
366         return image;
367 }
368
369 /**
370    Generates the firmware image in sysupgrade format
371
372    This makes some assumptions about the provided flash and image partition tables and
373    should be generalized when TP-LINK starts building its safeloader into hardware with
374    different flash layouts.
375 */
376 void * generate_sysupgrade_image(const struct flash_partition_entry *flash_parts, const struct image_partition_entry *image_parts, size_t *len) {
377         const struct flash_partition_entry *flash_os_image = &flash_parts[5];
378         const struct flash_partition_entry *flash_soft_version = &flash_parts[6];
379         const struct flash_partition_entry *flash_support_list = &flash_parts[7];
380         const struct flash_partition_entry *flash_file_system = &flash_parts[8];
381
382         const struct image_partition_entry *image_os_image = &image_parts[3];
383         const struct image_partition_entry *image_soft_version = &image_parts[1];
384         const struct image_partition_entry *image_support_list = &image_parts[2];
385         const struct image_partition_entry *image_file_system = &image_parts[4];
386
387         assert(strcmp(flash_os_image->name, "os-image") == 0);
388         assert(strcmp(flash_soft_version->name, "soft-version") == 0);
389         assert(strcmp(flash_support_list->name, "support-list") == 0);
390         assert(strcmp(flash_file_system->name, "file-system") == 0);
391
392         assert(strcmp(image_os_image->name, "os-image") == 0);
393         assert(strcmp(image_soft_version->name, "soft-version") == 0);
394         assert(strcmp(image_support_list->name, "support-list") == 0);
395         assert(strcmp(image_file_system->name, "file-system") == 0);
396
397         if (image_os_image->size > flash_os_image->size)
398                 error(1, 0, "kernel image too big (more than %u bytes)", (unsigned)flash_os_image->size);
399         if (image_file_system->size > flash_file_system->size)
400                 error(1, 0, "rootfs image too big (more than %u bytes)", (unsigned)flash_file_system->size);
401
402         *len = flash_file_system->base - flash_os_image->base + image_file_system->size;
403
404         uint8_t *image = malloc(*len);
405         if (!image)
406                 error(1, errno, "malloc");
407
408         memset(image, 0xff, *len);
409
410         memcpy(image, image_os_image->data, image_os_image->size);
411         memcpy(image + flash_soft_version->base - flash_os_image->base, image_soft_version->data, image_soft_version->size);
412         memcpy(image + flash_support_list->base - flash_os_image->base, image_support_list->data, image_support_list->size);
413         memcpy(image + flash_file_system->base - flash_os_image->base, image_file_system->data, image_file_system->size);
414
415         return image;
416 }
417
418
419 /** Generates an image for CPE210/220/510/520 and writes it to a file */
420 static void do_cpe510(const char *output, const char *kernel_image, const char *rootfs_image, uint32_t rev, bool add_jffs2_eof, bool sysupgrade) {
421         struct image_partition_entry parts[6] = {};
422
423         parts[0] = make_partition_table(cpe510_partitions);
424         parts[1] = make_soft_version(rev);
425         parts[2] = make_support_list(cpe510_support_list, sizeof(cpe510_support_list)-1);
426         parts[3] = read_file("os-image", kernel_image, false);
427         parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
428
429         size_t len;
430         void *image;
431         if (sysupgrade)
432                 image = generate_sysupgrade_image(cpe510_partitions, parts, &len);
433         else
434                 image = generate_factory_image(cpe510_vendor, sizeof(cpe510_vendor)-1, parts, &len);
435
436         FILE *file = fopen(output, "wb");
437         if (!file)
438                 error(1, errno, "unable to open output file");
439
440         if (fwrite(image, len, 1, file) != 1)
441                 error(1, 0, "unable to write output file");
442
443         fclose(file);
444
445         free(image);
446
447         size_t i;
448         for (i = 0; parts[i].name; i++)
449                 free_image_partition(parts[i]);
450 }
451
452
453 /** Usage output */
454 void usage(const char *argv0) {
455         fprintf(stderr,
456                 "Usage: %s [OPTIONS...]\n"
457                 "\n"
458                 "Options:\n"
459                 "  -B <board>      create image for the board specified with <board>\n"
460                 "  -k <file>       read kernel image from the file <file>\n"
461                 "  -r <file>       read rootfs image from the file <file>\n"
462                 "  -o <file>       write output to the file <file>\n"
463                 "  -V <rev>        sets the revision number to <rev>\n"
464                 "  -j              add jffs2 end-of-filesystem markers\n"
465                 "  -S              create sysupgrade instead of factory image\n"
466                 "  -h              show this help\n",
467                 argv0
468         );
469 };
470
471
472 int main(int argc, char *argv[]) {
473         const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
474         bool add_jffs2_eof = false, sysupgrade = false;
475         unsigned rev = 0;
476
477         while (true) {
478                 int c;
479
480                 c = getopt(argc, argv, "B:k:r:o:V:jSh");
481                 if (c == -1)
482                         break;
483
484                 switch (c) {
485                 case 'B':
486                         board = optarg;
487                         break;
488
489                 case 'k':
490                         kernel_image = optarg;
491                         break;
492
493                 case 'r':
494                         rootfs_image = optarg;
495                         break;
496
497                 case 'o':
498                         output = optarg;
499                         break;
500
501                 case 'V':
502                         sscanf(optarg, "r%u", &rev);
503                         break;
504
505                 case 'j':
506                         add_jffs2_eof = true;
507                         break;
508
509                 case 'S':
510                         sysupgrade = true;
511                         break;
512
513                 case 'h':
514                         usage(argv[0]);
515                         return 0;
516
517                 default:
518                         usage(argv[0]);
519                         return 1;
520                 }
521         }
522
523         if (!board)
524                 error(1, 0, "no board has been specified");
525         if (!kernel_image)
526                 error(1, 0, "no kernel image has been specified");
527         if (!rootfs_image)
528                 error(1, 0, "no rootfs image has been specified");
529         if (!output)
530                 error(1, 0, "no output filename has been specified");
531
532         if (strcmp(board, "CPE510") == 0)
533                 do_cpe510(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade);
534         else
535                 error(1, 0, "unsupported board %s", board);
536
537         return 0;
538 }