1 | /* |
2 | * Copyright (C) 2007 Ubiquiti Networks, Inc. |
3 | * Copyright (C) 2008 Lukas Kuna <ValXdater@seznam.cz> |
4 | * |
5 | * This program is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU General Public License as |
7 | * published by the Free Software Foundation; either version 2 of the |
8 | * License, or (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, but |
11 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License |
16 | * along with this program; if not, write to the Free Software |
17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
18 | */ |
19 | |
20 | #include <sys/types.h> |
21 | #include <sys/stat.h> |
22 | #include <fcntl.h> |
23 | #include <unistd.h> |
24 | #include <string.h> |
25 | #include <errno.h> |
26 | #include <zlib.h> |
27 | #include <sys/mman.h> |
28 | #include <netinet/in.h> |
29 | #include <stdio.h> |
30 | #include <stdlib.h> |
31 | #include <limits.h> |
32 | #include "fw.h" |
33 | |
34 | typedef struct fw_layout_data { |
35 | char name[PATH_MAX]; |
36 | u_int32_t kern_start; |
37 | u_int32_t kern_entry; |
38 | u_int32_t firmware_max_length; |
39 | } fw_layout_t; |
40 | |
41 | fw_layout_t fw_layout_data[] = { |
42 | { |
43 | .name = "XS2", |
44 | .kern_start = 0xbfc30000, |
45 | .kern_entry = 0x80041000, |
46 | .firmware_max_length= 0x00390000, |
47 | }, |
48 | { |
49 | .name = "XS5", |
50 | .kern_start = 0xbe030000, |
51 | .kern_entry = 0x80041000, |
52 | .firmware_max_length= 0x00390000, |
53 | }, |
54 | { |
55 | .name = "RS", |
56 | .kern_start = 0xbf030000, |
57 | .kern_entry = 0x80060000, |
58 | .firmware_max_length= 0x00B00000, |
59 | }, |
60 | { |
61 | .name = "RSPRO", |
62 | .kern_start = 0xbf030000, |
63 | .kern_entry = 0x80060000, |
64 | .firmware_max_length= 0x00B00000, |
65 | }, |
66 | { |
67 | .name = "LS-SR71", |
68 | .kern_start = 0xbf030000, |
69 | .kern_entry = 0x80060000, |
70 | .firmware_max_length= 0x00640000, |
71 | }, |
72 | { |
73 | .name = "XS2-8", |
74 | .kern_start = 0xa8030000, |
75 | .kern_entry = 0x80041000, |
76 | .firmware_max_length= 0x006C0000, |
77 | }, |
78 | { |
79 | .name = "XM", |
80 | .kern_start = 0x9f050000, |
81 | .kern_entry = 0x80002000, |
82 | .firmware_max_length= 0x006A0000, |
83 | }, |
84 | { .name = "", |
85 | }, |
86 | }; |
87 | |
88 | typedef struct part_data { |
89 | char partition_name[64]; |
90 | int partition_index; |
91 | u_int32_t partition_baseaddr; |
92 | u_int32_t partition_startaddr; |
93 | u_int32_t partition_memaddr; |
94 | u_int32_t partition_entryaddr; |
95 | u_int32_t partition_length; |
96 | |
97 | char filename[PATH_MAX]; |
98 | struct stat stats; |
99 | } part_data_t; |
100 | |
101 | #define MAX_SECTIONS 8 |
102 | #define DEFAULT_OUTPUT_FILE "firmware-image.bin" |
103 | #define DEFAULT_VERSION "UNKNOWN" |
104 | |
105 | #define OPTIONS "B:hv:o:r:k:" |
106 | |
107 | static int debug = 0; |
108 | |
109 | typedef struct image_info { |
110 | char version[256]; |
111 | char outputfile[PATH_MAX]; |
112 | u_int32_t part_count; |
113 | part_data_t parts[MAX_SECTIONS]; |
114 | } image_info_t; |
115 | |
116 | static void write_header(void* mem, const char* version) |
117 | { |
118 | header_t* header = mem; |
119 | memset(header, 0, sizeof(header_t)); |
120 | |
121 | memcpy(header->magic, MAGIC_HEADER, MAGIC_LENGTH); |
122 | strncpy(header->version, version, sizeof(header->version)); |
123 | header->crc = htonl(crc32(0L, (unsigned char *)header, |
124 | sizeof(header_t) - 2 * sizeof(u_int32_t))); |
125 | header->pad = 0L; |
126 | } |
127 | |
128 | |
129 | static void write_signature(void* mem, u_int32_t sig_offset) |
130 | { |
131 | /* write signature */ |
132 | signature_t* sign = (signature_t*)(mem + sig_offset); |
133 | memset(sign, 0, sizeof(signature_t)); |
134 | |
135 | memcpy(sign->magic, MAGIC_END, MAGIC_LENGTH); |
136 | sign->crc = htonl(crc32(0L,(unsigned char *)mem, sig_offset)); |
137 | sign->pad = 0L; |
138 | } |
139 | |
140 | static int write_part(void* mem, part_data_t* d) |
141 | { |
142 | char* addr; |
143 | int fd; |
144 | part_t* p = mem; |
145 | part_crc_t* crc = mem + sizeof(part_t) + d->stats.st_size; |
146 | |
147 | fd = open(d->filename, O_RDONLY); |
148 | if (fd < 0) |
149 | { |
150 | ERROR("Failed opening file '%s'\n", d->filename); |
151 | return -1; |
152 | } |
153 | |
154 | if ((addr=(char*)mmap(0, d->stats.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) |
155 | { |
156 | ERROR("Failed mmaping memory for file '%s'\n", d->filename); |
157 | close(fd); |
158 | return -2; |
159 | } |
160 | |
161 | memcpy(mem + sizeof(part_t), addr, d->stats.st_size); |
162 | munmap(addr, d->stats.st_size); |
163 | |
164 | memset(p->name, 0, sizeof(p->name)); |
165 | strncpy(p->magic, MAGIC_PART, MAGIC_LENGTH); |
166 | strncpy(p->name, d->partition_name, sizeof(p->name)); |
167 | p->index = htonl(d->partition_index); |
168 | p->data_size = htonl(d->stats.st_size); |
169 | p->part_size = htonl(d->partition_length); |
170 | p->baseaddr = htonl(d->partition_baseaddr); |
171 | p->memaddr = htonl(d->partition_memaddr); |
172 | p->entryaddr = htonl(d->partition_entryaddr); |
173 | |
174 | crc->crc = htonl(crc32(0L, mem, d->stats.st_size + sizeof(part_t))); |
175 | crc->pad = 0L; |
176 | |
177 | return 0; |
178 | } |
179 | |
180 | static void usage(const char* progname) |
181 | { |
182 | INFO("Version %s\n" |
183 | "Usage: %s [options]\n" |
184 | "\t-v <version string>\t - firmware version information, default: %s\n" |
185 | "\t-o <output file>\t - firmware output file, default: %s\n" |
186 | "\t-k <kernel file>\t\t - kernel file\n" |
187 | "\t-r <rootfs file>\t\t - rootfs file\n" |
188 | "\t-B <board name>\t\t - choose firmware layout for specified board (XS2, XS5, RS, XM)\n" |
189 | "\t-h\t\t\t - this help\n", VERSION, |
190 | progname, DEFAULT_VERSION, DEFAULT_OUTPUT_FILE); |
191 | } |
192 | |
193 | static void print_image_info(const image_info_t* im) |
194 | { |
195 | int i = 0; |
196 | INFO("Firmware version: '%s'\n" |
197 | "Output file: '%s'\n" |
198 | "Part count: %u\n", |
199 | im->version, im->outputfile, |
200 | im->part_count); |
201 | |
202 | for (i = 0; i < im->part_count; ++i) |
203 | { |
204 | const part_data_t* d = &im->parts[i]; |
205 | INFO(" %10s: %8ld bytes (free: %8ld)\n", |
206 | d->partition_name, |
207 | d->stats.st_size, |
208 | d->partition_length - d->stats.st_size); |
209 | } |
210 | } |
211 | |
212 | |
213 | |
214 | static u_int32_t filelength(const char* file) |
215 | { |
216 | FILE *p; |
217 | int ret = -1; |
218 | |
219 | if ( (p = fopen(file, "rb") ) == NULL) return (-1); |
220 | |
221 | fseek(p, 0, SEEK_END); |
222 | ret = ftell(p); |
223 | |
224 | fclose (p); |
225 | |
226 | return (ret); |
227 | } |
228 | |
229 | static int create_image_layout(const char* kernelfile, const char* rootfsfile, char* board_name, image_info_t* im) |
230 | { |
231 | part_data_t* kernel = &im->parts[0]; |
232 | part_data_t* rootfs = &im->parts[1]; |
233 | |
234 | fw_layout_t* p; |
235 | |
236 | p = &fw_layout_data[0]; |
237 | while ((strlen(p->name) != 0) && (strncmp(p->name, board_name, sizeof(board_name)) != 0)) |
238 | p++; |
239 | if (p->name == NULL) { |
240 | printf("BUG! Unable to find default fw layout!\n"); |
241 | exit(-1); |
242 | } |
243 | |
244 | printf("board = %s\n", p->name); |
245 | strcpy(kernel->partition_name, "kernel"); |
246 | kernel->partition_index = 1; |
247 | kernel->partition_baseaddr = p->kern_start; |
248 | if ( (kernel->partition_length = filelength(kernelfile)) < 0) return (-1); |
249 | kernel->partition_memaddr = p->kern_entry; |
250 | kernel->partition_entryaddr = p->kern_entry; |
251 | strncpy(kernel->filename, kernelfile, sizeof(kernel->filename)); |
252 | |
253 | if (filelength(rootfsfile) + kernel->partition_length > p->firmware_max_length) |
254 | return (-2); |
255 | |
256 | strcpy(rootfs->partition_name, "rootfs"); |
257 | rootfs->partition_index = 2; |
258 | rootfs->partition_baseaddr = kernel->partition_baseaddr + kernel->partition_length; |
259 | rootfs->partition_length = p->firmware_max_length - kernel->partition_length; |
260 | rootfs->partition_memaddr = 0x00000000; |
261 | rootfs->partition_entryaddr = 0x00000000; |
262 | strncpy(rootfs->filename, rootfsfile, sizeof(rootfs->filename)); |
263 | |
264 | printf("kernel: %d 0x%08x\n", kernel->partition_length, kernel->partition_baseaddr); |
265 | printf("root: %d 0x%08x\n", rootfs->partition_length, rootfs->partition_baseaddr); |
266 | im->part_count = 2; |
267 | |
268 | return 0; |
269 | } |
270 | |
271 | /** |
272 | * Checks the availability and validity of all image components. |
273 | * Fills in stats member of the part_data structure. |
274 | */ |
275 | static int validate_image_layout(image_info_t* im) |
276 | { |
277 | int i; |
278 | |
279 | if (im->part_count == 0 || im->part_count > MAX_SECTIONS) |
280 | { |
281 | ERROR("Invalid part count '%d'\n", im->part_count); |
282 | return -1; |
283 | } |
284 | |
285 | for (i = 0; i < im->part_count; ++i) |
286 | { |
287 | part_data_t* d = &im->parts[i]; |
288 | int len = strlen(d->partition_name); |
289 | if (len == 0 || len > 16) |
290 | { |
291 | ERROR("Invalid partition name '%s' of the part %d\n", |
292 | d->partition_name, i); |
293 | return -1; |
294 | } |
295 | if (stat(d->filename, &d->stats) < 0) |
296 | { |
297 | ERROR("Couldn't stat file '%s' from part '%s'\n", |
298 | d->filename, d->partition_name); |
299 | return -2; |
300 | } |
301 | if (d->stats.st_size == 0) |
302 | { |
303 | ERROR("File '%s' from part '%s' is empty!\n", |
304 | d->filename, d->partition_name); |
305 | return -3; |
306 | } |
307 | if (d->stats.st_size > d->partition_length) { |
308 | ERROR("File '%s' too big (%d) - max size: 0x%08X (exceeds %lu bytes)\n", |
309 | d->filename, i, d->partition_length, |
310 | d->stats.st_size - d->partition_length); |
311 | return -4; |
312 | } |
313 | } |
314 | |
315 | return 0; |
316 | } |
317 | |
318 | static int build_image(image_info_t* im) |
319 | { |
320 | char* mem; |
321 | char* ptr; |
322 | u_int32_t mem_size; |
323 | FILE* f; |
324 | int i; |
325 | |
326 | // build in-memory buffer |
327 | mem_size = sizeof(header_t) + sizeof(signature_t); |
328 | for (i = 0; i < im->part_count; ++i) |
329 | { |
330 | part_data_t* d = &im->parts[i]; |
331 | mem_size += sizeof(part_t) + d->stats.st_size + sizeof(part_crc_t); |
332 | } |
333 | |
334 | mem = (char*)calloc(mem_size, 1); |
335 | if (mem == NULL) |
336 | { |
337 | ERROR("Cannot allocate memory chunk of size '%u'\n", mem_size); |
338 | return -1; |
339 | } |
340 | |
341 | // write header |
342 | write_header(mem, im->version); |
343 | ptr = mem + sizeof(header_t); |
344 | // write all parts |
345 | for (i = 0; i < im->part_count; ++i) |
346 | { |
347 | part_data_t* d = &im->parts[i]; |
348 | int rc; |
349 | if ((rc = write_part(ptr, d)) != 0) |
350 | { |
351 | ERROR("ERROR: failed writing part %u '%s'\n", i, d->partition_name); |
352 | } |
353 | ptr += sizeof(part_t) + d->stats.st_size + sizeof(part_crc_t); |
354 | } |
355 | // write signature |
356 | write_signature(mem, mem_size - sizeof(signature_t)); |
357 | |
358 | // write in-memory buffer into file |
359 | if ((f = fopen(im->outputfile, "w")) == NULL) |
360 | { |
361 | ERROR("Can not create output file: '%s'\n", im->outputfile); |
362 | return -10; |
363 | } |
364 | |
365 | if (fwrite(mem, mem_size, 1, f) != 1) |
366 | { |
367 | ERROR("Could not write %d bytes into file: '%s'\n", |
368 | mem_size, im->outputfile); |
369 | return -11; |
370 | } |
371 | |
372 | free(mem); |
373 | fclose(f); |
374 | return 0; |
375 | } |
376 | |
377 | |
378 | int main(int argc, char* argv[]) |
379 | { |
380 | char kernelfile[PATH_MAX]; |
381 | char rootfsfile[PATH_MAX]; |
382 | char board_name[PATH_MAX]; |
383 | int o, rc; |
384 | image_info_t im; |
385 | |
386 | memset(&im, 0, sizeof(im)); |
387 | memset(kernelfile, 0, sizeof(kernelfile)); |
388 | memset(rootfsfile, 0, sizeof(rootfsfile)); |
389 | memset(board_name, 0, sizeof(board_name)); |
390 | |
391 | strcpy(im.outputfile, DEFAULT_OUTPUT_FILE); |
392 | strcpy(im.version, DEFAULT_VERSION); |
393 | |
394 | while ((o = getopt(argc, argv, OPTIONS)) != -1) |
395 | { |
396 | switch (o) { |
397 | case 'v': |
398 | if (optarg) |
399 | strncpy(im.version, optarg, sizeof(im.version)); |
400 | break; |
401 | case 'o': |
402 | if (optarg) |
403 | strncpy(im.outputfile, optarg, sizeof(im.outputfile)); |
404 | break; |
405 | case 'h': |
406 | usage(argv[0]); |
407 | return -1; |
408 | case 'k': |
409 | if (optarg) |
410 | strncpy(kernelfile, optarg, sizeof(kernelfile)); |
411 | break; |
412 | case 'r': |
413 | if (optarg) |
414 | strncpy(rootfsfile, optarg, sizeof(rootfsfile)); |
415 | break; |
416 | case 'B': |
417 | if (optarg) |
418 | strncpy(board_name, optarg, sizeof(board_name)); |
419 | break; |
420 | } |
421 | } |
422 | if (strlen(board_name) == 0) |
423 | strcpy(board_name, "XS2"); /* default to XS2 */ |
424 | |
425 | if (strlen(kernelfile) == 0) |
426 | { |
427 | ERROR("Kernel file is not specified, cannot continue\n"); |
428 | usage(argv[0]); |
429 | return -2; |
430 | } |
431 | |
432 | if (strlen(rootfsfile) == 0) |
433 | { |
434 | ERROR("Root FS file is not specified, cannot continue\n"); |
435 | usage(argv[0]); |
436 | return -2; |
437 | } |
438 | |
439 | if ((rc = create_image_layout(kernelfile, rootfsfile, board_name, &im)) != 0) |
440 | { |
441 | ERROR("Failed creating firmware layout description - error code: %d\n", rc); |
442 | return -3; |
443 | } |
444 | |
445 | if ((rc = validate_image_layout(&im)) != 0) |
446 | { |
447 | ERROR("Failed validating firmware layout - error code: %d\n", rc); |
448 | return -4; |
449 | } |
450 | |
451 | print_image_info(&im); |
452 | |
453 | if ((rc = build_image(&im)) != 0) |
454 | { |
455 | ERROR("Failed building image file '%s' - error code: %d\n", im.outputfile, rc); |
456 | return -5; |
457 | } |
458 | |
459 | return 0; |
460 | } |
461 | |