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