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