firmware-utils: add support for TL-WR1043ND v4 to mktplinkfw and 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 #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         {}
334 };
335
336 #define error(_ret, _errno, _str, ...)                          \
337         do {                                                    \
338                 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__,  \
339                         strerror(_errno));                      \
340                 if (_ret)                                       \
341                         exit(_ret);                             \
342         } while (0)
343
344
345 /** Stores a uint32 as big endian */
346 static inline void put32(uint8_t *buf, uint32_t val) {
347         buf[0] = val >> 24;
348         buf[1] = val >> 16;
349         buf[2] = val >> 8;
350         buf[3] = val;
351 }
352
353 /** Allocates a new image partition */
354 static struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
355         struct image_partition_entry entry = {name, len, malloc(len)};
356         if (!entry.data)
357                 error(1, errno, "malloc");
358
359         return entry;
360 }
361
362 /** Frees an image partition */
363 static void free_image_partition(struct image_partition_entry entry) {
364         free(entry.data);
365 }
366
367 /** Generates the partition-table partition */
368 static struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
369         struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
370
371         char *s = (char *)entry.data, *end = (char *)(s+entry.size);
372
373         *(s++) = 0x00;
374         *(s++) = 0x04;
375         *(s++) = 0x00;
376         *(s++) = 0x00;
377
378         size_t i;
379         for (i = 0; p[i].name; i++) {
380                 size_t len = end-s;
381                 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
382
383                 if (w > len-1)
384                         error(1, 0, "flash partition table overflow?");
385
386                 s += w;
387         }
388
389         s++;
390
391         memset(s, 0xff, end-s);
392
393         return entry;
394 }
395
396
397 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
398 static inline uint8_t bcd(uint8_t v) {
399         return 0x10 * (v/10) + v%10;
400 }
401
402
403 /** Generates the soft-version partition */
404 static struct image_partition_entry make_soft_version(uint32_t rev) {
405         struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
406         struct soft_version *s = (struct soft_version *)entry.data;
407
408         time_t t;
409
410         if (time(&t) == (time_t)(-1))
411                 error(1, errno, "time");
412
413         struct tm *tm = localtime(&t);
414
415         s->magic = htonl(0x0000000c);
416         s->zero = 0;
417         s->pad1 = 0xff;
418
419         s->version_major = 0;
420         s->version_minor = 0;
421         s->version_patch = 0;
422
423         s->year_hi = bcd((1900+tm->tm_year)/100);
424         s->year_lo = bcd(tm->tm_year%100);
425         s->month = bcd(tm->tm_mon+1);
426         s->day = bcd(tm->tm_mday);
427         s->rev = htonl(rev);
428
429         s->pad2 = 0xff;
430
431         return entry;
432 }
433
434 /** Generates the support-list partition */
435 static struct image_partition_entry make_support_list(const struct device_info *info) {
436         size_t len = strlen(info->support_list);
437         struct image_partition_entry entry = alloc_image_partition("support-list", len + 9);
438
439         put32(entry.data, len);
440         memset(entry.data+4, 0, 4);
441         memcpy(entry.data+8, info->support_list, len);
442         entry.data[len+8] = info->support_trail;
443
444         return entry;
445 }
446
447 /** Creates a new image partition with an arbitrary name from a file */
448 static struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
449         struct stat statbuf;
450
451         if (stat(filename, &statbuf) < 0)
452                 error(1, errno, "unable to stat file `%s'", filename);
453
454         size_t len = statbuf.st_size;
455
456         if (add_jffs2_eof)
457                 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
458
459         struct image_partition_entry entry = alloc_image_partition(part_name, len);
460
461         FILE *file = fopen(filename, "rb");
462         if (!file)
463                 error(1, errno, "unable to open file `%s'", filename);
464
465         if (fread(entry.data, statbuf.st_size, 1, file) != 1)
466                 error(1, errno, "unable to read file `%s'", filename);
467
468         if (add_jffs2_eof) {
469                 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
470
471                 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
472                 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
473         }
474
475         fclose(file);
476
477         return entry;
478 }
479
480
481 /**
482    Copies a list of image partitions into an image buffer and generates the image partition table while doing so
483
484    Example image partition table:
485
486      fwup-ptn partition-table base 0x00800 size 0x00800
487      fwup-ptn os-image base 0x01000 size 0x113b45
488      fwup-ptn file-system base 0x114b45 size 0x1d0004
489      fwup-ptn support-list base 0x2e4b49 size 0x000d1
490
491    Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
492    the end of the partition table is marked with a zero byte.
493
494    The firmware image must contain at least the partition-table and support-list partitions
495    to be accepted. There aren't any alignment constraints for the image partitions.
496
497    The partition-table partition contains the actual flash layout; partitions
498    from the image partition table are mapped to the corresponding flash partitions during
499    the firmware upgrade. The support-list partition contains a list of devices supported by
500    the firmware image.
501
502    The base offsets in the firmware partition table are relative to the end
503    of the vendor information block, so the partition-table partition will
504    actually start at offset 0x1814 of the image.
505
506    I think partition-table must be the first partition in the firmware image.
507 */
508 static void put_partitions(uint8_t *buffer, const struct flash_partition_entry *flash_parts, const struct image_partition_entry *parts) {
509         size_t i, j;
510         char *image_pt = (char *)buffer, *end = image_pt + 0x800;
511
512         size_t base = 0x800;
513         for (i = 0; parts[i].name; i++) {
514                 for (j = 0; flash_parts[j].name; j++) {
515                         if (!strcmp(flash_parts[j].name, parts[i].name)) {
516                                 if (parts[i].size > flash_parts[j].size)
517                                         error(1, 0, "%s partition too big (more than %u bytes)", flash_parts[j].name, (unsigned)flash_parts[j].size);
518                                 break;
519                         }
520                 }
521
522                 assert(flash_parts[j].name);
523
524                 memcpy(buffer + base, parts[i].data, parts[i].size);
525
526                 size_t len = end-image_pt;
527                 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);
528
529                 if (w > len-1)
530                         error(1, 0, "image partition table overflow?");
531
532                 image_pt += w;
533
534                 base += parts[i].size;
535         }
536 }
537
538 /** Generates and writes the image MD5 checksum */
539 static void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
540         MD5_CTX ctx;
541
542         MD5_Init(&ctx);
543         MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
544         MD5_Update(&ctx, buffer, len);
545         MD5_Final(md5, &ctx);
546 }
547
548
549 /**
550    Generates the firmware image in factory format
551
552    Image format:
553
554      Bytes (hex)  Usage
555      -----------  -----
556      0000-0003    Image size (4 bytes, big endian)
557      0004-0013    MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
558      0014-0017    Vendor information length (without padding) (4 bytes, big endian)
559      0018-1013    Vendor information (4092 bytes, padded with 0xff; there seem to be older
560                   (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
561      1014-1813    Image partition table (2048 bytes, padded with 0xff)
562      1814-xxxx    Firmware partitions
563 */
564 static void * generate_factory_image(const struct device_info *info, const struct image_partition_entry *parts, size_t *len) {
565         *len = 0x1814;
566
567         size_t i;
568         for (i = 0; parts[i].name; i++)
569                 *len += parts[i].size;
570
571         uint8_t *image = malloc(*len);
572         if (!image)
573                 error(1, errno, "malloc");
574
575         memset(image, 0xff, *len);
576         put32(image, *len);
577
578         if (info->vendor) {
579                 size_t vendor_len = strlen(info->vendor);
580                 put32(image+0x14, vendor_len);
581                 memcpy(image+0x18, info->vendor, vendor_len);
582         }
583
584         put_partitions(image + 0x1014, info->partitions, parts);
585         put_md5(image+0x04, image+0x14, *len-0x14);
586
587         return image;
588 }
589
590 /**
591    Generates the firmware image in sysupgrade format
592
593    This makes some assumptions about the provided flash and image partition tables and
594    should be generalized when TP-LINK starts building its safeloader into hardware with
595    different flash layouts.
596 */
597 static void * generate_sysupgrade_image(const struct device_info *info, const struct image_partition_entry *image_parts, size_t *len) {
598         size_t i, j;
599         size_t flash_first_partition_index = 0;
600         size_t flash_last_partition_index = 0;
601         const struct flash_partition_entry *flash_first_partition = NULL;
602         const struct flash_partition_entry *flash_last_partition = NULL;
603         const struct image_partition_entry *image_last_partition = NULL;
604
605         /** Find first and last partitions */
606         for (i = 0; info->partitions[i].name; i++) {
607                 if (!strcmp(info->partitions[i].name, info->first_sysupgrade_partition)) {
608                         flash_first_partition = &info->partitions[i];
609                         flash_first_partition_index = i;
610                 } else if (!strcmp(info->partitions[i].name, info->last_sysupgrade_partition)) {
611                         flash_last_partition = &info->partitions[i];
612                         flash_last_partition_index = i;
613                 }
614         }
615
616         assert(flash_first_partition && flash_last_partition);
617         assert(flash_first_partition_index < flash_last_partition_index);
618
619         /** Find last partition from image to calculate needed size */
620         for (i = 0; image_parts[i].name; i++) {
621                 if (!strcmp(image_parts[i].name, info->last_sysupgrade_partition)) {
622                         image_last_partition = &image_parts[i];
623                         break;
624                 }
625         }
626
627         assert(image_last_partition);
628
629         *len = flash_last_partition->base - flash_first_partition->base + image_last_partition->size;
630
631         uint8_t *image = malloc(*len);
632         if (!image)
633                 error(1, errno, "malloc");
634
635         memset(image, 0xff, *len);
636
637         for (i = flash_first_partition_index; i <= flash_last_partition_index; i++) {
638                 for (j = 0; image_parts[j].name; j++) {
639                         if (!strcmp(info->partitions[i].name, image_parts[j].name)) {
640                                 if (image_parts[j].size > info->partitions[i].size)
641                                         error(1, 0, "%s partition too big (more than %u bytes)", info->partitions[i].name, (unsigned)info->partitions[i].size);
642                                 memcpy(image + info->partitions[i].base - flash_first_partition->base, image_parts[j].data, image_parts[j].size);
643                                 break;
644                         }
645
646                         assert(image_parts[j].name);
647                 }
648         }
649
650         return image;
651 }
652
653 /** Generates an image according to a given layout and writes it to a file */
654 static void build_image(const char *output,
655                 const char *kernel_image,
656                 const char *rootfs_image,
657                 uint32_t rev,
658                 bool add_jffs2_eof,
659                 bool sysupgrade,
660                 const struct device_info *info) {
661         struct image_partition_entry parts[6] = {};
662
663         parts[0] = make_partition_table(info->partitions);
664         parts[1] = make_soft_version(rev);
665         parts[2] = make_support_list(info);
666         parts[3] = read_file("os-image", kernel_image, false);
667         parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
668
669         size_t len;
670         void *image;
671         if (sysupgrade)
672                 image = generate_sysupgrade_image(info, parts, &len);
673         else
674                 image = generate_factory_image(info, parts, &len);
675
676         FILE *file = fopen(output, "wb");
677         if (!file)
678                 error(1, errno, "unable to open output file");
679
680         if (fwrite(image, len, 1, file) != 1)
681                 error(1, 0, "unable to write output file");
682
683         fclose(file);
684
685         free(image);
686
687         size_t i;
688         for (i = 0; parts[i].name; i++)
689                 free_image_partition(parts[i]);
690 }
691
692 /** Usage output */
693 static void usage(const char *argv0) {
694         fprintf(stderr,
695                 "Usage: %s [OPTIONS...]\n"
696                 "\n"
697                 "Options:\n"
698                 "  -B <board>      create image for the board specified with <board>\n"
699                 "  -k <file>       read kernel image from the file <file>\n"
700                 "  -r <file>       read rootfs image from the file <file>\n"
701                 "  -o <file>       write output to the file <file>\n"
702                 "  -V <rev>        sets the revision number to <rev>\n"
703                 "  -j              add jffs2 end-of-filesystem markers\n"
704                 "  -S              create sysupgrade instead of factory image\n"
705                 "  -h              show this help\n",
706                 argv0
707         );
708 };
709
710
711 static const struct device_info *find_board(const char *id)
712 {
713         struct device_info *board = NULL;
714
715         for (board = boards; board->id != NULL; board++)
716                 if (strcasecmp(id, board->id) == 0)
717                         return board;
718
719         return NULL;
720 }
721
722 int main(int argc, char *argv[]) {
723         const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
724         bool add_jffs2_eof = false, sysupgrade = false;
725         unsigned rev = 0;
726         const struct device_info *info;
727
728         while (true) {
729                 int c;
730
731                 c = getopt(argc, argv, "B:k:r:o:V:jSh");
732                 if (c == -1)
733                         break;
734
735                 switch (c) {
736                 case 'B':
737                         board = optarg;
738                         break;
739
740                 case 'k':
741                         kernel_image = optarg;
742                         break;
743
744                 case 'r':
745                         rootfs_image = optarg;
746                         break;
747
748                 case 'o':
749                         output = optarg;
750                         break;
751
752                 case 'V':
753                         sscanf(optarg, "r%u", &rev);
754                         break;
755
756                 case 'j':
757                         add_jffs2_eof = true;
758                         break;
759
760                 case 'S':
761                         sysupgrade = true;
762                         break;
763
764                 case 'h':
765                         usage(argv[0]);
766                         return 0;
767
768                 default:
769                         usage(argv[0]);
770                         return 1;
771                 }
772         }
773
774         if (!board)
775                 error(1, 0, "no board has been specified");
776         if (!kernel_image)
777                 error(1, 0, "no kernel image has been specified");
778         if (!rootfs_image)
779                 error(1, 0, "no rootfs image has been specified");
780         if (!output)
781                 error(1, 0, "no output filename has been specified");
782
783         info = find_board(board);
784
785         if (info == NULL)
786                 error(1, 0, "unsupported board %s", board);
787
788         build_image(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade, info);
789
790         return 0;
791 }