firmware-utils: replace GPL 2.0 boilerplate/reference with SPDX
[openwrt/staging/ldir.git] / tools / firmware-utils / src / mkrasimage.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * --- ZyXEL header format ---
4 * Original Version by Benjamin Berg <benjamin@sipsolutions.net>
5 * C implementation based on generation-script by Christian Lamparter <chunkeey@gmail.com>
6 *
7 * The firmware image prefixed with a header (which is written into the MTD device).
8 * The header is one erase block (~64KiB) in size, but the checksum only convers the
9 * first 2KiB. Padding is 0xff. All integers are in big-endian.
10 *
11 * The checksum is always a 16-Bit System V checksum (sum -s) stored in a 32-Bit integer.
12 *
13 * 4 bytes: checksum of the rootfs image
14 * 4 bytes: length of the contained rootfs image file (big endian)
15 * 32 bytes: Firmware Version string (NUL terminated, 0xff padded)
16 * 4 bytes: checksum over the header partition (big endian - see below)
17 * 64 bytes: Model (e.g. "NBG6617", NUL termiated, 0xff padded)
18 * 4 bytes: checksum of the kernel partition
19 * 4 bytes: length of the contained kernel image file (big endian)
20 * rest: 0xff padding (To erase block size)
21 *
22 * The kernel partition checksum and length is not used for every device.
23 * If it's notused, pad those 8 bytes with 0xFF.
24 *
25 * The checksums are calculated by adding up all bytes and if a 16bit
26 * overflow occurs, one is added and the sum is masked to 16 bit:
27 * csum = csum + databyte; if (csum > 0xffff) { csum += 1; csum &= 0xffff };
28 * Should the file have an odd number of bytes then the byte len-0x800 is
29 * used additionally.
30 *
31 * The checksum for the header is calculated over the first 2048 bytes with
32 * the rootfs image checksum as the placeholder during calculation.
33 */
34 #include <fcntl.h>
35 #include <getopt.h>
36 #include <libgen.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <stdlib.h>
40 #include <unistd.h>
41
42 #include <sys/mman.h>
43 #include <sys/stat.h>
44
45 #include <arpa/inet.h>
46
47 #define VERSION_STRING_LEN 31
48 #define ROOTFS_HEADER_LEN 40
49
50 #define KERNEL_HEADER_LEN 8
51
52 #define BOARD_NAME_LEN 64
53 #define BOARD_HEADER_LEN 68
54
55 #define HEADER_PARTITION_CALC_LENGTH 2048
56 #define HEADER_PARTITION_LENGTH 0x10000
57
58 struct file_info {
59 char *name; /* name of the file */
60 char *data; /* file content */
61 size_t size; /* length of the file */
62 };
63
64 static char *progname;
65
66 static char *board_name = 0;
67 static char *version_name = 0;
68 static unsigned int rootfs_size = 0;
69 static unsigned int header_length = HEADER_PARTITION_LENGTH;
70
71 static struct file_info kernel = { NULL, NULL, 0 };
72 static struct file_info rootfs = { NULL, NULL, 0 };
73 static struct file_info rootfs_out = { NULL, NULL, 0 };
74 static struct file_info out = { NULL, NULL, 0 };
75
76 #define ERR(fmt, ...) do { \
77 fprintf(stderr, "[%s] *** error: " fmt "\n", \
78 progname, ## __VA_ARGS__ ); \
79 } while (0)
80
81 void map_file(struct file_info *finfo)
82 {
83 struct stat file_stat = {0};
84 int fd;
85
86 fd = open(finfo->name, O_RDONLY, (mode_t)0600);
87 if (fd == -1) {
88 ERR("Error while opening file %s.", finfo->name);
89 exit(EXIT_FAILURE);
90 }
91
92 if (fstat(fd, &file_stat) == -1) {
93 ERR("Error getting file size for %s.", finfo->name);
94 exit(EXIT_FAILURE);
95 }
96
97 finfo->size = file_stat.st_size;
98 finfo->data = mmap(0, finfo->size, PROT_READ, MAP_SHARED, fd, 0);
99
100 if (finfo->data == MAP_FAILED) {
101 ERR("Error mapping file %s.", finfo->name);
102 exit(EXIT_FAILURE);
103 }
104
105 close(fd);
106 }
107
108 void unmap_file(struct file_info *finfo)
109 {
110 if(munmap(finfo->data, finfo->size) == -1) {
111 ERR("Error unmapping file %s.", finfo->name);
112 exit(EXIT_FAILURE);
113 }
114 }
115
116 void write_file(struct file_info *finfo)
117 {
118 FILE *fout = fopen(finfo->name, "w");
119
120 fwrite(finfo->data, finfo->size, 1, fout);
121
122 if (ferror(fout)) {
123 ERR("Wanted to write, but something went wrong.");
124 exit(EXIT_FAILURE);
125 }
126
127 fclose(fout);
128 }
129
130 void usage(int status)
131 {
132 FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout;
133
134 fprintf(stream, "Usage: %s [OPTIONS...]\n", progname);
135 fprintf(stream,
136 "\n"
137 "Options:\n"
138 " -k <kernel> path for kernel image\n"
139 " -r <rootfs> path for rootfs image\n"
140 " -s <rfssize> size of output rootfs\n"
141 " -v <version> version string\n"
142 " -b <boardname> name of board to generate image for\n"
143 " -o <out_name> name of output image\n"
144 " -l <hdr_length> length of header, default 65536\n"
145 " -h show this screen\n"
146 );
147
148 exit(status);
149 }
150
151 static int sysv_chksm(const unsigned char *data, int size)
152 {
153 int r;
154 int checksum;
155 unsigned int s = 0; /* The sum of all the input bytes, modulo (UINT_MAX + 1). */
156
157
158 for (int i = 0; i < size; i++) {
159 s += data[i];
160 }
161
162 r = (s & 0xffff) + ((s & 0xffffffff) >> 16);
163 checksum = (r & 0xffff) + (r >> 16);
164
165 return checksum;
166 }
167
168 static int zyxel_chksm(const unsigned char *data, int size)
169 {
170 return htonl(sysv_chksm(data, size));
171 }
172
173 char *generate_rootfs_header(struct file_info filesystem, char *version)
174 {
175 size_t version_string_length;
176 unsigned int chksm, size;
177 char *rootfs_header;
178 size_t ptr = 0;
179
180 rootfs_header = malloc(ROOTFS_HEADER_LEN);
181 if (!rootfs_header) {
182 ERR("Couldn't allocate memory for rootfs header!");
183 exit(EXIT_FAILURE);
184 }
185
186 /* Prepare padding for firmware-version string here */
187 memset(rootfs_header, 0xff, ROOTFS_HEADER_LEN);
188
189 chksm = zyxel_chksm((const unsigned char *)filesystem.data, filesystem.size);
190 size = htonl(filesystem.size);
191
192 /* 4 bytes: checksum of the rootfs image */
193 memcpy(rootfs_header + ptr, &chksm, 4);
194 ptr += 4;
195
196 /* 4 bytes: length of the contained rootfs image file (big endian) */
197 memcpy(rootfs_header + ptr, &size, 4);
198 ptr += 4;
199
200 /* 32 bytes: Firmware Version string (NUL terminated, 0xff padded) */
201 version_string_length = strlen(version) <= VERSION_STRING_LEN ? strlen(version) : VERSION_STRING_LEN;
202 memcpy(rootfs_header + ptr, version, version_string_length);
203 ptr += version_string_length;
204 /* Add null-terminator */
205 rootfs_header[ptr] = 0x0;
206
207 return rootfs_header;
208 }
209
210 char *generate_kernel_header(struct file_info kernel)
211 {
212 unsigned int chksm, size;
213 char *kernel_header;
214 size_t ptr = 0;
215
216 kernel_header = malloc(KERNEL_HEADER_LEN);
217 if (!kernel_header) {
218 ERR("Couldn't allocate memory for kernel header!");
219 exit(EXIT_FAILURE);
220 }
221
222 chksm = zyxel_chksm((const unsigned char *)kernel.data, kernel.size);
223 size = htonl(kernel.size);
224
225 /* 4 bytes: checksum of the kernel image */
226 memcpy(kernel_header + ptr, &chksm, 4);
227 ptr += 4;
228
229 /* 4 bytes: length of the contained kernel image file (big endian) */
230 memcpy(kernel_header + ptr, &size, 4);
231
232 return kernel_header;
233 }
234
235 unsigned int generate_board_header_checksum(char *kernel_hdr, char *rootfs_hdr, char *boardname)
236 {
237 char *board_hdr_tmp;
238 unsigned int sum;
239 size_t ptr = 0;
240
241 /*
242 * The checksum of the board header is calculated over the first 2048 bytes of
243 * the header partition with the rootfs checksum used as a placeholder for then
244 * board checksum we calculate in this step. The checksum gained from this step
245 * is then used for the final board header partition.
246 */
247
248 board_hdr_tmp = malloc(HEADER_PARTITION_CALC_LENGTH);
249 if (!board_hdr_tmp) {
250 ERR("Couldn't allocate memory for temporary board header!");
251 exit(EXIT_FAILURE);
252 }
253 memset(board_hdr_tmp, 0xff, HEADER_PARTITION_CALC_LENGTH);
254
255 /* 40 bytes: RootFS header */
256 memcpy(board_hdr_tmp, rootfs_hdr, ROOTFS_HEADER_LEN);
257 ptr += ROOTFS_HEADER_LEN;
258
259 /* 4 bytes: RootFS checksum (BE) as placeholder for board-header checksum */
260 memcpy(board_hdr_tmp + ptr, rootfs_hdr, 4);
261 ptr += 4;
262
263 /* 32 bytes: Model (e.g. "NBG6617", NUL termiated, 0xff padded) */
264 memcpy(board_hdr_tmp + ptr, boardname, strlen(boardname));
265 ptr += strlen(boardname);
266 /* Add null-terminator */
267 board_hdr_tmp[ptr] = 0x0;
268 ptr = ROOTFS_HEADER_LEN + 4 + BOARD_NAME_LEN;
269
270 /* 8 bytes: Kernel header */
271 if (kernel_hdr)
272 memcpy(board_hdr_tmp + ptr, kernel_hdr, 8);
273
274 /* Calculate the checksum over the first 2048 bytes */
275 sum = zyxel_chksm((const unsigned char *)board_hdr_tmp, HEADER_PARTITION_CALC_LENGTH);
276 free(board_hdr_tmp);
277 return sum;
278 }
279
280 char *generate_board_header(char *kernel_hdr, char *rootfs_hdr, char *boardname)
281 {
282 unsigned int board_checksum;
283 char *board_hdr;
284
285 board_hdr = malloc(BOARD_HEADER_LEN);
286 if (!board_hdr) {
287 ERR("Couldn't allocate memory for board header!");
288 exit(EXIT_FAILURE);
289 }
290 memset(board_hdr, 0xff, BOARD_HEADER_LEN);
291
292 /* 4 bytes: checksum over the header partition (big endian) */
293 board_checksum = generate_board_header_checksum(kernel_hdr, rootfs_hdr, boardname);
294 memcpy(board_hdr, &board_checksum, 4);
295
296 /* 32 bytes: Model (e.g. "NBG6617", NUL termiated, 0xff padded) */
297 memcpy(board_hdr + 4, boardname, strlen(boardname));
298 board_hdr[4 + strlen(boardname)] = 0x0;
299
300 return board_hdr;
301 }
302
303 int build_image()
304 {
305 char *rootfs_header = NULL;
306 char *kernel_header = NULL;
307 char *board_header = NULL;
308
309 size_t ptr;
310
311 /* Load files */
312 if (kernel.name)
313 map_file(&kernel);
314 map_file(&rootfs);
315
316 /* As ZyXEL Web-GUI only accept images with a rootfs equal or larger than the first firmware shipped
317 * for the device, we need to pad rootfs partition to this size. To perform further calculations, we
318 * decide the size of this part here. In case the rootfs we want to integrate in our image is larger,
319 * take it's size, otherwise the supplied size.
320 *
321 * Be careful! We rely on assertion of correct size to be performed beforehand. It is unknown if images
322 * with a to large rootfs are accepted or not.
323 */
324 rootfs_out.size = rootfs_size < rootfs.size ? rootfs.size : rootfs_size;
325
326 /*
327 * Allocate memory and copy input rootfs for temporary output rootfs.
328 * This is important as we have to generate the rootfs checksum over the
329 * entire rootfs partition. As we might have to pad the partition to allow
330 * for flashing via ZyXEL's Web-GUI, we prepare the rootfs partition for the
331 * output image here (and also use it for calculating the rootfs checksum).
332 *
333 * The roofs padding has to be done with 0x00.
334 */
335 rootfs_out.data = calloc(rootfs_out.size, sizeof(char));
336 memcpy(rootfs_out.data, rootfs.data, rootfs.size);
337
338 /* Prepare headers */
339 rootfs_header = generate_rootfs_header(rootfs_out, version_name);
340 if (kernel.name)
341 kernel_header = generate_kernel_header(kernel);
342 board_header = generate_board_header(kernel_header, rootfs_header, board_name);
343
344 /* Prepare output file */
345 out.size = header_length + rootfs_out.size;
346 if (kernel.name)
347 out.size += kernel.size;
348 out.data = malloc(out.size);
349 memset(out.data, 0xFF, out.size);
350
351 /* Build output image */
352 memcpy(out.data, rootfs_header, ROOTFS_HEADER_LEN);
353 memcpy(out.data + ROOTFS_HEADER_LEN, board_header, BOARD_HEADER_LEN);
354 if (kernel.name)
355 memcpy(out.data + ROOTFS_HEADER_LEN + BOARD_HEADER_LEN, kernel_header, KERNEL_HEADER_LEN);
356 ptr = header_length;
357 memcpy(out.data + ptr, rootfs_out.data, rootfs_out.size);
358 ptr += rootfs_out.size;
359 if (kernel.name)
360 memcpy(out.data + ptr, kernel.data, kernel.size);
361
362 /* Write back output image */
363 write_file(&out);
364
365 /* Free allocated memory */
366 if (kernel.name)
367 unmap_file(&kernel);
368 unmap_file(&rootfs);
369 free(out.data);
370 free(rootfs_out.data);
371
372 free(rootfs_header);
373 if (kernel.name)
374 free(kernel_header);
375 free(board_header);
376
377 return 0;
378 }
379
380 int check_options()
381 {
382 if (!rootfs.name) {
383 ERR("No rootfs filename supplied");
384 return -2;
385 }
386
387 if (!out.name) {
388 ERR("No output filename supplied");
389 return -3;
390 }
391
392 if (!board_name) {
393 ERR("No board-name supplied");
394 return -4;
395 }
396
397 if (!version_name) {
398 ERR("No version supplied");
399 return -5;
400 }
401
402 if (rootfs_size <= 0) {
403 ERR("Invalid rootfs size supplied");
404 return -6;
405 }
406
407 if (strlen(board_name) > 31) {
408 ERR("Board name is to long");
409 return -7;
410 }
411 return 0;
412 }
413
414 int main(int argc, char *argv[])
415 {
416 int ret;
417 progname = basename(argv[0]);
418 while (1) {
419 int c;
420
421 c = getopt(argc, argv, "b:k:o:r:s:v:l:h");
422 if (c == -1)
423 break;
424
425 switch (c) {
426 case 'b':
427 board_name = optarg;
428 break;
429 case 'h':
430 usage(EXIT_SUCCESS);
431 break;
432 case 'k':
433 kernel.name = optarg;
434 break;
435 case 'o':
436 out.name = optarg;
437 break;
438 case 'r':
439 rootfs.name = optarg;
440 break;
441 case 's':
442 sscanf(optarg, "%u", &rootfs_size);
443 break;
444 case 'v':
445 version_name = optarg;
446 break;
447 case 'l':
448 sscanf(optarg, "%u", &header_length);
449 break;
450 default:
451 usage(EXIT_FAILURE);
452 break;
453 }
454 }
455
456 ret = check_options();
457 if (ret)
458 usage(EXIT_FAILURE);
459
460 return build_image();
461 }