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