fw-utils/tplink-safeloader.c: Add support for Archer C2600
[openwrt/openwrt.git] / tools / firmware-utils / src / tplink-safeloader.c
1 /*
2 Copyright (c) 2014, Matthias Schiffer <mschiffer@universe-factory.net>
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8 1. Redistributions of source code must retain the above copyright notice,
9 this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26
27 /*
28 tplink-safeloader
29
30 Image generation tool for the TP-LINK SafeLoader as seen on
31 TP-LINK Pharos devices (CPE210/220/510/520)
32 */
33
34
35 #include <assert.h>
36 #include <errno.h>
37 #include <stdbool.h>
38 #include <stdio.h>
39 #include <stdint.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <time.h>
43 #include <unistd.h>
44
45 #include <arpa/inet.h>
46
47 #include <sys/types.h>
48 #include <sys/stat.h>
49
50 #include "md5.h"
51
52
53 #define ALIGN(x,a) ({ typeof(a) __a = (a); (((x) + __a - 1) & ~(__a - 1)); })
54
55
56 /** An image partition table entry */
57 struct image_partition_entry {
58 const char *name;
59 size_t size;
60 uint8_t *data;
61 };
62
63 /** A flash partition table entry */
64 struct flash_partition_entry {
65 const char *name;
66 uint32_t base;
67 uint32_t size;
68 };
69
70
71 /** The content of the soft-version structure */
72 struct __attribute__((__packed__)) soft_version {
73 uint32_t magic;
74 uint32_t zero;
75 uint8_t pad1;
76 uint8_t version_major;
77 uint8_t version_minor;
78 uint8_t version_patch;
79 uint8_t year_hi;
80 uint8_t year_lo;
81 uint8_t month;
82 uint8_t day;
83 uint32_t rev;
84 uint8_t pad2;
85 };
86
87
88 static const uint8_t jffs2_eof_mark[4] = {0xde, 0xad, 0xc0, 0xde};
89
90
91 /**
92 Salt for the MD5 hash
93
94 Fortunately, TP-LINK seems to use the same salt for most devices which use
95 the new image format.
96 */
97 static const uint8_t md5_salt[16] = {
98 0x7a, 0x2b, 0x15, 0xed,
99 0x9b, 0x98, 0x59, 0x6d,
100 0xe5, 0x04, 0xab, 0x44,
101 0xac, 0x2a, 0x9f, 0x4e,
102 };
103
104
105 /** Vendor information for CPE210/220/510/520 */
106 static const char cpe510_vendor[] = "CPE510(TP-LINK|UN|N300-5):1.0\r\n";
107
108 /** Vendor information for C2600 */
109 static const char c2600_vendor[] = "";
110
111 /**
112 The flash partition table for CPE210/220/510/520;
113 it is the same as the one used by the stock images.
114 */
115 static const struct flash_partition_entry cpe510_partitions[] = {
116 {"fs-uboot", 0x00000, 0x20000},
117 {"partition-table", 0x20000, 0x02000},
118 {"default-mac", 0x30000, 0x00020},
119 {"product-info", 0x31100, 0x00100},
120 {"signature", 0x32000, 0x00400},
121 {"os-image", 0x40000, 0x170000},
122 {"soft-version", 0x1b0000, 0x00100},
123 {"support-list", 0x1b1000, 0x00400},
124 {"file-system", 0x1c0000, 0x600000},
125 {"user-config", 0x7c0000, 0x10000},
126 {"default-config", 0x7d0000, 0x10000},
127 {"log", 0x7e0000, 0x10000},
128 {"radio", 0x7f0000, 0x10000},
129 {NULL, 0, 0}
130 };
131
132 /**
133 The flash partition table for C2600;
134 it is the same as the one used by the stock images.
135 */
136 static const struct flash_partition_entry c2600_partitions[] = {
137 {"SBL1", 0x00000, 0x20000},
138 {"MIBIB", 0x20000, 0x20000},
139 {"SBL2", 0x40000, 0x20000},
140 {"SBL3", 0x60000, 0x30000},
141 {"DDRCONFIG", 0x90000, 0x10000},
142 {"SSD", 0xa0000, 0x10000},
143 {"TZ", 0xb0000, 0x30000},
144 {"RPM", 0xe0000, 0x20000},
145 {"fs-uboot", 0x100000, 0x70000},
146 {"uboot-env", 0x170000, 0x40000},
147 {"radio", 0x1b0000, 0x40000},
148 {"os-image", 0x1f0000, 0x200000},
149 {"file-system", 0x3f0000, 0x1b00000},
150 {"default-mac", 0x1ef0000, 0x00200},
151 {"pin", 0x1ef0200, 0x00200},
152 {"product-info", 0x1ef0400, 0x0fc00},
153 {"partition-table", 0x1f00000, 0x10000},
154 {"soft-version", 0x1f10000, 0x10000},
155 {"support-list", 0x1f20000, 0x10000},
156 {"profile", 0x1f30000, 0x10000},
157 {"default-config", 0x1f40000, 0x10000},
158 {"user-config", 0x1f50000, 0x40000},
159 {"qos-db", 0x1f90000, 0x40000},
160 {"usb-config", 0x1fd0000, 0x10000},
161 {"log", 0x1fe0000, 0x20000},
162 {NULL, 0, 0}
163 };
164
165 /**
166 The support list for CPE210/220/510/520
167 */
168 static const char cpe510_support_list[] =
169 "SupportList:\r\n"
170 "CPE510(TP-LINK|UN|N300-5):1.0\r\n"
171 "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
172 "CPE520(TP-LINK|UN|N300-5):1.0\r\n"
173 "CPE520(TP-LINK|UN|N300-5):1.1\r\n"
174 "CPE210(TP-LINK|UN|N300-2):1.0\r\n"
175 "CPE210(TP-LINK|UN|N300-2):1.1\r\n"
176 "CPE220(TP-LINK|UN|N300-2):1.0\r\n"
177 "CPE220(TP-LINK|UN|N300-2):1.1\r\n";
178
179 /**
180 The support list for C2600
181 */
182 static const char c2600_support_list[] =
183 "SupportList:\r\n"
184 "{product_name:Archer C2600,product_ver:1.0.0,special_id:00000000}\r\n";
185
186 #define error(_ret, _errno, _str, ...) \
187 do { \
188 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__, \
189 strerror(_errno)); \
190 if (_ret) \
191 exit(_ret); \
192 } while (0)
193
194
195 /** Stores a uint32 as big endian */
196 static inline void put32(uint8_t *buf, uint32_t val) {
197 buf[0] = val >> 24;
198 buf[1] = val >> 16;
199 buf[2] = val >> 8;
200 buf[3] = val;
201 }
202
203 /** Allocates a new image partition */
204 static struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
205 struct image_partition_entry entry = {name, len, malloc(len)};
206 if (!entry.data)
207 error(1, errno, "malloc");
208
209 return entry;
210 }
211
212 /** Frees an image partition */
213 static void free_image_partition(struct image_partition_entry entry) {
214 free(entry.data);
215 }
216
217 /** Generates the partition-table partition */
218 static struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
219 struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
220
221 char *s = (char *)entry.data, *end = (char *)(s+entry.size);
222
223 *(s++) = 0x00;
224 *(s++) = 0x04;
225 *(s++) = 0x00;
226 *(s++) = 0x00;
227
228 size_t i;
229 for (i = 0; p[i].name; i++) {
230 size_t len = end-s;
231 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
232
233 if (w > len-1)
234 error(1, 0, "flash partition table overflow?");
235
236 s += w;
237 }
238
239 s++;
240
241 memset(s, 0xff, end-s);
242
243 return entry;
244 }
245
246
247 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
248 static inline uint8_t bcd(uint8_t v) {
249 return 0x10 * (v/10) + v%10;
250 }
251
252
253 /** Generates the soft-version partition */
254 static struct image_partition_entry make_soft_version(uint32_t rev) {
255 struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
256 struct soft_version *s = (struct soft_version *)entry.data;
257
258 time_t t;
259
260 if (time(&t) == (time_t)(-1))
261 error(1, errno, "time");
262
263 struct tm *tm = localtime(&t);
264
265 s->magic = htonl(0x0000000c);
266 s->zero = 0;
267 s->pad1 = 0xff;
268
269 s->version_major = 0;
270 s->version_minor = 0;
271 s->version_patch = 0;
272
273 s->year_hi = bcd((1900+tm->tm_year)/100);
274 s->year_lo = bcd(tm->tm_year%100);
275 s->month = bcd(tm->tm_mon+1);
276 s->day = bcd(tm->tm_mday);
277 s->rev = htonl(rev);
278
279 s->pad2 = 0xff;
280
281 return entry;
282 }
283
284 /** Generates the support-list partition */
285 static struct image_partition_entry make_support_list(const char *support_list, bool trailzero) {
286 size_t len = strlen(support_list);
287 struct image_partition_entry entry = alloc_image_partition("support-list", len + 9);
288
289 put32(entry.data, len);
290 memset(entry.data+4, 0, 4);
291 memcpy(entry.data+8, support_list, len);
292 entry.data[len+8] = trailzero ? '\x00' : '\xff';
293
294 return entry;
295 }
296
297 /** Creates a new image partition with an arbitrary name from a file */
298 static struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
299 struct stat statbuf;
300
301 if (stat(filename, &statbuf) < 0)
302 error(1, errno, "unable to stat file `%s'", filename);
303
304 size_t len = statbuf.st_size;
305
306 if (add_jffs2_eof)
307 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
308
309 struct image_partition_entry entry = alloc_image_partition(part_name, len);
310
311 FILE *file = fopen(filename, "rb");
312 if (!file)
313 error(1, errno, "unable to open file `%s'", filename);
314
315 if (fread(entry.data, statbuf.st_size, 1, file) != 1)
316 error(1, errno, "unable to read file `%s'", filename);
317
318 if (add_jffs2_eof) {
319 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
320
321 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
322 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
323 }
324
325 fclose(file);
326
327 return entry;
328 }
329
330
331 /**
332 Copies a list of image partitions into an image buffer and generates the image partition table while doing so
333
334 Example image partition table:
335
336 fwup-ptn partition-table base 0x00800 size 0x00800
337 fwup-ptn os-image base 0x01000 size 0x113b45
338 fwup-ptn file-system base 0x114b45 size 0x1d0004
339 fwup-ptn support-list base 0x2e4b49 size 0x000d1
340
341 Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
342 the end of the partition table is marked with a zero byte.
343
344 The firmware image must contain at least the partition-table and support-list partitions
345 to be accepted. There aren't any alignment constraints for the image partitions.
346
347 The partition-table partition contains the actual flash layout; partitions
348 from the image partition table are mapped to the corresponding flash partitions during
349 the firmware upgrade. The support-list partition contains a list of devices supported by
350 the firmware image.
351
352 The base offsets in the firmware partition table are relative to the end
353 of the vendor information block, so the partition-table partition will
354 actually start at offset 0x1814 of the image.
355
356 I think partition-table must be the first partition in the firmware image.
357 */
358 static void put_partitions(uint8_t *buffer, const struct image_partition_entry *parts) {
359 size_t i;
360 char *image_pt = (char *)buffer, *end = image_pt + 0x800;
361
362 size_t base = 0x800;
363 for (i = 0; parts[i].name; i++) {
364 memcpy(buffer + base, parts[i].data, parts[i].size);
365
366 size_t len = end-image_pt;
367 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);
368
369 if (w > len-1)
370 error(1, 0, "image partition table overflow?");
371
372 image_pt += w;
373
374 base += parts[i].size;
375 }
376
377 image_pt++;
378
379 memset(image_pt, 0xff, end-image_pt);
380 }
381
382 /** Generates and writes the image MD5 checksum */
383 static void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
384 MD5_CTX ctx;
385
386 MD5_Init(&ctx);
387 MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
388 MD5_Update(&ctx, buffer, len);
389 MD5_Final(md5, &ctx);
390 }
391
392
393 /**
394 Generates the firmware image in factory format
395
396 Image format:
397
398 Bytes (hex) Usage
399 ----------- -----
400 0000-0003 Image size (4 bytes, big endian)
401 0004-0013 MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
402 0014-0017 Vendor information length (without padding) (4 bytes, big endian)
403 0018-1013 Vendor information (4092 bytes, padded with 0xff; there seem to be older
404 (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
405 1014-1813 Image partition table (2048 bytes, padded with 0xff)
406 1814-xxxx Firmware partitions
407 */
408 static void * generate_factory_image(const char *vendor, const struct image_partition_entry *parts, size_t *len) {
409 *len = 0x1814;
410
411 size_t i;
412 for (i = 0; parts[i].name; i++)
413 *len += parts[i].size;
414
415 uint8_t *image = malloc(*len);
416 if (!image)
417 error(1, errno, "malloc");
418
419 put32(image, *len);
420
421 size_t vendor_len = strlen(vendor);
422 put32(image+0x14, vendor_len);
423 memcpy(image+0x18, vendor, vendor_len);
424 memset(image+0x18+vendor_len, 0xff, 4092-vendor_len);
425
426 put_partitions(image + 0x1014, parts);
427 put_md5(image+0x04, image+0x14, *len-0x14);
428
429 return image;
430 }
431
432 /**
433 Generates the firmware image in sysupgrade format
434
435 This makes some assumptions about the provided flash and image partition tables and
436 should be generalized when TP-LINK starts building its safeloader into hardware with
437 different flash layouts.
438 */
439 static void * generate_sysupgrade_image(const struct flash_partition_entry *flash_parts, const struct image_partition_entry *image_parts, size_t *len) {
440 const struct flash_partition_entry *flash_os_image = &flash_parts[5];
441 const struct flash_partition_entry *flash_soft_version = &flash_parts[6];
442 const struct flash_partition_entry *flash_support_list = &flash_parts[7];
443 const struct flash_partition_entry *flash_file_system = &flash_parts[8];
444
445 const struct image_partition_entry *image_os_image = &image_parts[3];
446 const struct image_partition_entry *image_soft_version = &image_parts[1];
447 const struct image_partition_entry *image_support_list = &image_parts[2];
448 const struct image_partition_entry *image_file_system = &image_parts[4];
449
450 assert(strcmp(flash_os_image->name, "os-image") == 0);
451 assert(strcmp(flash_soft_version->name, "soft-version") == 0);
452 assert(strcmp(flash_support_list->name, "support-list") == 0);
453 assert(strcmp(flash_file_system->name, "file-system") == 0);
454
455 assert(strcmp(image_os_image->name, "os-image") == 0);
456 assert(strcmp(image_soft_version->name, "soft-version") == 0);
457 assert(strcmp(image_support_list->name, "support-list") == 0);
458 assert(strcmp(image_file_system->name, "file-system") == 0);
459
460 if (image_os_image->size > flash_os_image->size)
461 error(1, 0, "kernel image too big (more than %u bytes)", (unsigned)flash_os_image->size);
462 if (image_file_system->size > flash_file_system->size)
463 error(1, 0, "rootfs image too big (more than %u bytes)", (unsigned)flash_file_system->size);
464
465 *len = flash_file_system->base - flash_os_image->base + image_file_system->size;
466
467 uint8_t *image = malloc(*len);
468 if (!image)
469 error(1, errno, "malloc");
470
471 memset(image, 0xff, *len);
472
473 memcpy(image, image_os_image->data, image_os_image->size);
474 memcpy(image + flash_soft_version->base - flash_os_image->base, image_soft_version->data, image_soft_version->size);
475 memcpy(image + flash_support_list->base - flash_os_image->base, image_support_list->data, image_support_list->size);
476 memcpy(image + flash_file_system->base - flash_os_image->base, image_file_system->data, image_file_system->size);
477
478 return image;
479 }
480
481 static void * generate_sysupgrade_image_c2600(const struct flash_partition_entry *flash_parts, const struct image_partition_entry *image_parts, size_t *len) {
482 const struct flash_partition_entry *flash_os_image = &flash_parts[11];
483 const struct flash_partition_entry *flash_file_system = &flash_parts[12];
484
485 const struct image_partition_entry *image_os_image = &image_parts[3];
486 const struct image_partition_entry *image_file_system = &image_parts[4];
487
488 assert(strcmp(flash_os_image->name, "os-image") == 0);
489 assert(strcmp(flash_file_system->name, "file-system") == 0);
490
491 assert(strcmp(image_os_image->name, "os-image") == 0);
492 assert(strcmp(image_file_system->name, "file-system") == 0);
493
494 if (image_os_image->size > flash_os_image->size)
495 error(1, 0, "kernel image too big (more than %u bytes)", (unsigned)flash_os_image->size);
496 if (image_file_system->size > flash_file_system->size)
497 error(1, 0, "rootfs image too big (more than %u bytes)", (unsigned)flash_file_system->size);
498
499 *len = flash_file_system->base - flash_os_image->base + image_file_system->size;
500
501 uint8_t *image = malloc(*len);
502 if (!image)
503 error(1, errno, "malloc");
504
505 memset(image, 0xff, *len);
506
507 memcpy(image, image_os_image->data, image_os_image->size);
508 memcpy(image + flash_file_system->base - flash_os_image->base, image_file_system->data, image_file_system->size);
509
510 return image;
511 }
512
513 /** Generates an image for CPE210/220/510/520 and writes it to a file */
514 static void do_cpe510(const char *output, const char *kernel_image, const char *rootfs_image, uint32_t rev, bool add_jffs2_eof, bool sysupgrade) {
515 struct image_partition_entry parts[6] = {};
516
517 parts[0] = make_partition_table(cpe510_partitions);
518 parts[1] = make_soft_version(rev);
519 parts[2] = make_support_list(cpe510_support_list,false);
520 parts[3] = read_file("os-image", kernel_image, false);
521 parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
522
523 size_t len;
524 void *image;
525 if (sysupgrade)
526 image = generate_sysupgrade_image(cpe510_partitions, parts, &len);
527 else
528 image = generate_factory_image(cpe510_vendor, parts, &len);
529
530 FILE *file = fopen(output, "wb");
531 if (!file)
532 error(1, errno, "unable to open output file");
533
534 if (fwrite(image, len, 1, file) != 1)
535 error(1, 0, "unable to write output file");
536
537 fclose(file);
538
539 free(image);
540
541 size_t i;
542 for (i = 0; parts[i].name; i++)
543 free_image_partition(parts[i]);
544 }
545
546 /** Generates an image for C2600 and writes it to a file */
547 static void do_c2600(const char *output, const char *kernel_image, const char *rootfs_image, uint32_t rev, bool add_jffs2_eof, bool sysupgrade) {
548 struct image_partition_entry parts[6] = {};
549
550 parts[0] = make_partition_table(c2600_partitions);
551 parts[1] = make_soft_version(rev);
552 parts[2] = make_support_list(c2600_support_list,true);
553 parts[3] = read_file("os-image", kernel_image, false);
554 parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
555
556 size_t len;
557 void *image;
558 if (sysupgrade)
559 image = generate_sysupgrade_image_c2600(c2600_partitions, parts, &len);
560 else
561 image = generate_factory_image(c2600_vendor, parts, &len);
562
563 FILE *file = fopen(output, "wb");
564 if (!file)
565 error(1, errno, "unable to open output file");
566
567 if (fwrite(image, len, 1, file) != 1)
568 error(1, 0, "unable to write output file");
569
570 fclose(file);
571
572 free(image);
573
574 size_t i;
575 for (i = 0; parts[i].name; i++)
576 free_image_partition(parts[i]);
577 }
578
579
580 /** Usage output */
581 static void usage(const char *argv0) {
582 fprintf(stderr,
583 "Usage: %s [OPTIONS...]\n"
584 "\n"
585 "Options:\n"
586 " -B <board> create image for the board specified with <board>\n"
587 " -k <file> read kernel image from the file <file>\n"
588 " -r <file> read rootfs image from the file <file>\n"
589 " -o <file> write output to the file <file>\n"
590 " -V <rev> sets the revision number to <rev>\n"
591 " -j add jffs2 end-of-filesystem markers\n"
592 " -S create sysupgrade instead of factory image\n"
593 " -h show this help\n",
594 argv0
595 );
596 };
597
598
599 int main(int argc, char *argv[]) {
600 const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
601 bool add_jffs2_eof = false, sysupgrade = false;
602 unsigned rev = 0;
603
604 while (true) {
605 int c;
606
607 c = getopt(argc, argv, "B:k:r:o:V:jSh");
608 if (c == -1)
609 break;
610
611 switch (c) {
612 case 'B':
613 board = optarg;
614 break;
615
616 case 'k':
617 kernel_image = optarg;
618 break;
619
620 case 'r':
621 rootfs_image = optarg;
622 break;
623
624 case 'o':
625 output = optarg;
626 break;
627
628 case 'V':
629 sscanf(optarg, "r%u", &rev);
630 break;
631
632 case 'j':
633 add_jffs2_eof = true;
634 break;
635
636 case 'S':
637 sysupgrade = true;
638 break;
639
640 case 'h':
641 usage(argv[0]);
642 return 0;
643
644 default:
645 usage(argv[0]);
646 return 1;
647 }
648 }
649
650 if (!board)
651 error(1, 0, "no board has been specified");
652 if (!kernel_image)
653 error(1, 0, "no kernel image has been specified");
654 if (!rootfs_image)
655 error(1, 0, "no rootfs image has been specified");
656 if (!output)
657 error(1, 0, "no output filename has been specified");
658
659 if (strcmp(board, "CPE510") == 0)
660 do_cpe510(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade);
661 else if (strcmp(board, "C2600") == 0)
662 do_c2600(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade);
663 else
664 error(1, 0, "unsupported board %s", board);
665
666 return 0;
667 }