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