firmware-utils: remove dependency on error.h in tplink-safeloader
[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 <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 unsigned char cpe510_vendor[] = "\x00\x00\x00\x1f""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    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.
135 */
136 static const unsigned char cpe510_support_list[] =
137         "\x00\x00\x00\xc8\x00\x00\x00\x00"
138         "SupportList:\r\n"
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"
143         "\r\n\xff";
144
145 #define error(_ret, _errno, _str, ...)                          \
146         do {                                                    \
147                 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__,  \
148                         strerror(_errno));                      \
149                 if (_ret)                                       \
150                         exit(_ret);                             \
151         } while (0)
152
153
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)};
157         if (!entry.data)
158                 error(1, errno, "malloc");
159
160         return entry;
161 }
162
163 /** Frees an image partition */
164 void free_image_partition(struct image_partition_entry entry) {
165         free(entry.data);
166 }
167
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);
171
172         char *s = (char *)entry.data, *end = (char *)(s+entry.size);
173
174         *(s++) = 0x00;
175         *(s++) = 0x04;
176         *(s++) = 0x00;
177         *(s++) = 0x00;
178
179         size_t i;
180         for (i = 0; p[i].name; i++) {
181                 size_t len = end-s;
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);
183
184                 if (w > len-1)
185                         error(1, 0, "flash partition table overflow?");
186
187                 s += w;
188         }
189
190         s++;
191
192         memset(s, 0xff, end-s);
193
194         return entry;
195 }
196
197
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;
201 }
202
203
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;
208
209         time_t t;
210
211         if (time(&t) == (time_t)(-1))
212                 error(1, errno, "time");
213
214         struct tm *tm = localtime(&t);
215
216         s->magic = htonl(0x0000000c);
217         s->zero = 0;
218         s->pad1 = 0xff;
219
220         s->version_major = 0;
221         s->version_minor = 0;
222         s->version_patch = 0;
223
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);
228         s->rev = htonl(rev);
229
230         s->pad2 = 0xff;
231
232         return entry;
233 }
234
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);
239         return entry;
240 }
241
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) {
244         struct stat statbuf;
245
246         if (stat(filename, &statbuf) < 0)
247                 error(1, errno, "unable to stat file `%s'", filename);
248
249         size_t len = statbuf.st_size;
250
251         if (add_jffs2_eof)
252                 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
253
254         struct image_partition_entry entry = alloc_image_partition(part_name, len);
255
256         FILE *file = fopen(filename, "rb");
257         if (!file)
258                 error(1, errno, "unable to open file `%s'", filename);
259
260         if (fread(entry.data, statbuf.st_size, 1, file) != 1)
261                 error(1, errno, "unable to read file `%s'", filename);
262
263         if (add_jffs2_eof) {
264                 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
265
266                 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
267                 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
268         }
269
270         fclose(file);
271
272         return entry;
273 }
274
275
276 /**
277    Copies a list of image partitions into an image buffer and generates the image partition table while doing so
278
279    Example image partition table:
280
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
285
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.
288
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.
291
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
295    the firmware image.
296
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.
300
301    I think partition-table must be the first partition in the firmware image.
302 */
303 void put_partitions(uint8_t *buffer, const struct image_partition_entry *parts) {
304         size_t i;
305         char *image_pt = (char *)buffer, *end = image_pt + 0x800;
306
307         size_t base = 0x800;
308         for (i = 0; parts[i].name; i++) {
309                 memcpy(buffer + base, parts[i].data, parts[i].size);
310
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);
313
314                 if (w > len-1)
315                         error(1, 0, "image partition table overflow?");
316
317                 image_pt += w;
318
319                 base += parts[i].size;
320         }
321
322         image_pt++;
323
324         memset(image_pt, 0xff, end-image_pt);
325 }
326
327 /** Generates and writes the image MD5 checksum */
328 void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
329         MD5_CTX ctx;
330
331         MD5_Init(&ctx);
332         MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
333         MD5_Update(&ctx, buffer, len);
334         MD5_Final(md5, &ctx);
335 }
336
337
338 /**
339    Generates the firmware image in factory format
340
341    Image format:
342
343      Bytes (hex)  Usage
344      -----------  -----
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
351 */
352 void * generate_factory_image(const unsigned char *vendor, size_t vendor_len, const struct image_partition_entry *parts, size_t *len) {
353         *len = 0x1814;
354
355         size_t i;
356         for (i = 0; parts[i].name; i++)
357                 *len += parts[i].size;
358
359         uint8_t *image = malloc(*len);
360         if (!image)
361                 error(1, errno, "malloc");
362
363         image[0] = *len >> 24;
364         image[1] = *len >> 16;
365         image[2] = *len >> 8;
366         image[3] = *len;
367
368         memcpy(image+0x14, vendor, vendor_len);
369         memset(image+0x14+vendor_len, 0xff, 4096-vendor_len);
370
371         put_partitions(image + 0x1014, parts);
372         put_md5(image+0x04, image+0x14, *len-0x14);
373
374         return image;
375 }
376
377 /**
378    Generates the firmware image in sysupgrade format
379
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.
383 */
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];
389
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];
394
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);
399
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);
404
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);
409
410         *len = flash_file_system->base - flash_os_image->base + image_file_system->size;
411
412         uint8_t *image = malloc(*len);
413         if (!image)
414                 error(1, errno, "malloc");
415
416         memset(image, 0xff, *len);
417
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);
422
423         return image;
424 }
425
426
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] = {};
430
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);
436
437         size_t len;
438         void *image;
439         if (sysupgrade)
440                 image = generate_sysupgrade_image(cpe510_partitions, parts, &len);
441         else
442                 image = generate_factory_image(cpe510_vendor, sizeof(cpe510_vendor)-1, parts, &len);
443
444         FILE *file = fopen(output, "wb");
445         if (!file)
446                 error(1, errno, "unable to open output file");
447
448         if (fwrite(image, len, 1, file) != 1)
449                 error(1, 0, "unable to write output file");
450
451         fclose(file);
452
453         free(image);
454
455         size_t i;
456         for (i = 0; parts[i].name; i++)
457                 free_image_partition(parts[i]);
458 }
459
460
461 /** Usage output */
462 void usage(const char *argv0) {
463         fprintf(stderr,
464                 "Usage: %s [OPTIONS...]\n"
465                 "\n"
466                 "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",
475                 argv0
476         );
477 };
478
479
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;
483         unsigned rev = 0;
484
485         while (true) {
486                 int c;
487
488                 c = getopt(argc, argv, "B:k:r:o:V:jSh");
489                 if (c == -1)
490                         break;
491
492                 switch (c) {
493                 case 'B':
494                         board = optarg;
495                         break;
496
497                 case 'k':
498                         kernel_image = optarg;
499                         break;
500
501                 case 'r':
502                         rootfs_image = optarg;
503                         break;
504
505                 case 'o':
506                         output = optarg;
507                         break;
508
509                 case 'V':
510                         sscanf(optarg, "r%u", &rev);
511                         break;
512
513                 case 'j':
514                         add_jffs2_eof = true;
515                         break;
516
517                 case 'S':
518                         sysupgrade = true;
519                         break;
520
521                 case 'h':
522                         usage(argv[0]);
523                         return 0;
524
525                 default:
526                         usage(argv[0]);
527                         return 1;
528                 }
529         }
530
531         if (!board)
532                 error(1, 0, "no board has been specified");
533         if (!kernel_image)
534                 error(1, 0, "no kernel image has been specified");
535         if (!rootfs_image)
536                 error(1, 0, "no rootfs image has been specified");
537         if (!output)
538                 error(1, 0, "no output filename has been specified");
539
540         if (strcmp(board, "CPE510") == 0)
541                 do_cpe510(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade);
542         else
543                 error(1, 0, "unsupported board %s", board);
544
545         return 0;
546 }