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