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