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