16e0afe9de1343a214ade3defdc0e66f7fed6c22
[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 * Check
266 **************************************************/
267
268 static int bcm4908img_check(int argc, char **argv) {
269 struct bcm4908img_info info;
270 const char *pathname = NULL;
271 FILE *fp;
272 int err = 0;
273
274 if (argc >= 3)
275 pathname = argv[2];
276
277 fp = bcm4908img_open(pathname, "r");
278 if (!fp) {
279 fprintf(stderr, "Failed to open %s\n", pathname);
280 err = -EACCES;
281 goto out;
282 }
283
284 err = bcm4908img_parse(fp, &info);
285 if (err) {
286 fprintf(stderr, "Failed to parse %s\n", pathname);
287 goto err_close;
288 }
289
290 printf("Found a valid BCM4908 image (crc: 0x%08x)\n", info.crc32);
291
292 err_close:
293 bcm4908img_close(fp);
294 out:
295 return err;
296 }
297
298 /**************************************************
299 * Create
300 **************************************************/
301
302 static ssize_t bcm4908img_create_append_file(FILE *trx, const char *in_path, uint32_t *crc32) {
303 FILE *in;
304 size_t bytes;
305 ssize_t length = 0;
306 uint8_t buf[1024];
307
308 in = fopen(in_path, "r");
309 if (!in) {
310 fprintf(stderr, "Failed to open %s\n", in_path);
311 return -EACCES;
312 }
313
314 while ((bytes = fread(buf, 1, sizeof(buf), in)) > 0) {
315 if (fwrite(buf, 1, bytes, trx) != bytes) {
316 fprintf(stderr, "Failed to write %zu B to %s\n", bytes, pathname);
317 length = -EIO;
318 break;
319 }
320 *crc32 = bcm4908img_crc32(*crc32, buf, bytes);
321 length += bytes;
322 }
323
324 fclose(in);
325
326 return length;
327 }
328
329 static ssize_t bcm4908img_create_append_zeros(FILE *trx, size_t length) {
330 uint8_t *buf;
331
332 buf = malloc(length);
333 if (!buf)
334 return -ENOMEM;
335 memset(buf, 0, length);
336
337 if (fwrite(buf, 1, length, trx) != length) {
338 fprintf(stderr, "Failed to write %zu B to %s\n", length, pathname);
339 free(buf);
340 return -EIO;
341 }
342
343 free(buf);
344
345 return length;
346 }
347
348 static ssize_t bcm4908img_create_align(FILE *trx, size_t cur_offset, size_t alignment) {
349 if (cur_offset & (alignment - 1)) {
350 size_t length = alignment - (cur_offset % alignment);
351 return bcm4908img_create_append_zeros(trx, length);
352 }
353
354 return 0;
355 }
356
357 static int bcm4908img_create(int argc, char **argv) {
358 struct bcm4908img_tail tail = {
359 .version = cpu_to_le32(WFI_VERSION),
360 .chip_id = cpu_to_le32(0x4908),
361 .flash_type = cpu_to_le32(WFI_NAND128_FLASH),
362 .flags = cpu_to_le32(WFI_FLAG_SUPPORTS_BTRM),
363 };
364 uint32_t crc32 = 0xffffffff;
365 size_t cur_offset = 0;
366 ssize_t bytes;
367 FILE *fp;
368 int c;
369 int err = 0;
370
371 if (argc < 3) {
372 fprintf(stderr, "No BCM4908 image pathname passed\n");
373 err = -EINVAL;
374 goto out;
375 }
376 pathname = argv[2];
377
378 fp = fopen(pathname, "w+");
379 if (!fp) {
380 fprintf(stderr, "Failed to open %s\n", pathname);
381 err = -EACCES;
382 goto out;
383 }
384
385 optind = 3;
386 while ((c = getopt(argc, argv, "f:a:A:")) != -1) {
387 switch (c) {
388 case 'f':
389 bytes = bcm4908img_create_append_file(fp, optarg, &crc32);
390 if (bytes < 0) {
391 fprintf(stderr, "Failed to append file %s\n", optarg);
392 } else {
393 cur_offset += bytes;
394 }
395 break;
396 case 'a':
397 bytes = bcm4908img_create_align(fp, cur_offset, strtol(optarg, NULL, 0));
398 if (bytes < 0)
399 fprintf(stderr, "Failed to append zeros\n");
400 else
401 cur_offset += bytes;
402 break;
403 case 'A':
404 bytes = strtol(optarg, NULL, 0) - cur_offset;
405 if (bytes < 0) {
406 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));
407 } else {
408 bytes = bcm4908img_create_append_zeros(fp, bytes);
409 if (bytes < 0)
410 fprintf(stderr, "Failed to append zeros\n");
411 else
412 cur_offset += bytes;
413 }
414 break;
415 }
416 if (err)
417 goto err_close;
418 }
419
420 tail.crc32 = cpu_to_le32(crc32);
421
422 bytes = fwrite(&tail, 1, sizeof(tail), fp);
423 if (bytes != sizeof(tail)) {
424 fprintf(stderr, "Failed to write BCM4908 image tail to %s\n", pathname);
425 return -EIO;
426 }
427
428 err_close:
429 fclose(fp);
430 out:
431 return err;
432 }
433
434 /**************************************************
435 * Start
436 **************************************************/
437
438 static void usage() {
439 printf("Usage:\n");
440 printf("\n");
441 printf("Checking a BCM4908 image:\n");
442 printf("\tbcm4908img check <file>\t\t\tcheck if images is valid\n");
443 printf("\n");
444 printf("Creating a new BCM4908 image:\n");
445 printf("\tbcm4908img create <file> [options]\n");
446 printf("\t-f file\t\t\t\tadd data from specified file\n");
447 printf("\t-a alignment\t\t\tpad image with zeros to specified alignment\n");
448 printf("\t-A offset\t\t\t\tappend zeros until reaching specified offset\n");
449 }
450
451 int main(int argc, char **argv) {
452 if (argc > 1) {
453 if (!strcmp(argv[1], "check"))
454 return bcm4908img_check(argc, argv);
455 else if (!strcmp(argv[1], "create"))
456 return bcm4908img_create(argc, argv);
457 }
458
459 usage();
460 return 0;
461 }