7e9cba299d482f0316105266654a131a978384f4
[openwrt/openwrt.git] / tools / firmware-utils / src / bcm4908img.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
4 */
5
6 #include <byteswap.h>
7 #include <endian.h>
8 #include <errno.h>
9 #include <stdint.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15
16 #if !defined(__BYTE_ORDER)
17 #error "Unknown byte order"
18 #endif
19
20 #if __BYTE_ORDER == __BIG_ENDIAN
21 #define cpu_to_le32(x) bswap_32(x)
22 #define le32_to_cpu(x) bswap_32(x)
23 #define cpu_to_be32(x) (x)
24 #define be32_to_cpu(x) (x)
25 #define cpu_to_le16(x) bswap_16(x)
26 #define le16_to_cpu(x) bswap_16(x)
27 #define cpu_to_be16(x) (x)
28 #define be16_to_cpu(x) (x)
29 #elif __BYTE_ORDER == __LITTLE_ENDIAN
30 #define cpu_to_le32(x) (x)
31 #define le32_to_cpu(x) (x)
32 #define cpu_to_be32(x) bswap_32(x)
33 #define be32_to_cpu(x) bswap_32(x)
34 #define cpu_to_le16(x) (x)
35 #define le16_to_cpu(x) (x)
36 #define cpu_to_be16(x) bswap_16(x)
37 #define be16_to_cpu(x) bswap_16(x)
38 #else
39 #error "Unsupported endianness"
40 #endif
41
42 #define WFI_VERSION 0x00005732
43 #define WFI_VERSION_NAND_1MB_DATA 0x00005731
44
45 #define WFI_NOR_FLASH 1
46 #define WFI_NAND16_FLASH 2
47 #define WFI_NAND128_FLASH 3
48 #define WFI_NAND256_FLASH 4
49 #define WFI_NAND512_FLASH 5
50 #define WFI_NAND1024_FLASH 6
51 #define WFI_NAND2048_FLASH 7
52
53 #define WFI_FLAG_HAS_PMC 0x1
54 #define WFI_FLAG_SUPPORTS_BTRM 0x2
55
56 struct bcm4908img_tail {
57 uint32_t crc32;
58 uint32_t version;
59 uint32_t chip_id;
60 uint32_t flash_type;
61 uint32_t flags;
62 };
63
64 /* Info about BCM4908 image */
65 struct bcm4908img_info {
66 size_t file_size;
67 size_t vendor_header_size; /* Vendor header size */
68 size_t cferom_size;
69 uint32_t crc32; /* Calculated checksum */
70 struct bcm4908img_tail tail;
71 };
72
73 char *pathname;
74
75 static inline size_t bcm4908img_min(size_t x, size_t y) {
76 return x < y ? x : y;
77 }
78
79 /**************************************************
80 * CRC32
81 **************************************************/
82
83 static const uint32_t crc32_tbl[] = {
84 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
85 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
86 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
87 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
88 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
89 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
90 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
91 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
92 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
93 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
94 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
95 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
96 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
97 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
98 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
99 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
100 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
101 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
102 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
103 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
104 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
105 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
106 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
107 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
108 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
109 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
110 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
111 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
112 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
113 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
114 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
115 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
116 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
117 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
118 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
119 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
120 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
121 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
122 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
123 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
124 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
125 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
126 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
127 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
128 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
129 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
130 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
131 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
132 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
133 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
134 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
135 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
136 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
137 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
138 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
139 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
140 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
141 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
142 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
143 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
144 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
145 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
146 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
147 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
148 };
149
150 uint32_t bcm4908img_crc32(uint32_t crc, uint8_t *buf, size_t len) {
151 while (len) {
152 crc = crc32_tbl[(crc ^ *buf) & 0xff] ^ (crc >> 8);
153 buf++;
154 len--;
155 }
156
157 return crc;
158 }
159
160 /**************************************************
161 * Helpers
162 **************************************************/
163
164 static FILE *bcm4908img_open(const char *pathname, const char *mode) {
165 struct stat st;
166
167 if (pathname)
168 return fopen(pathname, mode);
169
170 if (isatty(fileno(stdin))) {
171 fprintf(stderr, "Reading from TTY stdin is unsupported\n");
172 return NULL;
173 }
174
175 if (fstat(fileno(stdin), &st)) {
176 fprintf(stderr, "Failed to fstat stdin: %d\n", -errno);
177 return NULL;
178 }
179
180 if (S_ISFIFO(st.st_mode)) {
181 fprintf(stderr, "Reading from pipe stdin is unsupported\n");
182 return NULL;
183 }
184
185 return stdin;
186 }
187
188 static void bcm4908img_close(FILE *fp) {
189 if (fp != stdin)
190 fclose(fp);
191 }
192
193 /**************************************************
194 * Existing firmware parser
195 **************************************************/
196
197 struct chk_header {
198 uint32_t magic;
199 uint32_t header_len;
200 uint8_t reserved[8];
201 uint32_t kernel_chksum;
202 uint32_t rootfs_chksum;
203 uint32_t kernel_len;
204 uint32_t rootfs_len;
205 uint32_t image_chksum;
206 uint32_t header_chksum;
207 char board_id[0];
208 };
209
210 static int bcm4908img_parse(FILE *fp, struct bcm4908img_info *info) {
211 struct bcm4908img_tail *tail = &info->tail;
212 struct chk_header *chk;
213 struct stat st;
214 uint8_t buf[1024];
215 uint16_t tmp16;
216 size_t length;
217 size_t bytes;
218 int err = 0;
219
220 memset(info, 0, sizeof(*info));
221
222 /* File size */
223
224 if (fstat(fileno(fp), &st)) {
225 err = -errno;
226 fprintf(stderr, "Failed to fstat: %d\n", err);
227 return err;
228 }
229 info->file_size = st.st_size;
230
231 /* Vendor formats */
232
233 rewind(fp);
234 if (fread(buf, 1, sizeof(buf), fp) != sizeof(buf)) {
235 fprintf(stderr, "Failed to read file header\n");
236 return -EIO;
237 }
238 chk = (void *)buf;
239 if (be32_to_cpu(chk->magic) == 0x2a23245e)
240 info->vendor_header_size = be32_to_cpu(chk->header_len);
241
242 /* Sizes */
243
244 for (; info->vendor_header_size + info->cferom_size <= info->file_size; info->cferom_size += 0x20000) {
245 if (fseek(fp, info->vendor_header_size + info->cferom_size, SEEK_SET)) {
246 err = -errno;
247 fprintf(stderr, "Failed to fseek to the 0x%zx\n", info->cferom_size);
248 return err;
249 }
250 if (fread(&tmp16, 1, sizeof(tmp16), fp) != sizeof(tmp16)) {
251 fprintf(stderr, "Failed to read while looking for JFFS2\n");
252 return -EIO;
253 }
254 if (be16_to_cpu(tmp16) == 0x8519)
255 break;
256 }
257 if (info->vendor_header_size + info->cferom_size >= info->file_size) {
258 fprintf(stderr, "Failed to find cferom size (no bootfs found)\n");
259 return -EPROTO;
260 }
261
262 /* CRC32 */
263
264 fseek(fp, info->vendor_header_size, SEEK_SET);
265
266 info->crc32 = 0xffffffff;
267 length = info->file_size - info->vendor_header_size - sizeof(*tail);
268 while (length && (bytes = fread(buf, 1, bcm4908img_min(sizeof(buf), length), fp)) > 0) {
269 info->crc32 = bcm4908img_crc32(info->crc32, buf, bytes);
270 length -= bytes;
271 }
272 if (length) {
273 fprintf(stderr, "Failed to read last %zd B of data\n", length);
274 return -EIO;
275 }
276
277 /* Tail */
278
279 if (fread(tail, 1, sizeof(*tail), fp) != sizeof(*tail)) {
280 fprintf(stderr, "Failed to read BCM4908 image tail\n");
281 return -EIO;
282 }
283
284 /* Standard validation */
285
286 if (info->crc32 != le32_to_cpu(tail->crc32)) {
287 fprintf(stderr, "Invalid data crc32: 0x%08x instead of 0x%08x\n", info->crc32, le32_to_cpu(tail->crc32));
288 return -EPROTO;
289 }
290
291 return 0;
292 }
293
294 /**************************************************
295 * Info
296 **************************************************/
297
298 static int bcm4908img_info(int argc, char **argv) {
299 struct bcm4908img_info info;
300 const char *pathname = NULL;
301 FILE *fp;
302 int c;
303 int err = 0;
304
305 while ((c = getopt(argc, argv, "i:")) != -1) {
306 switch (c) {
307 case 'i':
308 pathname = optarg;
309 break;
310 }
311 }
312
313 fp = bcm4908img_open(pathname, "r");
314 if (!fp) {
315 fprintf(stderr, "Failed to open BCM4908 image\n");
316 err = -EACCES;
317 goto out;
318 }
319
320 err = bcm4908img_parse(fp, &info);
321 if (err) {
322 fprintf(stderr, "Failed to parse BCM4908 image\n");
323 goto err_close;
324 }
325
326 printf("Vendor header length:\t%zu\n", info.vendor_header_size);
327 printf("cferom size:\t0x%zx\n", info.cferom_size);
328 printf("Checksum:\t0x%08x\n", info.crc32);
329
330 err_close:
331 bcm4908img_close(fp);
332 out:
333 return err;
334 }
335
336 /**************************************************
337 * Create
338 **************************************************/
339
340 static ssize_t bcm4908img_create_append_file(FILE *trx, const char *in_path, uint32_t *crc32) {
341 FILE *in;
342 size_t bytes;
343 ssize_t length = 0;
344 uint8_t buf[1024];
345
346 in = fopen(in_path, "r");
347 if (!in) {
348 fprintf(stderr, "Failed to open %s\n", in_path);
349 return -EACCES;
350 }
351
352 while ((bytes = fread(buf, 1, sizeof(buf), in)) > 0) {
353 if (fwrite(buf, 1, bytes, trx) != bytes) {
354 fprintf(stderr, "Failed to write %zu B to %s\n", bytes, pathname);
355 length = -EIO;
356 break;
357 }
358 *crc32 = bcm4908img_crc32(*crc32, buf, bytes);
359 length += bytes;
360 }
361
362 fclose(in);
363
364 return length;
365 }
366
367 static ssize_t bcm4908img_create_append_zeros(FILE *trx, size_t length) {
368 uint8_t *buf;
369
370 buf = malloc(length);
371 if (!buf)
372 return -ENOMEM;
373 memset(buf, 0, length);
374
375 if (fwrite(buf, 1, length, trx) != length) {
376 fprintf(stderr, "Failed to write %zu B to %s\n", length, pathname);
377 free(buf);
378 return -EIO;
379 }
380
381 free(buf);
382
383 return length;
384 }
385
386 static ssize_t bcm4908img_create_align(FILE *trx, size_t cur_offset, size_t alignment) {
387 if (cur_offset & (alignment - 1)) {
388 size_t length = alignment - (cur_offset % alignment);
389 return bcm4908img_create_append_zeros(trx, length);
390 }
391
392 return 0;
393 }
394
395 static int bcm4908img_create(int argc, char **argv) {
396 struct bcm4908img_tail tail = {
397 .version = cpu_to_le32(WFI_VERSION),
398 .chip_id = cpu_to_le32(0x4908),
399 .flash_type = cpu_to_le32(WFI_NAND128_FLASH),
400 .flags = cpu_to_le32(WFI_FLAG_SUPPORTS_BTRM),
401 };
402 uint32_t crc32 = 0xffffffff;
403 size_t cur_offset = 0;
404 ssize_t bytes;
405 FILE *fp;
406 int c;
407 int err = 0;
408
409 if (argc < 3) {
410 fprintf(stderr, "No BCM4908 image pathname passed\n");
411 err = -EINVAL;
412 goto out;
413 }
414 pathname = argv[2];
415
416 fp = fopen(pathname, "w+");
417 if (!fp) {
418 fprintf(stderr, "Failed to open %s\n", pathname);
419 err = -EACCES;
420 goto out;
421 }
422
423 optind = 3;
424 while ((c = getopt(argc, argv, "f:a:A:")) != -1) {
425 switch (c) {
426 case 'f':
427 bytes = bcm4908img_create_append_file(fp, optarg, &crc32);
428 if (bytes < 0) {
429 fprintf(stderr, "Failed to append file %s\n", optarg);
430 } else {
431 cur_offset += bytes;
432 }
433 break;
434 case 'a':
435 bytes = bcm4908img_create_align(fp, cur_offset, strtol(optarg, NULL, 0));
436 if (bytes < 0)
437 fprintf(stderr, "Failed to append zeros\n");
438 else
439 cur_offset += bytes;
440 break;
441 case 'A':
442 bytes = strtol(optarg, NULL, 0) - cur_offset;
443 if (bytes < 0) {
444 fprintf(stderr, "Current BCM4908 image length is 0x%zx, can't pad it with zeros to 0x%lx\n", cur_offset, strtol(optarg, NULL, 0));
445 } else {
446 bytes = bcm4908img_create_append_zeros(fp, bytes);
447 if (bytes < 0)
448 fprintf(stderr, "Failed to append zeros\n");
449 else
450 cur_offset += bytes;
451 }
452 break;
453 }
454 if (err)
455 goto err_close;
456 }
457
458 tail.crc32 = cpu_to_le32(crc32);
459
460 bytes = fwrite(&tail, 1, sizeof(tail), fp);
461 if (bytes != sizeof(tail)) {
462 fprintf(stderr, "Failed to write BCM4908 image tail to %s\n", pathname);
463 return -EIO;
464 }
465
466 err_close:
467 fclose(fp);
468 out:
469 return err;
470 }
471
472 /**************************************************
473 * Extract
474 **************************************************/
475
476 static int bcm4908img_extract(int argc, char **argv) {
477 struct bcm4908img_info info;
478 const char *pathname = NULL;
479 uint8_t buf[1024];
480 const char *type;
481 size_t offset;
482 size_t length;
483 size_t bytes;
484 FILE *fp;
485 int c;
486 int err = 0;
487
488 while ((c = getopt(argc, argv, "i:t:")) != -1) {
489 switch (c) {
490 case 'i':
491 pathname = optarg;
492 break;
493 case 't':
494 type = optarg;
495 break;
496 }
497 }
498
499 fp = bcm4908img_open(pathname, "r");
500 if (!fp) {
501 fprintf(stderr, "Failed to open BCM4908 image\n");
502 err = -EACCES;
503 goto err_out;
504 }
505
506 err = bcm4908img_parse(fp, &info);
507 if (err) {
508 fprintf(stderr, "Failed to parse BCM4908 image\n");
509 goto err_close;
510 }
511
512 if (!strcmp(type, "cferom")) {
513 offset = 0;
514 length = info.cferom_size;
515 if (!length) {
516 err = -ENOENT;
517 fprintf(stderr, "This BCM4908 image doesn't contain cferom\n");
518 goto err_close;
519 }
520 } else if (!strcmp(type, "firmware")) {
521 offset = info.vendor_header_size + info.cferom_size;
522 length = info.file_size - offset - sizeof(struct bcm4908img_tail);
523 } else {
524 err = -EINVAL;
525 fprintf(stderr, "Unsupported extract type: %s\n", type);
526 goto err_close;
527 }
528
529 if (!length) {
530 err = -EINVAL;
531 fprintf(stderr, "No data to extract specified\n");
532 goto err_close;
533 }
534
535 fseek(fp, offset, SEEK_SET);
536 while (length && (bytes = fread(buf, 1, bcm4908img_min(sizeof(buf), length), fp)) > 0) {
537 fwrite(buf, bytes, 1, stdout);
538 length -= bytes;
539 }
540 if (length) {
541 err = -EIO;
542 fprintf(stderr, "Failed to read last %zd B of data\n", length);
543 goto err_close;
544 }
545
546 err_close:
547 bcm4908img_close(fp);
548 err_out:
549 return err;
550 }
551
552 /**************************************************
553 * Start
554 **************************************************/
555
556 static void usage() {
557 printf("Usage:\n");
558 printf("\n");
559 printf("Info about a BCM4908 image:\n");
560 printf("\tbcm4908img info <options>\n");
561 printf("\t-i <file>\t\t\t\tinput BCM490 image\n");
562 printf("\n");
563 printf("Creating a new BCM4908 image:\n");
564 printf("\tbcm4908img create <file> [options]\n");
565 printf("\t-f file\t\t\t\tadd data from specified file\n");
566 printf("\t-a alignment\t\t\tpad image with zeros to specified alignment\n");
567 printf("\t-A offset\t\t\t\tappend zeros until reaching specified offset\n");
568 printf("\n");
569 printf("Extracting from a BCM4908 image:\n");
570 printf("\tbcm4908img extract <options>\n");
571 printf("\t-i <file>\t\t\t\tinput BCM490 image\n");
572 printf("\t-t <type>\t\t\t\tone of: cferom, bootfs, rootfs, firmware\n");
573 }
574
575 int main(int argc, char **argv) {
576 if (argc > 1) {
577 optind++;
578 if (!strcmp(argv[1], "info"))
579 return bcm4908img_info(argc, argv);
580 else if (!strcmp(argv[1], "create"))
581 return bcm4908img_create(argc, argv);
582 else if (!strcmp(argv[1], "extract"))
583 return bcm4908img_extract(argc, argv);
584 }
585
586 usage();
587 return 0;
588 }