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