firmware-utils/tplink-safeloader: add support for TP-Link RE450
[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 #define MAX_PARTITIONS  32
57
58 /** An image partition table entry */
59 struct image_partition_entry {
60         const char *name;
61         size_t size;
62         uint8_t *data;
63 };
64
65 /** A flash partition table entry */
66 struct flash_partition_entry {
67         const char *name;
68         uint32_t base;
69         uint32_t size;
70 };
71
72 /** Firmware layout description */
73 struct device_info {
74         const char *id;
75         const char *vendor;
76         const char *support_list;
77         char support_trail;
78         const struct flash_partition_entry partitions[MAX_PARTITIONS+1];
79         const char *first_sysupgrade_partition;
80         const char *last_sysupgrade_partition;
81 };
82
83 /** The content of the soft-version structure */
84 struct __attribute__((__packed__)) soft_version {
85         uint32_t magic;
86         uint32_t zero;
87         uint8_t pad1;
88         uint8_t version_major;
89         uint8_t version_minor;
90         uint8_t version_patch;
91         uint8_t year_hi;
92         uint8_t year_lo;
93         uint8_t month;
94         uint8_t day;
95         uint32_t rev;
96         uint8_t pad2;
97 };
98
99
100 static const uint8_t jffs2_eof_mark[4] = {0xde, 0xad, 0xc0, 0xde};
101
102
103 /**
104    Salt for the MD5 hash
105
106    Fortunately, TP-LINK seems to use the same salt for most devices which use
107    the new image format.
108 */
109 static const uint8_t md5_salt[16] = {
110         0x7a, 0x2b, 0x15, 0xed,
111         0x9b, 0x98, 0x59, 0x6d,
112         0xe5, 0x04, 0xab, 0x44,
113         0xac, 0x2a, 0x9f, 0x4e,
114 };
115
116
117 /** Firmware layout table */
118 static struct device_info boards[] = {
119         /** Firmware layout for the CPE210/220 */
120         {
121                 .id     = "CPE210",
122                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
123                 .support_list =
124                         "SupportList:\r\n"
125                         "CPE210(TP-LINK|UN|N300-2):1.0\r\n"
126                         "CPE210(TP-LINK|UN|N300-2):1.1\r\n"
127                         "CPE210(TP-LINK|US|N300-2):1.1\r\n"
128                         "CPE210(TP-LINK|EU|N300-2):1.1\r\n"
129                         "CPE220(TP-LINK|UN|N300-2):1.1\r\n"
130                         "CPE220(TP-LINK|US|N300-2):1.1\r\n"
131                         "CPE220(TP-LINK|EU|N300-2):1.1\r\n",
132                 .support_trail = '\xff',
133
134                 .partitions = {
135                         {"fs-uboot", 0x00000, 0x20000},
136                         {"partition-table", 0x20000, 0x02000},
137                         {"default-mac", 0x30000, 0x00020},
138                         {"product-info", 0x31100, 0x00100},
139                         {"signature", 0x32000, 0x00400},
140                         {"os-image", 0x40000, 0x170000},
141                         {"soft-version", 0x1b0000, 0x00100},
142                         {"support-list", 0x1b1000, 0x00400},
143                         {"file-system", 0x1c0000, 0x600000},
144                         {"user-config", 0x7c0000, 0x10000},
145                         {"default-config", 0x7d0000, 0x10000},
146                         {"log", 0x7e0000, 0x10000},
147                         {"radio", 0x7f0000, 0x10000},
148                         {NULL, 0, 0}
149                 },
150
151                 .first_sysupgrade_partition = "os-image",
152                 .last_sysupgrade_partition = "file-system",
153         },
154
155         /** Firmware layout for the CPE510/520 */
156         {
157                 .id     = "CPE510",
158                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
159                 .support_list =
160                         "SupportList:\r\n"
161                         "CPE510(TP-LINK|UN|N300-5):1.0\r\n"
162                         "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
163                         "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
164                         "CPE510(TP-LINK|US|N300-5):1.1\r\n"
165                         "CPE510(TP-LINK|EU|N300-5):1.1\r\n"
166                         "CPE520(TP-LINK|UN|N300-5):1.1\r\n"
167                         "CPE520(TP-LINK|US|N300-5):1.1\r\n"
168                         "CPE520(TP-LINK|EU|N300-5):1.1\r\n",
169                 .support_trail = '\xff',
170
171                 .partitions = {
172                         {"fs-uboot", 0x00000, 0x20000},
173                         {"partition-table", 0x20000, 0x02000},
174                         {"default-mac", 0x30000, 0x00020},
175                         {"product-info", 0x31100, 0x00100},
176                         {"signature", 0x32000, 0x00400},
177                         {"os-image", 0x40000, 0x170000},
178                         {"soft-version", 0x1b0000, 0x00100},
179                         {"support-list", 0x1b1000, 0x00400},
180                         {"file-system", 0x1c0000, 0x600000},
181                         {"user-config", 0x7c0000, 0x10000},
182                         {"default-config", 0x7d0000, 0x10000},
183                         {"log", 0x7e0000, 0x10000},
184                         {"radio", 0x7f0000, 0x10000},
185                         {NULL, 0, 0}
186                 },
187
188                 .first_sysupgrade_partition = "os-image",
189                 .last_sysupgrade_partition = "file-system",
190         },
191
192         /** Firmware layout for the C2600 */
193         {
194                 .id = "C2600",
195                 .vendor = "",
196                 .support_list =
197                         "SupportList:\r\n"
198                         "{product_name:Archer C2600,product_ver:1.0.0,special_id:00000000}\r\n",
199                 .support_trail = '\x00',
200
201                 .partitions = {
202                         {"SBL1", 0x00000, 0x20000},
203                         {"MIBIB", 0x20000, 0x20000},
204                         {"SBL2", 0x40000, 0x20000},
205                         {"SBL3", 0x60000, 0x30000},
206                         {"DDRCONFIG", 0x90000, 0x10000},
207                         {"SSD", 0xa0000, 0x10000},
208                         {"TZ", 0xb0000, 0x30000},
209                         {"RPM", 0xe0000, 0x20000},
210                         {"fs-uboot", 0x100000, 0x70000},
211                         {"uboot-env", 0x170000, 0x40000},
212                         {"radio", 0x1b0000, 0x40000},
213                         {"os-image", 0x1f0000, 0x200000},
214                         {"file-system", 0x3f0000, 0x1b00000},
215                         {"default-mac", 0x1ef0000, 0x00200},
216                         {"pin", 0x1ef0200, 0x00200},
217                         {"product-info", 0x1ef0400, 0x0fc00},
218                         {"partition-table", 0x1f00000, 0x10000},
219                         {"soft-version", 0x1f10000, 0x10000},
220                         {"support-list", 0x1f20000, 0x10000},
221                         {"profile", 0x1f30000, 0x10000},
222                         {"default-config", 0x1f40000, 0x10000},
223                         {"user-config", 0x1f50000, 0x40000},
224                         {"qos-db", 0x1f90000, 0x40000},
225                         {"usb-config", 0x1fd0000, 0x10000},
226                         {"log", 0x1fe0000, 0x20000},
227                         {NULL, 0, 0}
228                 },
229
230                 .first_sysupgrade_partition = "os-image",
231                 .last_sysupgrade_partition = "file-system"
232         },
233
234         /** Firmware layout for the C9 */
235         {
236                 .id = "ARCHERC9",
237                 .vendor = "",
238                 .support_list =
239                         "SupportList:\n"
240                         "{product_name:ArcherC9,"
241                         "product_ver:1.0.0,"
242                         "special_id:00000000}\n",
243                 .support_trail = '\x00',
244
245                 .partitions = {
246                         {"fs-uboot", 0x00000, 0x40000},
247                         {"os-image", 0x40000, 0x200000},
248                         {"file-system", 0x240000, 0xc00000},
249                         {"default-mac", 0xe40000, 0x00200},
250                         {"pin", 0xe40200, 0x00200},
251                         {"product-info", 0xe40400, 0x00200},
252                         {"partition-table", 0xe50000, 0x10000},
253                         {"soft-version", 0xe60000, 0x00200},
254                         {"support-list", 0xe61000, 0x0f000},
255                         {"profile", 0xe70000, 0x10000},
256                         {"default-config", 0xe80000, 0x10000},
257                         {"user-config", 0xe90000, 0x50000},
258                         {"log", 0xee0000, 0x100000},
259                         {"radio_bk", 0xfe0000, 0x10000},
260                         {"radio", 0xff0000, 0x10000},
261                         {NULL, 0, 0}
262                 },
263
264                 .first_sysupgrade_partition = "os-image",
265                 .last_sysupgrade_partition = "file-system"
266         },
267
268         /** Firmware layout for the EAP120 */
269         {
270                 .id     = "EAP120",
271                 .vendor = "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
272                 .support_list =
273                         "SupportList:\r\n"
274                         "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
275                 .support_trail = '\xff',
276
277                 .partitions = {
278                         {"fs-uboot", 0x00000, 0x20000},
279                         {"partition-table", 0x20000, 0x02000},
280                         {"default-mac", 0x30000, 0x00020},
281                         {"support-list", 0x31000, 0x00100},
282                         {"product-info", 0x31100, 0x00100},
283                         {"soft-version", 0x32000, 0x00100},
284                         {"os-image", 0x40000, 0x180000},
285                         {"file-system", 0x1c0000, 0x600000},
286                         {"user-config", 0x7c0000, 0x10000},
287                         {"backup-config", 0x7d0000, 0x10000},
288                         {"log", 0x7e0000, 0x10000},
289                         {"radio", 0x7f0000, 0x10000},
290                         {NULL, 0, 0}
291                 },
292
293                 .first_sysupgrade_partition = "os-image",
294                 .last_sysupgrade_partition = "file-system"
295         },
296
297         /** Firmware layout for the TL-WR1043 v4 */
298         {
299                 .id     = "TLWR1043NDV4",
300                 .vendor = "",
301                 .support_list =
302                         "SupportList:\n"
303                         "{product_name:TL-WR1043ND,product_ver:4.0.0,special_id:45550000}\n",
304                 .support_trail = '\x00',
305
306                 /**
307                     We use a bigger os-image partition than the stock images (and thus
308                     smaller file-system), as our kernel doesn't fit in the stock firmware's
309                     1MB os-image.
310                 */
311                 .partitions = {
312                         {"fs-uboot", 0x00000, 0x20000},
313                         {"os-image", 0x20000, 0x180000},
314                         {"file-system", 0x1a0000, 0xdb0000},
315                         {"default-mac", 0xf50000, 0x00200},
316                         {"pin", 0xf50200, 0x00200},
317                         {"product-info", 0xf50400, 0x0fc00},
318                         {"soft-version", 0xf60000, 0x0b000},
319                         {"support-list", 0xf6b000, 0x04000},
320                         {"profile", 0xf70000, 0x04000},
321                         {"default-config", 0xf74000, 0x0b000},
322                         {"user-config", 0xf80000, 0x40000},
323                         {"partition-table", 0xfc0000, 0x10000},
324                         {"log", 0xfd0000, 0x20000},
325                         {"radio", 0xff0000, 0x10000},
326                         {NULL, 0, 0}
327                 },
328
329                 .first_sysupgrade_partition = "os-image",
330                 .last_sysupgrade_partition = "file-system"
331         },
332
333         /** Firmware layout for the RE450 */
334         {
335                 .id = "RE450",
336                 .vendor = "",
337                 .support_list =
338                         "SupportList:\r\n"
339                         "{product_name:RE450,product_ver:1.0.0,special_id:00000000}\r\n"
340                         "{product_name:RE450,product_ver:1.0.0,special_id:55530000}\r\n"
341                         "{product_name:RE450,product_ver:1.0.0,special_id:45550000}\r\n"
342                         "{product_name:RE450,product_ver:1.0.0,special_id:4A500000}\r\n"
343                         "{product_name:RE450,product_ver:1.0.0,special_id:43410000}\r\n"
344                         "{product_name:RE450,product_ver:1.0.0,special_id:41550000}\r\n"
345                         "{product_name:RE450,product_ver:1.0.0,special_id:4B520000}\r\n"
346                         "{product_name:RE450,product_ver:1.0.0,special_id:55534100}\r\n",
347                 .support_trail = '\x00',
348
349                 /**
350                    The flash partition table for RE450;
351                    it is almost the same as the one used by the stock images,
352                    576KB were moved from file-system to os-image.
353                 */
354                 .partitions = {
355                         {"fs-uboot", 0x00000, 0x20000},
356                         {"os-image", 0x20000, 0x150000},
357                         {"file-system", 0x170000, 0x4a0000},
358                         {"partition-table", 0x600000, 0x02000},
359                         {"default-mac", 0x610000, 0x00020},
360                         {"pin", 0x610100, 0x00020},
361                         {"product-info", 0x611100, 0x01000},
362                         {"soft-version", 0x620000, 0x01000},
363                         {"support-list", 0x621000, 0x01000},
364                         {"profile", 0x622000, 0x08000},
365                         {"user-config", 0x630000, 0x10000},
366                         {"default-config", 0x640000, 0x10000},
367                         {"radio", 0x7f0000, 0x10000},
368                         {NULL, 0, 0}
369                 },
370
371                 .first_sysupgrade_partition = "os-image",
372                 .last_sysupgrade_partition = "file-system"
373         },
374
375         {}
376 };
377
378 #define error(_ret, _errno, _str, ...)                          \
379         do {                                                    \
380                 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__,  \
381                         strerror(_errno));                      \
382                 if (_ret)                                       \
383                         exit(_ret);                             \
384         } while (0)
385
386
387 /** Stores a uint32 as big endian */
388 static inline void put32(uint8_t *buf, uint32_t val) {
389         buf[0] = val >> 24;
390         buf[1] = val >> 16;
391         buf[2] = val >> 8;
392         buf[3] = val;
393 }
394
395 /** Allocates a new image partition */
396 static struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
397         struct image_partition_entry entry = {name, len, malloc(len)};
398         if (!entry.data)
399                 error(1, errno, "malloc");
400
401         return entry;
402 }
403
404 /** Frees an image partition */
405 static void free_image_partition(struct image_partition_entry entry) {
406         free(entry.data);
407 }
408
409 /** Generates the partition-table partition */
410 static struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
411         struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
412
413         char *s = (char *)entry.data, *end = (char *)(s+entry.size);
414
415         *(s++) = 0x00;
416         *(s++) = 0x04;
417         *(s++) = 0x00;
418         *(s++) = 0x00;
419
420         size_t i;
421         for (i = 0; p[i].name; i++) {
422                 size_t len = end-s;
423                 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
424
425                 if (w > len-1)
426                         error(1, 0, "flash partition table overflow?");
427
428                 s += w;
429         }
430
431         s++;
432
433         memset(s, 0xff, end-s);
434
435         return entry;
436 }
437
438
439 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
440 static inline uint8_t bcd(uint8_t v) {
441         return 0x10 * (v/10) + v%10;
442 }
443
444
445 /** Generates the soft-version partition */
446 static struct image_partition_entry make_soft_version(uint32_t rev) {
447         struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
448         struct soft_version *s = (struct soft_version *)entry.data;
449
450         time_t t;
451
452         if (time(&t) == (time_t)(-1))
453                 error(1, errno, "time");
454
455         struct tm *tm = localtime(&t);
456
457         s->magic = htonl(0x0000000c);
458         s->zero = 0;
459         s->pad1 = 0xff;
460
461         s->version_major = 0;
462         s->version_minor = 0;
463         s->version_patch = 0;
464
465         s->year_hi = bcd((1900+tm->tm_year)/100);
466         s->year_lo = bcd(tm->tm_year%100);
467         s->month = bcd(tm->tm_mon+1);
468         s->day = bcd(tm->tm_mday);
469         s->rev = htonl(rev);
470
471         s->pad2 = 0xff;
472
473         return entry;
474 }
475
476 /** Generates the support-list partition */
477 static struct image_partition_entry make_support_list(const struct device_info *info) {
478         size_t len = strlen(info->support_list);
479         struct image_partition_entry entry = alloc_image_partition("support-list", len + 9);
480
481         put32(entry.data, len);
482         memset(entry.data+4, 0, 4);
483         memcpy(entry.data+8, info->support_list, len);
484         entry.data[len+8] = info->support_trail;
485
486         return entry;
487 }
488
489 /** Creates a new image partition with an arbitrary name from a file */
490 static struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
491         struct stat statbuf;
492
493         if (stat(filename, &statbuf) < 0)
494                 error(1, errno, "unable to stat file `%s'", filename);
495
496         size_t len = statbuf.st_size;
497
498         if (add_jffs2_eof)
499                 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
500
501         struct image_partition_entry entry = alloc_image_partition(part_name, len);
502
503         FILE *file = fopen(filename, "rb");
504         if (!file)
505                 error(1, errno, "unable to open file `%s'", filename);
506
507         if (fread(entry.data, statbuf.st_size, 1, file) != 1)
508                 error(1, errno, "unable to read file `%s'", filename);
509
510         if (add_jffs2_eof) {
511                 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
512
513                 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
514                 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
515         }
516
517         fclose(file);
518
519         return entry;
520 }
521
522
523 /**
524    Copies a list of image partitions into an image buffer and generates the image partition table while doing so
525
526    Example image partition table:
527
528      fwup-ptn partition-table base 0x00800 size 0x00800
529      fwup-ptn os-image base 0x01000 size 0x113b45
530      fwup-ptn file-system base 0x114b45 size 0x1d0004
531      fwup-ptn support-list base 0x2e4b49 size 0x000d1
532
533    Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
534    the end of the partition table is marked with a zero byte.
535
536    The firmware image must contain at least the partition-table and support-list partitions
537    to be accepted. There aren't any alignment constraints for the image partitions.
538
539    The partition-table partition contains the actual flash layout; partitions
540    from the image partition table are mapped to the corresponding flash partitions during
541    the firmware upgrade. The support-list partition contains a list of devices supported by
542    the firmware image.
543
544    The base offsets in the firmware partition table are relative to the end
545    of the vendor information block, so the partition-table partition will
546    actually start at offset 0x1814 of the image.
547
548    I think partition-table must be the first partition in the firmware image.
549 */
550 static void put_partitions(uint8_t *buffer, const struct flash_partition_entry *flash_parts, const struct image_partition_entry *parts) {
551         size_t i, j;
552         char *image_pt = (char *)buffer, *end = image_pt + 0x800;
553
554         size_t base = 0x800;
555         for (i = 0; parts[i].name; i++) {
556                 for (j = 0; flash_parts[j].name; j++) {
557                         if (!strcmp(flash_parts[j].name, parts[i].name)) {
558                                 if (parts[i].size > flash_parts[j].size)
559                                         error(1, 0, "%s partition too big (more than %u bytes)", flash_parts[j].name, (unsigned)flash_parts[j].size);
560                                 break;
561                         }
562                 }
563
564                 assert(flash_parts[j].name);
565
566                 memcpy(buffer + base, parts[i].data, parts[i].size);
567
568                 size_t len = end-image_pt;
569                 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);
570
571                 if (w > len-1)
572                         error(1, 0, "image partition table overflow?");
573
574                 image_pt += w;
575
576                 base += parts[i].size;
577         }
578 }
579
580 /** Generates and writes the image MD5 checksum */
581 static void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
582         MD5_CTX ctx;
583
584         MD5_Init(&ctx);
585         MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
586         MD5_Update(&ctx, buffer, len);
587         MD5_Final(md5, &ctx);
588 }
589
590
591 /**
592    Generates the firmware image in factory format
593
594    Image format:
595
596      Bytes (hex)  Usage
597      -----------  -----
598      0000-0003    Image size (4 bytes, big endian)
599      0004-0013    MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
600      0014-0017    Vendor information length (without padding) (4 bytes, big endian)
601      0018-1013    Vendor information (4092 bytes, padded with 0xff; there seem to be older
602                   (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
603      1014-1813    Image partition table (2048 bytes, padded with 0xff)
604      1814-xxxx    Firmware partitions
605 */
606 static void * generate_factory_image(const struct device_info *info, const struct image_partition_entry *parts, size_t *len) {
607         *len = 0x1814;
608
609         size_t i;
610         for (i = 0; parts[i].name; i++)
611                 *len += parts[i].size;
612
613         uint8_t *image = malloc(*len);
614         if (!image)
615                 error(1, errno, "malloc");
616
617         memset(image, 0xff, *len);
618         put32(image, *len);
619
620         if (info->vendor) {
621                 size_t vendor_len = strlen(info->vendor);
622                 put32(image+0x14, vendor_len);
623                 memcpy(image+0x18, info->vendor, vendor_len);
624         }
625
626         put_partitions(image + 0x1014, info->partitions, parts);
627         put_md5(image+0x04, image+0x14, *len-0x14);
628
629         return image;
630 }
631
632 /**
633    Generates the firmware image in sysupgrade format
634
635    This makes some assumptions about the provided flash and image partition tables and
636    should be generalized when TP-LINK starts building its safeloader into hardware with
637    different flash layouts.
638 */
639 static void * generate_sysupgrade_image(const struct device_info *info, const struct image_partition_entry *image_parts, size_t *len) {
640         size_t i, j;
641         size_t flash_first_partition_index = 0;
642         size_t flash_last_partition_index = 0;
643         const struct flash_partition_entry *flash_first_partition = NULL;
644         const struct flash_partition_entry *flash_last_partition = NULL;
645         const struct image_partition_entry *image_last_partition = NULL;
646
647         /** Find first and last partitions */
648         for (i = 0; info->partitions[i].name; i++) {
649                 if (!strcmp(info->partitions[i].name, info->first_sysupgrade_partition)) {
650                         flash_first_partition = &info->partitions[i];
651                         flash_first_partition_index = i;
652                 } else if (!strcmp(info->partitions[i].name, info->last_sysupgrade_partition)) {
653                         flash_last_partition = &info->partitions[i];
654                         flash_last_partition_index = i;
655                 }
656         }
657
658         assert(flash_first_partition && flash_last_partition);
659         assert(flash_first_partition_index < flash_last_partition_index);
660
661         /** Find last partition from image to calculate needed size */
662         for (i = 0; image_parts[i].name; i++) {
663                 if (!strcmp(image_parts[i].name, info->last_sysupgrade_partition)) {
664                         image_last_partition = &image_parts[i];
665                         break;
666                 }
667         }
668
669         assert(image_last_partition);
670
671         *len = flash_last_partition->base - flash_first_partition->base + image_last_partition->size;
672
673         uint8_t *image = malloc(*len);
674         if (!image)
675                 error(1, errno, "malloc");
676
677         memset(image, 0xff, *len);
678
679         for (i = flash_first_partition_index; i <= flash_last_partition_index; i++) {
680                 for (j = 0; image_parts[j].name; j++) {
681                         if (!strcmp(info->partitions[i].name, image_parts[j].name)) {
682                                 if (image_parts[j].size > info->partitions[i].size)
683                                         error(1, 0, "%s partition too big (more than %u bytes)", info->partitions[i].name, (unsigned)info->partitions[i].size);
684                                 memcpy(image + info->partitions[i].base - flash_first_partition->base, image_parts[j].data, image_parts[j].size);
685                                 break;
686                         }
687
688                         assert(image_parts[j].name);
689                 }
690         }
691
692         return image;
693 }
694
695 /** Generates an image according to a given layout and writes it to a file */
696 static void build_image(const char *output,
697                 const char *kernel_image,
698                 const char *rootfs_image,
699                 uint32_t rev,
700                 bool add_jffs2_eof,
701                 bool sysupgrade,
702                 const struct device_info *info) {
703         struct image_partition_entry parts[6] = {};
704
705         parts[0] = make_partition_table(info->partitions);
706         parts[1] = make_soft_version(rev);
707         parts[2] = make_support_list(info);
708         parts[3] = read_file("os-image", kernel_image, false);
709         parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
710
711         size_t len;
712         void *image;
713         if (sysupgrade)
714                 image = generate_sysupgrade_image(info, parts, &len);
715         else
716                 image = generate_factory_image(info, parts, &len);
717
718         FILE *file = fopen(output, "wb");
719         if (!file)
720                 error(1, errno, "unable to open output file");
721
722         if (fwrite(image, len, 1, file) != 1)
723                 error(1, 0, "unable to write output file");
724
725         fclose(file);
726
727         free(image);
728
729         size_t i;
730         for (i = 0; parts[i].name; i++)
731                 free_image_partition(parts[i]);
732 }
733
734 /** Usage output */
735 static void usage(const char *argv0) {
736         fprintf(stderr,
737                 "Usage: %s [OPTIONS...]\n"
738                 "\n"
739                 "Options:\n"
740                 "  -B <board>      create image for the board specified with <board>\n"
741                 "  -k <file>       read kernel image from the file <file>\n"
742                 "  -r <file>       read rootfs image from the file <file>\n"
743                 "  -o <file>       write output to the file <file>\n"
744                 "  -V <rev>        sets the revision number to <rev>\n"
745                 "  -j              add jffs2 end-of-filesystem markers\n"
746                 "  -S              create sysupgrade instead of factory image\n"
747                 "  -h              show this help\n",
748                 argv0
749         );
750 };
751
752
753 static const struct device_info *find_board(const char *id)
754 {
755         struct device_info *board = NULL;
756
757         for (board = boards; board->id != NULL; board++)
758                 if (strcasecmp(id, board->id) == 0)
759                         return board;
760
761         return NULL;
762 }
763
764 int main(int argc, char *argv[]) {
765         const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
766         bool add_jffs2_eof = false, sysupgrade = false;
767         unsigned rev = 0;
768         const struct device_info *info;
769
770         while (true) {
771                 int c;
772
773                 c = getopt(argc, argv, "B:k:r:o:V:jSh");
774                 if (c == -1)
775                         break;
776
777                 switch (c) {
778                 case 'B':
779                         board = optarg;
780                         break;
781
782                 case 'k':
783                         kernel_image = optarg;
784                         break;
785
786                 case 'r':
787                         rootfs_image = optarg;
788                         break;
789
790                 case 'o':
791                         output = optarg;
792                         break;
793
794                 case 'V':
795                         sscanf(optarg, "r%u", &rev);
796                         break;
797
798                 case 'j':
799                         add_jffs2_eof = true;
800                         break;
801
802                 case 'S':
803                         sysupgrade = true;
804                         break;
805
806                 case 'h':
807                         usage(argv[0]);
808                         return 0;
809
810                 default:
811                         usage(argv[0]);
812                         return 1;
813                 }
814         }
815
816         if (!board)
817                 error(1, 0, "no board has been specified");
818         if (!kernel_image)
819                 error(1, 0, "no kernel image has been specified");
820         if (!rootfs_image)
821                 error(1, 0, "no rootfs image has been specified");
822         if (!output)
823                 error(1, 0, "no output filename has been specified");
824
825         info = find_board(board);
826
827         if (info == NULL)
828                 error(1, 0, "unsupported board %s", board);
829
830         build_image(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade, info);
831
832         return 0;
833 }