fd4c2ab70d731447b27229357192c448b5e8c1df
[oweals/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 #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 char *soft_ver;
79         const struct flash_partition_entry partitions[MAX_PARTITIONS+1];
80         const char *first_sysupgrade_partition;
81         const char *last_sysupgrade_partition;
82 };
83
84 /** The content of the soft-version structure */
85 struct __attribute__((__packed__)) soft_version {
86         uint32_t magic;
87         uint32_t zero;
88         uint8_t pad1;
89         uint8_t version_major;
90         uint8_t version_minor;
91         uint8_t version_patch;
92         uint8_t year_hi;
93         uint8_t year_lo;
94         uint8_t month;
95         uint8_t day;
96         uint32_t rev;
97         uint8_t pad2;
98 };
99
100
101 static const uint8_t jffs2_eof_mark[4] = {0xde, 0xad, 0xc0, 0xde};
102
103
104 /**
105    Salt for the MD5 hash
106
107    Fortunately, TP-LINK seems to use the same salt for most devices which use
108    the new image format.
109 */
110 static const uint8_t md5_salt[16] = {
111         0x7a, 0x2b, 0x15, 0xed,
112         0x9b, 0x98, 0x59, 0x6d,
113         0xe5, 0x04, 0xab, 0x44,
114         0xac, 0x2a, 0x9f, 0x4e,
115 };
116
117
118 /** Firmware layout table */
119 static struct device_info boards[] = {
120         /** Firmware layout for the CPE210/220 */
121         {
122                 .id     = "CPE210",
123                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
124                 .support_list =
125                         "SupportList:\r\n"
126                         "CPE210(TP-LINK|UN|N300-2):1.0\r\n"
127                         "CPE210(TP-LINK|UN|N300-2):1.1\r\n"
128                         "CPE210(TP-LINK|US|N300-2):1.1\r\n"
129                         "CPE210(TP-LINK|EU|N300-2):1.1\r\n"
130                         "CPE220(TP-LINK|UN|N300-2):1.1\r\n"
131                         "CPE220(TP-LINK|US|N300-2):1.1\r\n"
132                         "CPE220(TP-LINK|EU|N300-2):1.1\r\n",
133                 .support_trail = '\xff',
134                 .soft_ver = NULL,
135
136                 .partitions = {
137                         {"fs-uboot", 0x00000, 0x20000},
138                         {"partition-table", 0x20000, 0x02000},
139                         {"default-mac", 0x30000, 0x00020},
140                         {"product-info", 0x31100, 0x00100},
141                         {"signature", 0x32000, 0x00400},
142                         {"os-image", 0x40000, 0x170000},
143                         {"soft-version", 0x1b0000, 0x00100},
144                         {"support-list", 0x1b1000, 0x00400},
145                         {"file-system", 0x1c0000, 0x600000},
146                         {"user-config", 0x7c0000, 0x10000},
147                         {"default-config", 0x7d0000, 0x10000},
148                         {"log", 0x7e0000, 0x10000},
149                         {"radio", 0x7f0000, 0x10000},
150                         {NULL, 0, 0}
151                 },
152
153                 .first_sysupgrade_partition = "os-image",
154                 .last_sysupgrade_partition = "file-system",
155         },
156
157         /** Firmware layout for the CPE510/520 */
158         {
159                 .id     = "CPE510",
160                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
161                 .support_list =
162                         "SupportList:\r\n"
163                         "CPE510(TP-LINK|UN|N300-5):1.0\r\n"
164                         "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
165                         "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
166                         "CPE510(TP-LINK|US|N300-5):1.1\r\n"
167                         "CPE510(TP-LINK|EU|N300-5):1.1\r\n"
168                         "CPE520(TP-LINK|UN|N300-5):1.1\r\n"
169                         "CPE520(TP-LINK|US|N300-5):1.1\r\n"
170                         "CPE520(TP-LINK|EU|N300-5):1.1\r\n",
171                 .support_trail = '\xff',
172                 .soft_ver = NULL,
173
174                 .partitions = {
175                         {"fs-uboot", 0x00000, 0x20000},
176                         {"partition-table", 0x20000, 0x02000},
177                         {"default-mac", 0x30000, 0x00020},
178                         {"product-info", 0x31100, 0x00100},
179                         {"signature", 0x32000, 0x00400},
180                         {"os-image", 0x40000, 0x170000},
181                         {"soft-version", 0x1b0000, 0x00100},
182                         {"support-list", 0x1b1000, 0x00400},
183                         {"file-system", 0x1c0000, 0x600000},
184                         {"user-config", 0x7c0000, 0x10000},
185                         {"default-config", 0x7d0000, 0x10000},
186                         {"log", 0x7e0000, 0x10000},
187                         {"radio", 0x7f0000, 0x10000},
188                         {NULL, 0, 0}
189                 },
190
191                 .first_sysupgrade_partition = "os-image",
192                 .last_sysupgrade_partition = "file-system",
193         },
194
195         {
196                 .id     = "WBS210",
197                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
198                 .support_list =
199                         "SupportList:\r\n"
200                         "WBS210(TP-LINK|UN|N300-2):1.20\r\n"
201                         "WBS210(TP-LINK|US|N300-2):1.20\r\n"
202                         "WBS210(TP-LINK|EU|N300-2):1.20\r\n",
203                 .support_trail = '\xff',
204                 .soft_ver = NULL,
205
206                 .partitions = {
207                         {"fs-uboot", 0x00000, 0x20000},
208                         {"partition-table", 0x20000, 0x02000},
209                         {"default-mac", 0x30000, 0x00020},
210                         {"product-info", 0x31100, 0x00100},
211                         {"signature", 0x32000, 0x00400},
212                         {"os-image", 0x40000, 0x170000},
213                         {"soft-version", 0x1b0000, 0x00100},
214                         {"support-list", 0x1b1000, 0x00400},
215                         {"file-system", 0x1c0000, 0x600000},
216                         {"user-config", 0x7c0000, 0x10000},
217                         {"default-config", 0x7d0000, 0x10000},
218                         {"log", 0x7e0000, 0x10000},
219                         {"radio", 0x7f0000, 0x10000},
220                         {NULL, 0, 0}
221                 },
222
223                 .first_sysupgrade_partition = "os-image",
224                 .last_sysupgrade_partition = "file-system",
225         },
226
227         {
228                 .id     = "WBS510",
229                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
230                 .support_list =
231                         "SupportList:\r\n"
232                         "WBS510(TP-LINK|UN|N300-5):1.20\r\n"
233                         "WBS510(TP-LINK|US|N300-5):1.20\r\n"
234                         "WBS510(TP-LINK|EU|N300-5):1.20\r\n",
235                 .support_trail = '\xff',
236                 .soft_ver = NULL,
237
238                 .partitions = {
239                         {"fs-uboot", 0x00000, 0x20000},
240                         {"partition-table", 0x20000, 0x02000},
241                         {"default-mac", 0x30000, 0x00020},
242                         {"product-info", 0x31100, 0x00100},
243                         {"signature", 0x32000, 0x00400},
244                         {"os-image", 0x40000, 0x170000},
245                         {"soft-version", 0x1b0000, 0x00100},
246                         {"support-list", 0x1b1000, 0x00400},
247                         {"file-system", 0x1c0000, 0x600000},
248                         {"user-config", 0x7c0000, 0x10000},
249                         {"default-config", 0x7d0000, 0x10000},
250                         {"log", 0x7e0000, 0x10000},
251                         {"radio", 0x7f0000, 0x10000},
252                         {NULL, 0, 0}
253                 },
254
255                 .first_sysupgrade_partition = "os-image",
256                 .last_sysupgrade_partition = "file-system",
257         },
258
259         /** Firmware layout for the C2600 */
260         {
261                 .id = "C2600",
262                 .vendor = "",
263                 .support_list =
264                         "SupportList:\r\n"
265                         "{product_name:Archer C2600,product_ver:1.0.0,special_id:00000000}\r\n",
266                 .support_trail = '\x00',
267                 .soft_ver = NULL,
268
269                 .partitions = {
270                         {"SBL1", 0x00000, 0x20000},
271                         {"MIBIB", 0x20000, 0x20000},
272                         {"SBL2", 0x40000, 0x20000},
273                         {"SBL3", 0x60000, 0x30000},
274                         {"DDRCONFIG", 0x90000, 0x10000},
275                         {"SSD", 0xa0000, 0x10000},
276                         {"TZ", 0xb0000, 0x30000},
277                         {"RPM", 0xe0000, 0x20000},
278                         {"fs-uboot", 0x100000, 0x70000},
279                         {"uboot-env", 0x170000, 0x40000},
280                         {"radio", 0x1b0000, 0x40000},
281                         {"os-image", 0x1f0000, 0x200000},
282                         {"file-system", 0x3f0000, 0x1b00000},
283                         {"default-mac", 0x1ef0000, 0x00200},
284                         {"pin", 0x1ef0200, 0x00200},
285                         {"product-info", 0x1ef0400, 0x0fc00},
286                         {"partition-table", 0x1f00000, 0x10000},
287                         {"soft-version", 0x1f10000, 0x10000},
288                         {"support-list", 0x1f20000, 0x10000},
289                         {"profile", 0x1f30000, 0x10000},
290                         {"default-config", 0x1f40000, 0x10000},
291                         {"user-config", 0x1f50000, 0x40000},
292                         {"qos-db", 0x1f90000, 0x40000},
293                         {"usb-config", 0x1fd0000, 0x10000},
294                         {"log", 0x1fe0000, 0x20000},
295                         {NULL, 0, 0}
296                 },
297
298                 .first_sysupgrade_partition = "os-image",
299                 .last_sysupgrade_partition = "file-system"
300         },
301
302         /** Firmware layout for the C25v1 */
303         {
304                 .id = "ARCHER-C25-V1",
305                 .support_list =
306                         "SupportList:\n"
307                         "{product_name:ArcherC25,product_ver:1.0.0,special_id:00000000}\n"
308                         "{product_name:ArcherC25,product_ver:1.0.0,special_id:55530000}\n"
309                         "{product_name:ArcherC25,product_ver:1.0.0,special_id:45550000}\n",
310                 .support_trail = '\x00',
311                 .soft_ver = "soft_ver:1.0.0\n",
312
313                 /**
314                     We use a bigger os-image partition than the stock images (and thus
315                     smaller file-system), as our kernel doesn't fit in the stock firmware's
316                     1MB os-image.
317                 */
318                 .partitions = {
319                         {"factory-boot", 0x00000, 0x20000},
320                         {"fs-uboot", 0x20000, 0x10000},
321                         {"os-image", 0x30000, 0x180000},        /* Stock: base 0x30000 size 0x100000 */
322                         {"file-system", 0x1b0000, 0x620000},    /* Stock: base 0x130000 size 0x6a0000 */
323                         {"user-config", 0x7d0000, 0x04000},
324                         {"default-mac", 0x7e0000, 0x00100},
325                         {"device-id", 0x7e0100, 0x00100},
326                         {"extra-para", 0x7e0200, 0x00100},
327                         {"pin", 0x7e0300, 0x00100},
328                         {"support-list", 0x7e0400, 0x00400},
329                         {"soft-version", 0x7e0800, 0x00400},
330                         {"product-info", 0x7e0c00, 0x01400},
331                         {"partition-table", 0x7e2000, 0x01000},
332                         {"profile", 0x7e3000, 0x01000},
333                         {"default-config", 0x7e4000, 0x04000},
334                         {"merge-config", 0x7ec000, 0x02000},
335                         {"qos-db", 0x7ee000, 0x02000},
336                         {"radio", 0x7f0000, 0x10000},
337                         {NULL, 0, 0}
338                 },
339
340                 .first_sysupgrade_partition = "os-image",
341                 .last_sysupgrade_partition = "file-system",
342         },
343
344         /** Firmware layout for the C59v1 */
345         {
346                 .id     = "ARCHER-C59-V1",
347                 .vendor = "",
348                 .support_list =
349                         "SupportList:\r\n"
350                         "{product_name:Archer C59,product_ver:1.0.0,special_id:00000000}\r\n"
351                         "{product_name:Archer C59,product_ver:1.0.0,special_id:45550000}\r\n"
352                         "{product_name:Archer C59,product_ver:1.0.0,special_id:55530000}\r\n",
353                 .support_trail = '\x00',
354                 .soft_ver = "soft_ver:1.0.0\n",
355
356                 .partitions = {
357                         {"fs-uboot", 0x00000, 0x10000},
358                         {"default-mac", 0x10000, 0x00200},
359                         {"pin", 0x10200, 0x00200},
360                         {"device-id", 0x10400, 0x00100},
361                         {"product-info", 0x10500, 0x0fb00},
362                         {"os-image", 0x20000, 0x180000},
363                         {"file-system", 0x1a0000, 0xcb0000},
364                         {"partition-table", 0xe50000, 0x10000},
365                         {"soft-version", 0xe60000, 0x10000},
366                         {"support-list", 0xe70000, 0x10000},
367                         {"profile", 0xe80000, 0x10000},
368                         {"default-config", 0xe90000, 0x10000},
369                         {"user-config", 0xea0000, 0x40000},
370                         {"usb-config", 0xee0000, 0x10000},
371                         {"certificate", 0xef0000, 0x10000},
372                         {"qos-db", 0xf00000, 0x40000},
373                         {"log", 0xfe0000, 0x10000},
374                         {"radio", 0xff0000, 0x10000},
375                         {NULL, 0, 0}
376                 },
377
378                 .first_sysupgrade_partition = "os-image",
379                 .last_sysupgrade_partition = "file-system",
380         },
381
382         /** Firmware layout for the C60v1 */
383         {
384                 .id     = "ARCHER-C60-V1",
385                 .vendor = "",
386                 .support_list =
387                         "SupportList:\r\n"
388                         "{product_name:Archer C60,product_ver:1.0.0,special_id:00000000}\r\n"
389                         "{product_name:Archer C60,product_ver:1.0.0,special_id:45550000}\r\n"
390                         "{product_name:Archer C60,product_ver:1.0.0,special_id:55530000}\r\n",
391                 .support_trail = '\x00',
392                 .soft_ver = "soft_ver:1.0.0\n",
393
394                 .partitions = {
395                         {"fs-uboot", 0x00000, 0x10000},
396                         {"default-mac", 0x10000, 0x00200},
397                         {"pin", 0x10200, 0x00200},
398                         {"product-info", 0x10400, 0x00100},
399                         {"partition-table", 0x10500, 0x00800},
400                         {"soft-version", 0x11300, 0x00200},
401                         {"support-list", 0x11500, 0x00100},
402                         {"device-id", 0x11600, 0x00100},
403                         {"profile", 0x11700, 0x03900},
404                         {"default-config", 0x15000, 0x04000},
405                         {"user-config", 0x19000, 0x04000},
406                         {"os-image", 0x20000, 0x150000},
407                         {"file-system", 0x170000, 0x678000},
408                         {"certyficate", 0x7e8000, 0x08000},
409                         {"radio", 0x7f0000, 0x10000},
410                         {NULL, 0, 0}
411                 },
412
413                 .first_sysupgrade_partition = "os-image",
414                 .last_sysupgrade_partition = "file-system",
415         },
416
417         /** Firmware layout for the C5 */
418         {
419                 .id = "ARCHER-C5-V2",
420                 .vendor = "",
421                 .support_list =
422                         "SupportList:\r\n"
423                         "{product_name:ArcherC5,"
424                         "product_ver:2.0.0,"
425                         "special_id:00000000}\r\n",
426                 .support_trail = '\x00',
427                 .soft_ver = NULL,
428
429                 .partitions = {
430                         {"fs-uboot", 0x00000, 0x40000},
431                         {"os-image", 0x40000, 0x200000},
432                         {"file-system", 0x240000, 0xc00000},
433                         {"default-mac", 0xe40000, 0x00200},
434                         {"pin", 0xe40200, 0x00200},
435                         {"product-info", 0xe40400, 0x00200},
436                         {"partition-table", 0xe50000, 0x10000},
437                         {"soft-version", 0xe60000, 0x00200},
438                         {"support-list", 0xe61000, 0x0f000},
439                         {"profile", 0xe70000, 0x10000},
440                         {"default-config", 0xe80000, 0x10000},
441                         {"user-config", 0xe90000, 0x50000},
442                         {"log", 0xee0000, 0x100000},
443                         {"radio_bk", 0xfe0000, 0x10000},
444                         {"radio", 0xff0000, 0x10000},
445                         {NULL, 0, 0}
446                 },
447
448                 .first_sysupgrade_partition = "os-image",
449                 .last_sysupgrade_partition = "file-system"
450         },
451
452         /** Firmware layout for the C9 */
453         {
454                 .id = "ARCHERC9",
455                 .vendor = "",
456                 .support_list =
457                         "SupportList:\n"
458                         "{product_name:ArcherC9,"
459                         "product_ver:1.0.0,"
460                         "special_id:00000000}\n",
461                 .support_trail = '\x00',
462                 .soft_ver = NULL,
463
464                 .partitions = {
465                         {"fs-uboot", 0x00000, 0x40000},
466                         {"os-image", 0x40000, 0x200000},
467                         {"file-system", 0x240000, 0xc00000},
468                         {"default-mac", 0xe40000, 0x00200},
469                         {"pin", 0xe40200, 0x00200},
470                         {"product-info", 0xe40400, 0x00200},
471                         {"partition-table", 0xe50000, 0x10000},
472                         {"soft-version", 0xe60000, 0x00200},
473                         {"support-list", 0xe61000, 0x0f000},
474                         {"profile", 0xe70000, 0x10000},
475                         {"default-config", 0xe80000, 0x10000},
476                         {"user-config", 0xe90000, 0x50000},
477                         {"log", 0xee0000, 0x100000},
478                         {"radio_bk", 0xfe0000, 0x10000},
479                         {"radio", 0xff0000, 0x10000},
480                         {NULL, 0, 0}
481                 },
482
483                 .first_sysupgrade_partition = "os-image",
484                 .last_sysupgrade_partition = "file-system"
485         },
486
487         /** Firmware layout for the EAP120 */
488         {
489                 .id     = "EAP120",
490                 .vendor = "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
491                 .support_list =
492                         "SupportList:\r\n"
493                         "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
494                 .support_trail = '\xff',
495                 .soft_ver = NULL,
496
497                 .partitions = {
498                         {"fs-uboot", 0x00000, 0x20000},
499                         {"partition-table", 0x20000, 0x02000},
500                         {"default-mac", 0x30000, 0x00020},
501                         {"support-list", 0x31000, 0x00100},
502                         {"product-info", 0x31100, 0x00100},
503                         {"soft-version", 0x32000, 0x00100},
504                         {"os-image", 0x40000, 0x180000},
505                         {"file-system", 0x1c0000, 0x600000},
506                         {"user-config", 0x7c0000, 0x10000},
507                         {"backup-config", 0x7d0000, 0x10000},
508                         {"log", 0x7e0000, 0x10000},
509                         {"radio", 0x7f0000, 0x10000},
510                         {NULL, 0, 0}
511                 },
512
513                 .first_sysupgrade_partition = "os-image",
514                 .last_sysupgrade_partition = "file-system"
515         },
516
517         /** Firmware layout for the TL-WA850RE v2 */
518         {
519                 .id     = "TLWA850REV2",
520                 .vendor = "",
521                 .support_list =
522                         "SupportList:\n"
523                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:55530000}\n"
524                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:00000000}\n"
525                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:55534100}\n"
526                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:45550000}\n"
527                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:4B520000}\n"
528                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:42520000}\n"
529                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:4A500000}\n"
530                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:43410000}\n"
531                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:41550000}\n"
532                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:52550000}\n",
533                 .support_trail = '\x00',
534                 .soft_ver = NULL,
535
536                 /**
537                    576KB were moved from file-system to os-image
538                    in comparison to the stock image
539                 */
540                 .partitions = {
541                         {"fs-uboot", 0x00000, 0x20000},
542                         {"os-image", 0x20000, 0x150000},
543                         {"file-system", 0x170000, 0x240000},
544                         {"partition-table", 0x3b0000, 0x02000},
545                         {"default-mac", 0x3c0000, 0x00020},
546                         {"pin", 0x3c0100, 0x00020},
547                         {"product-info", 0x3c1000, 0x01000},
548                         {"soft-version", 0x3c2000, 0x00100},
549                         {"support-list", 0x3c3000, 0x01000},
550                         {"profile", 0x3c4000, 0x08000},
551                         {"user-config", 0x3d0000, 0x10000},
552                         {"default-config", 0x3e0000, 0x10000},
553                         {"radio", 0x3f0000, 0x10000},
554                         {NULL, 0, 0}
555                 },
556
557                 .first_sysupgrade_partition = "os-image",
558                 .last_sysupgrade_partition = "file-system"
559         },
560
561         /** Firmware layout for the TL-WR1043 v4 */
562         {
563                 .id     = "TLWR1043NDV4",
564                 .vendor = "",
565                 .support_list =
566                         "SupportList:\n"
567                         "{product_name:TL-WR1043ND,product_ver:4.0.0,special_id:45550000}\n",
568                 .support_trail = '\x00',
569                 .soft_ver = NULL,
570
571                 /**
572                     We use a bigger os-image partition than the stock images (and thus
573                     smaller file-system), as our kernel doesn't fit in the stock firmware's
574                     1MB os-image.
575                 */
576                 .partitions = {
577                         {"fs-uboot", 0x00000, 0x20000},
578                         {"os-image", 0x20000, 0x180000},
579                         {"file-system", 0x1a0000, 0xdb0000},
580                         {"default-mac", 0xf50000, 0x00200},
581                         {"pin", 0xf50200, 0x00200},
582                         {"product-info", 0xf50400, 0x0fc00},
583                         {"soft-version", 0xf60000, 0x0b000},
584                         {"support-list", 0xf6b000, 0x04000},
585                         {"profile", 0xf70000, 0x04000},
586                         {"default-config", 0xf74000, 0x0b000},
587                         {"user-config", 0xf80000, 0x40000},
588                         {"partition-table", 0xfc0000, 0x10000},
589                         {"log", 0xfd0000, 0x20000},
590                         {"radio", 0xff0000, 0x10000},
591                         {NULL, 0, 0}
592                 },
593
594                 .first_sysupgrade_partition = "os-image",
595                 .last_sysupgrade_partition = "file-system"
596         },
597
598         /** Firmware layout for the TL-WR942N V1 */
599         {
600                 .id     = "TLWR942NV1",
601                 .vendor = "",
602                 .support_list =
603                         "SupportList:\r\n"
604                         "{product_name:TL-WR942N,product_ver:1.0.0,special_id:00000000}\r\n"
605                         "{product_name:TL-WR942N,product_ver:1.0.0,special_id:52550000}\r\n",
606                 .support_trail = '\x00',
607                 .soft_ver = NULL,
608
609                 .partitions = {
610                         {"fs-uboot", 0x00000, 0x20000},
611                         {"os-image", 0x20000, 0x150000},
612                         {"file-system", 0x170000, 0xcd0000},
613                         {"default-mac", 0xe40000, 0x00200},
614                         {"pin", 0xe40200, 0x00200},
615                         {"product-info", 0xe40400, 0x0fc00},
616                         {"partition-table", 0xe50000, 0x10000},
617                         {"soft-version", 0xe60000, 0x10000},
618                         {"support-list", 0xe70000, 0x10000},
619                         {"profile", 0xe80000, 0x10000},
620                         {"default-config", 0xe90000, 0x10000},
621                         {"user-config", 0xea0000, 0x40000},
622                         {"qos-db", 0xee0000, 0x40000},
623                         {"certificate", 0xf20000, 0x10000},
624                         {"usb-config", 0xfb0000, 0x10000},
625                         {"log", 0xfc0000, 0x20000},
626                         {"radio-bk", 0xfe0000, 0x10000},
627                         {"radio", 0xff0000, 0x10000},
628                         {NULL, 0, 0}
629                 },
630
631                 .first_sysupgrade_partition = "os-image",
632                 .last_sysupgrade_partition = "file-system",
633         },
634
635         /** Firmware layout for the RE450 */
636         {
637                 .id = "RE450",
638                 .vendor = "",
639                 .support_list =
640                         "SupportList:\r\n"
641                         "{product_name:RE450,product_ver:1.0.0,special_id:00000000}\r\n"
642                         "{product_name:RE450,product_ver:1.0.0,special_id:55530000}\r\n"
643                         "{product_name:RE450,product_ver:1.0.0,special_id:45550000}\r\n"
644                         "{product_name:RE450,product_ver:1.0.0,special_id:4A500000}\r\n"
645                         "{product_name:RE450,product_ver:1.0.0,special_id:43410000}\r\n"
646                         "{product_name:RE450,product_ver:1.0.0,special_id:41550000}\r\n"
647                         "{product_name:RE450,product_ver:1.0.0,special_id:4B520000}\r\n"
648                         "{product_name:RE450,product_ver:1.0.0,special_id:55534100}\r\n",
649                 .support_trail = '\x00',
650                 .soft_ver = NULL,
651
652                 /**
653                    The flash partition table for RE450;
654                    it is almost the same as the one used by the stock images,
655                    576KB were moved from file-system to os-image.
656                 */
657                 .partitions = {
658                         {"fs-uboot", 0x00000, 0x20000},
659                         {"os-image", 0x20000, 0x150000},
660                         {"file-system", 0x170000, 0x4a0000},
661                         {"partition-table", 0x600000, 0x02000},
662                         {"default-mac", 0x610000, 0x00020},
663                         {"pin", 0x610100, 0x00020},
664                         {"product-info", 0x611100, 0x01000},
665                         {"soft-version", 0x620000, 0x01000},
666                         {"support-list", 0x621000, 0x01000},
667                         {"profile", 0x622000, 0x08000},
668                         {"user-config", 0x630000, 0x10000},
669                         {"default-config", 0x640000, 0x10000},
670                         {"radio", 0x7f0000, 0x10000},
671                         {NULL, 0, 0}
672                 },
673
674                 .first_sysupgrade_partition = "os-image",
675                 .last_sysupgrade_partition = "file-system"
676         },
677
678         {}
679 };
680
681 #define error(_ret, _errno, _str, ...)                          \
682         do {                                                    \
683                 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__,  \
684                         strerror(_errno));                      \
685                 if (_ret)                                       \
686                         exit(_ret);                             \
687         } while (0)
688
689
690 /** Stores a uint32 as big endian */
691 static inline void put32(uint8_t *buf, uint32_t val) {
692         buf[0] = val >> 24;
693         buf[1] = val >> 16;
694         buf[2] = val >> 8;
695         buf[3] = val;
696 }
697
698 /** Allocates a new image partition */
699 static struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
700         struct image_partition_entry entry = {name, len, malloc(len)};
701         if (!entry.data)
702                 error(1, errno, "malloc");
703
704         return entry;
705 }
706
707 /** Frees an image partition */
708 static void free_image_partition(struct image_partition_entry entry) {
709         free(entry.data);
710 }
711
712 /** Generates the partition-table partition */
713 static struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
714         struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
715
716         char *s = (char *)entry.data, *end = (char *)(s+entry.size);
717
718         *(s++) = 0x00;
719         *(s++) = 0x04;
720         *(s++) = 0x00;
721         *(s++) = 0x00;
722
723         size_t i;
724         for (i = 0; p[i].name; i++) {
725                 size_t len = end-s;
726                 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
727
728                 if (w > len-1)
729                         error(1, 0, "flash partition table overflow?");
730
731                 s += w;
732         }
733
734         s++;
735
736         memset(s, 0xff, end-s);
737
738         return entry;
739 }
740
741
742 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
743 static inline uint8_t bcd(uint8_t v) {
744         return 0x10 * (v/10) + v%10;
745 }
746
747
748 /** Generates the soft-version partition */
749 static struct image_partition_entry make_soft_version(uint32_t rev) {
750         struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
751         struct soft_version *s = (struct soft_version *)entry.data;
752
753         time_t t;
754
755         if (time(&t) == (time_t)(-1))
756                 error(1, errno, "time");
757
758         struct tm *tm = localtime(&t);
759
760         s->magic = htonl(0x0000000c);
761         s->zero = 0;
762         s->pad1 = 0xff;
763
764         s->version_major = 0;
765         s->version_minor = 0;
766         s->version_patch = 0;
767
768         s->year_hi = bcd((1900+tm->tm_year)/100);
769         s->year_lo = bcd(tm->tm_year%100);
770         s->month = bcd(tm->tm_mon+1);
771         s->day = bcd(tm->tm_mday);
772         s->rev = htonl(rev);
773
774         s->pad2 = 0xff;
775
776         return entry;
777 }
778
779 static struct image_partition_entry make_soft_version_from_string(const char *soft_ver) {
780         /** String length _including_ the terminating zero byte */
781         uint32_t ver_len = strlen(soft_ver) + 1;
782         /** Partition contains 64 bit header, the version string, and one additional null byte */
783         size_t partition_len = 2*sizeof(uint32_t) + ver_len + 1;
784         struct image_partition_entry entry = alloc_image_partition("soft-version", partition_len);
785
786         uint32_t *len = (uint32_t *)entry.data;
787         len[0] = htonl(ver_len);
788         len[1] = 0;
789         memcpy(&len[2], soft_ver, ver_len);
790
791         entry.data[partition_len - 1] = 0;
792
793         return entry;
794 }
795
796 /** Generates the support-list partition */
797 static struct image_partition_entry make_support_list(const struct device_info *info) {
798         size_t len = strlen(info->support_list);
799         struct image_partition_entry entry = alloc_image_partition("support-list", len + 9);
800
801         put32(entry.data, len);
802         memset(entry.data+4, 0, 4);
803         memcpy(entry.data+8, info->support_list, len);
804         entry.data[len+8] = info->support_trail;
805
806         return entry;
807 }
808
809 /** Creates a new image partition with an arbitrary name from a file */
810 static struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
811         struct stat statbuf;
812
813         if (stat(filename, &statbuf) < 0)
814                 error(1, errno, "unable to stat file `%s'", filename);
815
816         size_t len = statbuf.st_size;
817
818         if (add_jffs2_eof)
819                 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
820
821         struct image_partition_entry entry = alloc_image_partition(part_name, len);
822
823         FILE *file = fopen(filename, "rb");
824         if (!file)
825                 error(1, errno, "unable to open file `%s'", filename);
826
827         if (fread(entry.data, statbuf.st_size, 1, file) != 1)
828                 error(1, errno, "unable to read file `%s'", filename);
829
830         if (add_jffs2_eof) {
831                 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
832
833                 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
834                 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
835         }
836
837         fclose(file);
838
839         return entry;
840 }
841
842 /** Creates a new image partition from arbitrary data */
843 static struct image_partition_entry put_data(const char *part_name, const char *datain, size_t len) {
844
845         struct image_partition_entry entry = alloc_image_partition(part_name, len);
846
847         memcpy(entry.data, datain, len);
848
849         return entry;
850 }
851
852 /**
853    Copies a list of image partitions into an image buffer and generates the image partition table while doing so
854
855    Example image partition table:
856
857      fwup-ptn partition-table base 0x00800 size 0x00800
858      fwup-ptn os-image base 0x01000 size 0x113b45
859      fwup-ptn file-system base 0x114b45 size 0x1d0004
860      fwup-ptn support-list base 0x2e4b49 size 0x000d1
861
862    Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
863    the end of the partition table is marked with a zero byte.
864
865    The firmware image must contain at least the partition-table and support-list partitions
866    to be accepted. There aren't any alignment constraints for the image partitions.
867
868    The partition-table partition contains the actual flash layout; partitions
869    from the image partition table are mapped to the corresponding flash partitions during
870    the firmware upgrade. The support-list partition contains a list of devices supported by
871    the firmware image.
872
873    The base offsets in the firmware partition table are relative to the end
874    of the vendor information block, so the partition-table partition will
875    actually start at offset 0x1814 of the image.
876
877    I think partition-table must be the first partition in the firmware image.
878 */
879 static void put_partitions(uint8_t *buffer, const struct flash_partition_entry *flash_parts, const struct image_partition_entry *parts) {
880         size_t i, j;
881         char *image_pt = (char *)buffer, *end = image_pt + 0x800;
882
883         size_t base = 0x800;
884         for (i = 0; parts[i].name; i++) {
885                 for (j = 0; flash_parts[j].name; j++) {
886                         if (!strcmp(flash_parts[j].name, parts[i].name)) {
887                                 if (parts[i].size > flash_parts[j].size)
888                                         error(1, 0, "%s partition too big (more than %u bytes)", flash_parts[j].name, (unsigned)flash_parts[j].size);
889                                 break;
890                         }
891                 }
892
893                 assert(flash_parts[j].name);
894
895                 memcpy(buffer + base, parts[i].data, parts[i].size);
896
897                 size_t len = end-image_pt;
898                 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);
899
900                 if (w > len-1)
901                         error(1, 0, "image partition table overflow?");
902
903                 image_pt += w;
904
905                 base += parts[i].size;
906         }
907 }
908
909 /** Generates and writes the image MD5 checksum */
910 static void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
911         MD5_CTX ctx;
912
913         MD5_Init(&ctx);
914         MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
915         MD5_Update(&ctx, buffer, len);
916         MD5_Final(md5, &ctx);
917 }
918
919
920 /**
921    Generates the firmware image in factory format
922
923    Image format:
924
925      Bytes (hex)  Usage
926      -----------  -----
927      0000-0003    Image size (4 bytes, big endian)
928      0004-0013    MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
929      0014-0017    Vendor information length (without padding) (4 bytes, big endian)
930      0018-1013    Vendor information (4092 bytes, padded with 0xff; there seem to be older
931                   (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
932      1014-1813    Image partition table (2048 bytes, padded with 0xff)
933      1814-xxxx    Firmware partitions
934 */
935 static void * generate_factory_image(const struct device_info *info, const struct image_partition_entry *parts, size_t *len) {
936         *len = 0x1814;
937
938         size_t i;
939         for (i = 0; parts[i].name; i++)
940                 *len += parts[i].size;
941
942         uint8_t *image = malloc(*len);
943         if (!image)
944                 error(1, errno, "malloc");
945
946         memset(image, 0xff, *len);
947         put32(image, *len);
948
949         if (info->vendor) {
950                 size_t vendor_len = strlen(info->vendor);
951                 put32(image+0x14, vendor_len);
952                 memcpy(image+0x18, info->vendor, vendor_len);
953         }
954
955         put_partitions(image + 0x1014, info->partitions, parts);
956         put_md5(image+0x04, image+0x14, *len-0x14);
957
958         return image;
959 }
960
961 /**
962    Generates the firmware image in sysupgrade format
963
964    This makes some assumptions about the provided flash and image partition tables and
965    should be generalized when TP-LINK starts building its safeloader into hardware with
966    different flash layouts.
967 */
968 static void * generate_sysupgrade_image(const struct device_info *info, const struct image_partition_entry *image_parts, size_t *len) {
969         size_t i, j;
970         size_t flash_first_partition_index = 0;
971         size_t flash_last_partition_index = 0;
972         const struct flash_partition_entry *flash_first_partition = NULL;
973         const struct flash_partition_entry *flash_last_partition = NULL;
974         const struct image_partition_entry *image_last_partition = NULL;
975
976         /** Find first and last partitions */
977         for (i = 0; info->partitions[i].name; i++) {
978                 if (!strcmp(info->partitions[i].name, info->first_sysupgrade_partition)) {
979                         flash_first_partition = &info->partitions[i];
980                         flash_first_partition_index = i;
981                 } else if (!strcmp(info->partitions[i].name, info->last_sysupgrade_partition)) {
982                         flash_last_partition = &info->partitions[i];
983                         flash_last_partition_index = i;
984                 }
985         }
986
987         assert(flash_first_partition && flash_last_partition);
988         assert(flash_first_partition_index < flash_last_partition_index);
989
990         /** Find last partition from image to calculate needed size */
991         for (i = 0; image_parts[i].name; i++) {
992                 if (!strcmp(image_parts[i].name, info->last_sysupgrade_partition)) {
993                         image_last_partition = &image_parts[i];
994                         break;
995                 }
996         }
997
998         assert(image_last_partition);
999
1000         *len = flash_last_partition->base - flash_first_partition->base + image_last_partition->size;
1001
1002         uint8_t *image = malloc(*len);
1003         if (!image)
1004                 error(1, errno, "malloc");
1005
1006         memset(image, 0xff, *len);
1007
1008         for (i = flash_first_partition_index; i <= flash_last_partition_index; i++) {
1009                 for (j = 0; image_parts[j].name; j++) {
1010                         if (!strcmp(info->partitions[i].name, image_parts[j].name)) {
1011                                 if (image_parts[j].size > info->partitions[i].size)
1012                                         error(1, 0, "%s partition too big (more than %u bytes)", info->partitions[i].name, (unsigned)info->partitions[i].size);
1013                                 memcpy(image + info->partitions[i].base - flash_first_partition->base, image_parts[j].data, image_parts[j].size);
1014                                 break;
1015                         }
1016
1017                         assert(image_parts[j].name);
1018                 }
1019         }
1020
1021         return image;
1022 }
1023
1024 /** Generates an image according to a given layout and writes it to a file */
1025 static void build_image(const char *output,
1026                 const char *kernel_image,
1027                 const char *rootfs_image,
1028                 uint32_t rev,
1029                 bool add_jffs2_eof,
1030                 bool sysupgrade,
1031                 const struct device_info *info) {
1032
1033         struct image_partition_entry parts[7] = {};
1034
1035         parts[0] = make_partition_table(info->partitions);
1036         if (info->soft_ver)
1037                 parts[1] = make_soft_version_from_string(info->soft_ver);
1038         else
1039                 parts[1] = make_soft_version(rev);
1040
1041         parts[2] = make_support_list(info);
1042         parts[3] = read_file("os-image", kernel_image, false);
1043         parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
1044
1045         if (strcasecmp(info->id, "ARCHER-C25-V1") == 0) {
1046                 const char mdat[11] = {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00};
1047                 parts[5] = put_data("extra-para", mdat, 11);
1048         }
1049
1050         size_t len;
1051         void *image;
1052         if (sysupgrade)
1053                 image = generate_sysupgrade_image(info, parts, &len);
1054         else
1055                 image = generate_factory_image(info, parts, &len);
1056
1057         FILE *file = fopen(output, "wb");
1058         if (!file)
1059                 error(1, errno, "unable to open output file");
1060
1061         if (fwrite(image, len, 1, file) != 1)
1062                 error(1, 0, "unable to write output file");
1063
1064         fclose(file);
1065
1066         free(image);
1067
1068         size_t i;
1069         for (i = 0; parts[i].name; i++)
1070                 free_image_partition(parts[i]);
1071 }
1072
1073 /** Usage output */
1074 static void usage(const char *argv0) {
1075         fprintf(stderr,
1076                 "Usage: %s [OPTIONS...]\n"
1077                 "\n"
1078                 "Options:\n"
1079                 "  -B <board>      create image for the board specified with <board>\n"
1080                 "  -k <file>       read kernel image from the file <file>\n"
1081                 "  -r <file>       read rootfs image from the file <file>\n"
1082                 "  -o <file>       write output to the file <file>\n"
1083                 "  -V <rev>        sets the revision number to <rev>\n"
1084                 "  -j              add jffs2 end-of-filesystem markers\n"
1085                 "  -S              create sysupgrade instead of factory image\n"
1086                 "  -h              show this help\n",
1087                 argv0
1088         );
1089 };
1090
1091
1092 static const struct device_info *find_board(const char *id)
1093 {
1094         struct device_info *board = NULL;
1095
1096         for (board = boards; board->id != NULL; board++)
1097                 if (strcasecmp(id, board->id) == 0)
1098                         return board;
1099
1100         return NULL;
1101 }
1102
1103 int main(int argc, char *argv[]) {
1104         const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
1105         bool add_jffs2_eof = false, sysupgrade = false;
1106         unsigned rev = 0;
1107         const struct device_info *info;
1108
1109         while (true) {
1110                 int c;
1111
1112                 c = getopt(argc, argv, "B:k:r:o:V:jSh");
1113                 if (c == -1)
1114                         break;
1115
1116                 switch (c) {
1117                 case 'B':
1118                         board = optarg;
1119                         break;
1120
1121                 case 'k':
1122                         kernel_image = optarg;
1123                         break;
1124
1125                 case 'r':
1126                         rootfs_image = optarg;
1127                         break;
1128
1129                 case 'o':
1130                         output = optarg;
1131                         break;
1132
1133                 case 'V':
1134                         sscanf(optarg, "r%u", &rev);
1135                         break;
1136
1137                 case 'j':
1138                         add_jffs2_eof = true;
1139                         break;
1140
1141                 case 'S':
1142                         sysupgrade = true;
1143                         break;
1144
1145                 case 'h':
1146                         usage(argv[0]);
1147                         return 0;
1148
1149                 default:
1150                         usage(argv[0]);
1151                         return 1;
1152                 }
1153         }
1154
1155         if (!board)
1156                 error(1, 0, "no board has been specified");
1157         if (!kernel_image)
1158                 error(1, 0, "no kernel image has been specified");
1159         if (!rootfs_image)
1160                 error(1, 0, "no rootfs image has been specified");
1161         if (!output)
1162                 error(1, 0, "no output filename has been specified");
1163
1164         info = find_board(board);
1165
1166         if (info == NULL)
1167                 error(1, 0, "unsupported board %s", board);
1168
1169         build_image(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade, info);
1170
1171         return 0;
1172 }