From: grarpamp <grarpamp@gmail.com>
[b43-tools.git] / fwcutter / fwcutter.c
1 /*
2  * firmware cutter for broadcom 43xx wireless driver files
3  * 
4  * Copyright (c) 2005 Martin Langer <martin-langer@gmx.de>,
5  *               2005-2007 Michael Buesch <mb@bu3sch.de>
6  *               2005 Alex Beregszaszi
7  *               2007 Johannes Berg <johannes@sipsolutions.net>
8  *
9  *   Redistribution and use in source and binary forms, with or without
10  *   modification, are permitted provided that the following conditions
11  *   are met:
12  *
13  *     1. Redistributions of source code must retain the above copyright
14  *        notice, this list of conditions and the following disclaimer.
15  *     2. Redistributions in binary form must reproduce the above
16  *        copyright notice, this list of conditions and the following
17  *        disclaimer in the documentation and/or other materials provided
18  *        with the distribution.
19  *
20  *   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
21  *   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  *   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23  *   DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
24  *   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  *   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
26  *   GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  *   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28  *   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29  *   OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
30  *   EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include <stdlib.h>
34 #include <ctype.h>
35 #include <string.h>
36 #include <stdio.h>
37 #include <errno.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40
41 #if defined(__DragonFly__) || defined(__FreeBSD__)
42 #include <sys/endian.h>
43 #else
44 #include <byteswap.h>
45 #endif
46
47 #include "md5.h"
48 #include "fwcutter.h"
49 #include "fwcutter_list.h"
50
51 #if defined(__DragonFly__) || defined(__FreeBSD__)
52 #define V3_FW_DIRNAME   "v3"
53 #define V4_FW_DIRNAME   "v4"
54 #else
55 #define V3_FW_DIRNAME   "b43legacy"
56 #define V4_FW_DIRNAME   "b43"
57 #endif
58
59 static struct cmdline_args cmdargs;
60
61
62 /* check whether file will be listed/extracted from */
63 static int file_ok(const struct file *f)
64 {
65         return !(f->flags & FW_FLAG_UNSUPPORTED) || cmdargs.unsupported;
66 }
67
68 /* Convert a Big-Endian 16bit integer to CPU-endian */
69 static uint16_t from_be16(be16_t v)
70 {
71         uint16_t ret = 0;
72
73         ret |= (uint16_t)(((uint8_t *)&v)[0]) << 8;
74         ret |= (uint16_t)(((uint8_t *)&v)[1]);
75
76         return ret;
77 }
78
79 /* Convert a CPU-endian 16bit integer to Big-Endian */
80 static be16_t to_be16(uint16_t v)
81 {
82         return (be16_t)from_be16((be16_t)v);
83 }
84
85 /* Convert a Big-Endian 32bit integer to CPU-endian */
86 static uint32_t from_be32(be32_t v)
87 {
88         uint32_t ret = 0;
89
90         ret |= (uint32_t)(((uint8_t *)&v)[0]) << 24;
91         ret |= (uint32_t)(((uint8_t *)&v)[1]) << 16;
92         ret |= (uint32_t)(((uint8_t *)&v)[2]) << 8;
93         ret |= (uint32_t)(((uint8_t *)&v)[3]);
94
95         return ret;
96 }
97
98 /* Convert a CPU-endian 32bit integer to Big-Endian */
99 static be32_t to_be32(uint32_t v)
100 {
101         return (be32_t)from_be32((be32_t)v);
102 }
103
104 /* tiny disassembler */
105 static void print_ucode_version(struct insn *insn)
106 {
107         int val;
108
109         /*
110          * The instruction we're looking for is a store to memory
111          * offset insn->op3 of the constant formed like `val' below.
112          * 0x2de00 is the opcode for type 1, 0x378 is the opcode
113          * for type 2 and 3.
114          */
115         if (insn->opcode != 0x378 && insn->opcode != 0x2de00)
116                 return;
117
118         val = ((0xFF & insn->op1) << 8) | (0xFF & insn->op2);
119
120         /*
121          * Memory offsets are word-offsets, for the meaning
122          * see http://bcm-v4.sipsolutions.net/802.11/ObjectMemory
123          */
124         switch (insn->op3) {
125         case 0:
126                 printf("  ucode version:  %d\n", val);
127                 break;
128         case 1:
129                 printf("  ucode revision: %d\n", val);
130                 break;
131         case 2:
132                 printf("  ucode date:     %.4d-%.2d-%.2d\n",
133                        2000 + (val >> 12), (val >> 8) & 0xF, val & 0xFF);
134                 break;
135         case 3:
136                 printf("  ucode time:     %.2d:%.2d:%.2d\n",
137                        val >> 11, (val >> 5) & 0x3f, val & 0x1f);
138                 break;
139         }
140 }
141
142 static void disasm_ucode_1(uint64_t in, struct insn *out)
143 {
144         /* xxyyyzzz00oooooX -> ooooo Xxx yyy zzz
145          * if we swap the upper and lower 32-bits first it becomes easier:
146          * 00oooooxxxyyyzzz -> ooooo xxx yyy zzz
147          */
148         in = (in >> 32) | (in << 32);
149
150         out->op3        = in & 0xFFF;
151         out->op2        = (in >> 12) & 0xFFF;
152         out->op1        = (in >> 24) & 0xFFF;
153         out->opcode     = (in >> 36) & 0xFFFFF;
154         /* the rest of the in word should be zero */
155 }
156
157 static void disasm_ucode_2(uint64_t in, struct insn *out)
158 {
159         /* xxyyyzzz0000oooX -> ooo Xxx yyy zzz
160          * if we swap the upper and lower 32-bits first it becomes easier:
161          * 0000oooxxxyyyzzz -> ooo xxx yyy zzz
162          */
163         in = (in >> 32) | (in << 32);
164
165         out->op3        = in & 0xFFF;
166         out->op2        = (in >> 12) & 0xFFF;
167         out->op1        = (in >> 24) & 0xFFF;
168         out->opcode     = (in >> 36) & 0xFFF;
169         /* the rest of the in word should be zero */
170 }
171
172 static void disasm_ucode_3(uint64_t in, struct insn *out)
173 {
174         /*
175          * like 2, but each operand has one bit more; appears
176          * to use the same instruction set slightly extended
177          */
178         in = (in >> 32) | (in << 32);
179
180         out->op3        = in & 0x1FFF;
181         out->op2        = (in >> 13) & 0x1FFF;
182         out->op1        = (in >> 26) & 0x1FFF;
183         out->opcode     = (in >> 39) & 0xFFF;
184         /* the rest of the in word should be zero */
185 }
186
187 static void analyse_ucode(int ucode_rev, uint8_t *data, uint32_t len)
188 {
189         uint64_t *insns = (uint64_t*)data;
190         struct insn insn;
191         uint32_t i;
192
193         for (i=0; i<len/sizeof(*insns); i++) {
194                 switch (ucode_rev) {
195                 case 1:
196                         disasm_ucode_1(insns[i], &insn);
197                         print_ucode_version(&insn);
198                         break;
199                 case 2:
200                         disasm_ucode_2(insns[i], &insn);
201                         print_ucode_version(&insn);
202                         break;
203                 case 3:
204                         disasm_ucode_3(insns[i], &insn);
205                         print_ucode_version(&insn);
206                         break;
207                 }
208         }
209 }
210
211 static void swap_endianness_ucode(uint8_t *buf, uint32_t len)
212 {
213         uint32_t *buf32 = (uint32_t*)buf;
214         uint32_t i;
215
216         for (i=0; i<len/4; i++)
217                 buf32[i] = bswap_32(buf32[i]);
218 }
219
220 #define swap_endianness_pcm swap_endianness_ucode
221
222 static void swap_endianness_iv(struct iv *iv)
223 {
224         iv->reg = bswap_16(iv->reg);
225         iv->size = bswap_16(iv->size);
226         iv->val = bswap_32(iv->val);
227 }
228
229 static void build_ivs(struct b43_iv **_out, size_t *_out_size,
230                       struct iv *in, size_t in_size,
231                       struct fw_header *hdr,
232                       uint32_t flags)
233 {
234         struct iv *iv;
235         struct b43_iv *out;
236         uint32_t i;
237         size_t out_size = 0;
238
239         if (sizeof(struct b43_iv) != 6) {
240                 printf("sizeof(struct b43_iv) != 6\n");
241                 exit(255);
242         }
243
244         out = malloc(in_size);
245         if (!out) {
246                 perror("failed to allocate buffer");
247                 exit(1);
248         }
249         *_out = out;
250         for (i = 0; i < in_size / sizeof(*iv); i++, in++) {
251                 if (flags & FW_FLAG_LE)
252                         swap_endianness_iv(in);
253                 /* input-IV is BigEndian */
254                 if (in->reg & to_be16(~FW_IV_OFFSET_MASK)) {
255                         printf("Input file IV offset > 0x%X\n", FW_IV_OFFSET_MASK);
256                         exit(1);
257                 }
258                 out->offset_size = in->reg;
259                 if (in->size == to_be16(4)) {
260                         out->offset_size |= to_be16(FW_IV_32BIT);
261                         out->data.d32 = in->val;
262
263                         out_size += sizeof(be16_t) + sizeof(be32_t);
264                         out = (struct b43_iv *)((uint8_t *)out + sizeof(be16_t) + sizeof(be32_t));
265                 } else if (in->size == to_be16(2)) {
266                         if (in->val & to_be32(~0xFFFF)) {
267                                 printf("Input file 16bit IV value overflow\n");
268                                 exit(1);
269                         }
270                         out->data.d16 = to_be16(from_be32(in->val));
271
272                         out_size += sizeof(be16_t) + sizeof(be16_t);
273                         out = (struct b43_iv *)((uint8_t *)out + sizeof(be16_t) + sizeof(be16_t));
274                 } else {
275                         printf("Input file IV size != 2|4\n");
276                         exit(1);
277                 }
278         }
279         hdr->size = to_be32(i);
280         *_out_size = out_size;
281 }
282
283 static void write_file(const char *name, uint8_t *buf, uint32_t len,
284                        const struct fw_header *hdr, uint32_t flags)
285 {
286         FILE *f;
287         char nbuf[4096];
288         const char *dir;
289         int r;
290
291         if (flags & FW_FLAG_V4)
292                 dir = V4_FW_DIRNAME;
293         else
294                 dir = V3_FW_DIRNAME;
295
296         r = snprintf(nbuf, sizeof(nbuf),
297                      "%s/%s", cmdargs.target_dir, dir);
298         if (r >= sizeof(nbuf)) {
299                 fprintf(stderr, "name too long");
300                 exit(2);
301         }
302
303         r = mkdir(nbuf, 0770);
304         if (r && errno != EEXIST) {
305                 perror("failed to create output directory");
306                 exit(2);
307         }
308
309         r = snprintf(nbuf, sizeof(nbuf),
310                      "%s/%s/%s.fw", cmdargs.target_dir, dir, name);
311         if (r >= sizeof(nbuf)) {
312                 fprintf(stderr, "name too long");
313                 exit(2);
314         }
315         f = fopen(nbuf, "w");
316         if (!f) {
317                 perror("failed to open file");
318                 exit(2);
319         }
320         if (fwrite(hdr, sizeof(*hdr), 1, f) != 1) {
321                 perror("failed to write file");
322                 exit(2);
323         }
324         if (fwrite(buf, 1, len, f) != len) {
325                 perror("failed to write file");
326                 exit(2);
327         }
328         fclose(f);
329 }
330
331 static void extract_or_identify(FILE *f, const struct extract *extract,
332                                 uint32_t flags)
333 {
334         uint8_t *buf;
335         size_t data_length;
336         int ucode_rev = 0;
337         struct fw_header hdr;
338
339         memset(&hdr, 0, sizeof(hdr));
340         hdr.ver = FW_HDR_VER;
341
342         if (fseek(f, extract->offset, SEEK_SET)) {
343                 perror("failed to seek on file");
344                 exit(2);
345         }
346
347         buf = malloc(extract->length);
348         if (!buf) {
349                 perror("failed to allocate buffer");
350                 exit(3);
351         }
352         if (fread(buf, 1, extract->length, f) != extract->length) {
353                 perror("failed to read complete data");
354                 exit(3);
355         }
356
357         switch (extract->type) {
358         case EXT_UCODE_3:
359                 ucode_rev += 1;
360         case EXT_UCODE_2:
361                 ucode_rev += 1;
362         case EXT_UCODE_1:
363                 ucode_rev += 1;
364                 data_length = extract->length;
365                 if (flags & FW_FLAG_LE)
366                         swap_endianness_ucode(buf, data_length);
367                 analyse_ucode(ucode_rev, buf, data_length);
368                 hdr.type = FW_TYPE_UCODE;
369                 hdr.size = to_be32(data_length);
370                 break;
371         case EXT_PCM:
372                 data_length = extract->length;
373                 if (flags & FW_FLAG_LE)
374                         swap_endianness_pcm(buf, data_length);
375                 hdr.type = FW_TYPE_PCM;
376                 hdr.size = to_be32(data_length);
377                 break;
378         case EXT_IV: {
379                 struct b43_iv *ivs;
380
381                 hdr.type = FW_TYPE_IV;
382                 build_ivs(&ivs, &data_length,
383                           (struct iv *)buf, extract->length,
384                           &hdr, flags);
385                 free(buf);
386                 buf = (uint8_t *)ivs;
387                 break;
388         }
389         default:
390                 exit(255);
391         }
392
393         if (cmdargs.mode == FWCM_EXTRACT)
394                 write_file(extract->name, buf, data_length, &hdr, flags);
395
396         free(buf);
397 }
398
399 static void print_banner(void)
400 {
401         printf("b43-fwcutter version " FWCUTTER_VERSION "\n");
402 }
403
404 static void print_file(const struct file *file)
405 {
406         char filename[30];
407         char shortname[30];
408
409         if (file->flags & FW_FLAG_V4)
410                 printf(V4_FW_DIRNAME "\t\t");
411         else
412                 printf(V3_FW_DIRNAME "\t");
413
414         if (strlen(file->name) > 20) {
415                 strncpy(shortname, file->name, 20);
416                 shortname[20] = '\0';
417                 snprintf(filename, sizeof(filename), "%s..", shortname);
418         } else
419                 strcpy (filename, file->name);
420
421         printf("%s\t", filename);
422         if (strlen(filename) < 8) printf("\t");
423         if (strlen(filename) < 16) printf("\t");
424
425         printf("%s\t", file->ucode_version);
426         if (strlen(file->ucode_version) < 8) printf("\t");
427
428         printf("%s\t", file->id);
429
430         printf("%s\n", file->md5);
431 }
432
433 static void print_supported_files(void)
434 {
435         int i;
436
437         print_banner();
438         printf("\nExtracting firmware is possible "
439                "from these binary driver files.\n"
440                "The <ID> column shows the unique identifier string "
441                "for your firmware.\nYou must select the firmware with the "
442                "same ID as printed by the kernel driver on modprobe.\n"
443                "Note that only recent drivers print such a message on modprobe.\n"
444                "Please read http://linuxwireless.org/en/users/Drivers/b43#devicefirmware\n\n");
445         printf("<driver>\t"
446                "<filename>\t\t"
447                "<microcode>\t"
448                "<ID>\t"
449                "<MD5 checksum>\n\n");
450         /* print for legacy driver first */
451         for (i = 0; i < ARRAY_SIZE(files); i++)
452                 if (file_ok(&files[i]) && !(files[i].flags & FW_FLAG_V4))
453                         print_file(&files[i]);
454         for (i = 0; i < ARRAY_SIZE(files); i++)
455                 if (file_ok(&files[i]) && files[i].flags & FW_FLAG_V4)
456                         print_file(&files[i]);
457         printf("\n");
458 }
459
460 static const struct file *find_file(FILE *fd)
461 {
462         unsigned char buffer[16384], signature[16];
463         struct MD5Context md5c;
464         char md5sig[33];
465         int i;
466
467         MD5Init(&md5c);
468         while ((i = (int) fread(buffer, 1, sizeof(buffer), fd)) > 0)
469                 MD5Update(&md5c, buffer, (unsigned) i);
470         MD5Final(signature, &md5c);
471
472         snprintf(md5sig, sizeof(md5sig),
473                  "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x"
474                  "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x",
475                  signature[0], signature[1], signature[2], signature[3],
476                  signature[4], signature[5], signature[6], signature[7],
477                  signature[8], signature[9], signature[10], signature[11],
478                  signature[12], signature[13], signature[14], signature[15]);
479
480         for (i = 0; i < ARRAY_SIZE(files); i++) {
481                 if (file_ok(&files[i]) &&
482                     strcasecmp(md5sig, files[i].md5) == 0) {
483                         printf("This file is recognised as:\n");
484                         printf("  ID         :  %s\n", files[i].id);
485                         printf("  filename   :  %s\n", files[i].name);
486                         printf("  version    :  %s\n", files[i].ucode_version);
487                         printf("  MD5        :  %s\n", files[i].md5);
488                         return &files[i];
489                 }
490         }
491         printf("Sorry, the input file is either wrong or "
492                "not supported by b43-fwcutter.\n");
493         printf("This file has an unknown MD5sum %s.\n", md5sig);
494
495         return NULL;
496 }
497
498 static void print_usage(int argc, char *argv[])
499 {
500         print_banner();
501         printf("\nA tool to extract firmware for a Broadcom 43xx device\n");
502         printf("from a proprietary Broadcom 43xx device driver file.\n");
503         printf("\nUsage: %s [OPTION] [proprietary-driver-file]\n", argv[0]);
504         printf("  --unsupported         "
505                "Allow working on extractable but unsupported drivers\n");
506         printf("  -l|--list             "
507                "List supported driver versions\n");
508         printf("  -i|--identify         "
509                "Only identify the driver file (don't extract)\n");
510         printf("  -w|--target-dir DIR   "
511                "Extract and write firmware to DIR\n");
512         printf("  -v|--version          "
513                "Print b43-fwcutter version\n");
514         printf("  -h|--help             "
515                "Print this help\n");
516         printf("\nExample: %s -w /lib/firmware wl_apsta.o\n"
517                "         to extract the firmware blobs from wl_apsta.o and store\n"
518                "         the resulting firmware in /lib/firmware\n",
519                argv[0]);
520 }
521
522 static int do_cmp_arg(char **argv, int *pos,
523                       const char *template,
524                       int allow_merged,
525                       char **param)
526 {
527         char *arg;
528         char *next_arg;
529         size_t arg_len, template_len;
530
531         arg = argv[*pos];
532         next_arg = argv[*pos + 1];
533         arg_len = strlen(arg);
534         template_len = strlen(template);
535
536         if (param) {
537                 /* Maybe we have a merged parameter here.
538                  * A merged parameter is "-pfoobar" for example.
539                  */
540                 if (allow_merged && arg_len > template_len) {
541                         if (memcmp(arg, template, template_len) == 0) {
542                                 *param = arg + template_len;
543                                 return ARG_MATCH;
544                         }
545                         return ARG_NOMATCH;
546                 } else if (arg_len != template_len)
547                         return ARG_NOMATCH;
548                 *param = next_arg;
549         }
550         if (strcmp(arg, template) == 0) {
551                 if (param) {
552                         /* Skip the parameter on the next iteration. */
553                         (*pos)++;
554                         if (!*param) {
555                                 printf("%s needs a parameter\n", arg);
556                                 return ARG_ERROR;
557                         }
558                 }
559                 return ARG_MATCH;
560         }
561
562         return ARG_NOMATCH;
563 }
564
565 /* Simple and lean command line argument parsing. */
566 static int cmp_arg(char **argv, int *pos,
567                    const char *long_template,
568                    const char *short_template,
569                    char **param)
570 {
571         int err;
572
573         if (long_template) {
574                 err = do_cmp_arg(argv, pos, long_template, 0, param);
575                 if (err == ARG_MATCH || err == ARG_ERROR)
576                         return err;
577         }
578         err = ARG_NOMATCH;
579         if (short_template)
580                 err = do_cmp_arg(argv, pos, short_template, 1, param);
581         return err;
582 }
583
584 static int parse_args(int argc, char *argv[])
585 {
586         int i, res;
587         char *param;
588
589         if (argc < 2)
590                 goto out_usage;
591         for (i = 1; i < argc; i++) {
592                 res = cmp_arg(argv, &i, "--list", "-l", NULL);
593                 if (res == ARG_MATCH) {
594                         cmdargs.mode = FWCM_LIST;
595                         continue;
596                 } else if (res == ARG_ERROR)
597                         goto out;
598
599                 res = cmp_arg(argv, &i, "--version", "-v", NULL);
600                 if (res == ARG_MATCH) {
601                         print_banner();
602                         return 1;
603                 } else if (res == ARG_ERROR)
604                         goto out;
605
606                 res = cmp_arg(argv, &i, "--help", "-h", NULL);
607                 if (res == ARG_MATCH)
608                         goto out_usage;
609                 else if (res == ARG_ERROR)
610                         goto out;
611
612                 res = cmp_arg(argv, &i, "--identify", "-i", NULL);
613                 if (res == ARG_MATCH) {
614                         cmdargs.mode = FWCM_IDENTIFY;
615                         continue;
616                 } else if (res == ARG_ERROR)
617                         goto out;
618
619                 res = cmp_arg(argv, &i, "--unsupported", NULL, NULL);
620                 if (res == ARG_MATCH) {
621                         cmdargs.unsupported = 1;
622                         continue;
623                 } else if (res == ARG_ERROR)
624                         goto out;
625
626                 res = cmp_arg(argv, &i, "--target-dir", "-w", &param);
627                 if (res == ARG_MATCH) {
628                         cmdargs.target_dir = param;
629                         continue;
630                 } else if (res == ARG_ERROR)
631                         goto out;
632
633                 cmdargs.infile = argv[i];
634                 break;
635         }
636
637         if (!cmdargs.infile && cmdargs.mode != FWCM_LIST)
638                 goto out_usage;
639         return 0;
640
641 out_usage:
642         print_usage(argc, argv);
643 out:
644         return -1;      
645 }
646
647 int main(int argc, char *argv[])
648 {
649         FILE *fd;
650         const struct file *file;
651         const struct extract *extract;
652         int err;
653         const char *dir;
654
655         cmdargs.target_dir = ".";
656         err = parse_args(argc, argv);
657         if (err == 1)
658                 return 0;
659         else if (err != 0)
660                 return err;
661
662         if (cmdargs.mode == FWCM_LIST) {
663                 print_supported_files();
664                 return 0;
665         }
666
667         fd = fopen(cmdargs.infile, "rb");
668         if (!fd) {
669                 fprintf(stderr, "Cannot open input file %s\n", cmdargs.infile);
670                 return 2;
671         }
672
673         err = -1;
674         file = find_file(fd);
675         if (!file)
676                 goto out_close;
677
678         if (file->flags & FW_FLAG_V4)
679                 dir = V4_FW_DIRNAME;
680         else
681                 dir = V3_FW_DIRNAME;
682
683         extract = file->extract;
684         while (extract->name) {
685                 printf("%s %s/%s.fw\n",
686                        cmdargs.mode == FWCM_IDENTIFY ? "Contains" : "Extracting",
687                        dir, extract->name);
688                 extract_or_identify(fd, extract, file->flags);
689                 extract++;
690         }
691
692         err = 0;
693 out_close:
694         fclose(fd);
695
696         return err;
697 }