2 * firmware cutter for broadcom 43xx wireless driver files
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>
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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.
39 #include <sys/types.h>
42 #include <sys/endian.h>
49 #include "fwcutter_list.h"
52 #define V3_FW_DIRNAME "v3"
53 #define V4_FW_DIRNAME "v4"
55 #define V3_FW_DIRNAME "b43legacy"
56 #define V4_FW_DIRNAME "b43"
59 static struct cmdline_args cmdargs;
62 /* check whether file will be listed/extracted from */
63 static int file_ok(const struct file *f)
65 return !(f->flags & FW_FLAG_UNSUPPORTED) || cmdargs.unsupported;
68 /* Convert a CPU-endian 16bit integer to Big-Endian */
69 static be16_t to_be16(uint16_t v)
73 ret[0] = (v & 0xFF00) >> 8;
74 ret[1] = (v & 0x00FF);
76 return *((be16_t *)ret);
80 /* Convert a Big-Endian 16bit integer to CPU-endian */
81 static uint16_t from_be16(be16_t v)
85 ret |= (uint16_t)(((uint8_t *)&v)[0]) << 8;
86 ret |= (uint16_t)(((uint8_t *)&v)[1]);
92 /* Convert a CPU-endian 32bit integer to Big-Endian */
93 static be32_t to_be32(uint32_t v)
97 ret[0] = (v & 0xFF000000) >> 24;
98 ret[1] = (v & 0x00FF0000) >> 16;
99 ret[2] = (v & 0x0000FF00) >> 8;
100 ret[3] = (v & 0x000000FF);
102 return *((be32_t *)ret);
105 /* Convert a Big-Endian 32bit integer to CPU-endian */
106 static uint32_t from_be32(be32_t v)
110 ret |= (uint32_t)(((uint8_t *)&v)[0]) << 24;
111 ret |= (uint32_t)(((uint8_t *)&v)[1]) << 16;
112 ret |= (uint32_t)(((uint8_t *)&v)[2]) << 8;
113 ret |= (uint32_t)(((uint8_t *)&v)[3]);
118 /* tiny disassembler */
119 static void print_ucode_version(struct insn *insn)
124 * The instruction we're looking for is a store to memory
125 * offset insn->op3 of the constant formed like `val' below.
126 * 0x2de00 is the opcode for type 1, 0x378 is the opcode
129 if (insn->opcode != 0x378 && insn->opcode != 0x2de00)
132 val = ((0xFF & insn->op1) << 8) | (0xFF & insn->op2);
135 * Memory offsets are word-offsets, for the meaning
136 * see http://bcm-v4.sipsolutions.net/802.11/ObjectMemory
140 printf(" ucode version: %d\n", val);
143 printf(" ucode revision: %d\n", val);
146 printf(" ucode date: %.4d-%.2d-%.2d\n",
147 2000 + (val >> 12), (val >> 8) & 0xF, val & 0xFF);
150 printf(" ucode time: %.2d:%.2d:%.2d\n",
151 val >> 11, (val >> 5) & 0x3f, val & 0x1f);
156 static void disasm_ucode_1(uint64_t in, struct insn *out)
158 /* xxyyyzzz00oooooX -> ooooo Xxx yyy zzz
159 * if we swap the upper and lower 32-bits first it becomes easier:
160 * 00oooooxxxyyyzzz -> ooooo xxx yyy zzz
162 in = (in >> 32) | (in << 32);
164 out->op3 = in & 0xFFF;
165 out->op2 = (in >> 12) & 0xFFF;
166 out->op1 = (in >> 24) & 0xFFF;
167 out->opcode = (in >> 36) & 0xFFFFF;
168 /* the rest of the in word should be zero */
171 static void disasm_ucode_2(uint64_t in, struct insn *out)
173 /* xxyyyzzz0000oooX -> ooo Xxx yyy zzz
174 * if we swap the upper and lower 32-bits first it becomes easier:
175 * 0000oooxxxyyyzzz -> ooo xxx yyy zzz
177 in = (in >> 32) | (in << 32);
179 out->op3 = in & 0xFFF;
180 out->op2 = (in >> 12) & 0xFFF;
181 out->op1 = (in >> 24) & 0xFFF;
182 out->opcode = (in >> 36) & 0xFFF;
183 /* the rest of the in word should be zero */
186 static void disasm_ucode_3(uint64_t in, struct insn *out)
189 * like 2, but each operand has one bit more; appears
190 * to use the same instruction set slightly extended
192 in = (in >> 32) | (in << 32);
194 out->op3 = in & 0x1FFF;
195 out->op2 = (in >> 13) & 0x1FFF;
196 out->op1 = (in >> 26) & 0x1FFF;
197 out->opcode = (in >> 39) & 0xFFF;
198 /* the rest of the in word should be zero */
201 static void analyse_ucode(int ucode_rev, uint8_t *data, uint32_t len)
203 uint64_t *insns = (uint64_t*)data;
207 for (i=0; i<len/sizeof(*insns); i++) {
210 disasm_ucode_1(insns[i], &insn);
211 print_ucode_version(&insn);
214 disasm_ucode_2(insns[i], &insn);
215 print_ucode_version(&insn);
218 disasm_ucode_3(insns[i], &insn);
219 print_ucode_version(&insn);
225 static void swap_endianness_ucode(uint8_t *buf, uint32_t len)
227 uint32_t *buf32 = (uint32_t*)buf;
230 for (i=0; i<len/4; i++)
231 buf32[i] = bswap_32(buf32[i]);
234 #define swap_endianness_pcm swap_endianness_ucode
236 static void swap_endianness_iv(struct iv *iv)
238 iv->reg = bswap_16(iv->reg);
239 iv->size = bswap_16(iv->size);
240 iv->val = bswap_32(iv->val);
243 static void build_ivs(struct b43_iv **_out, size_t *_out_size,
244 struct iv *in, size_t in_size,
245 struct fw_header *hdr,
253 if (sizeof(struct b43_iv) != 6) {
254 printf("sizeof(struct b43_iv) != 6\n");
258 out = malloc(in_size);
260 perror("failed to allocate buffer");
264 for (i = 0; i < in_size / sizeof(*iv); i++, in++) {
265 if (flags & FW_FLAG_LE)
266 swap_endianness_iv(in);
267 /* input-IV is BigEndian */
268 if (in->reg & to_be16(~FW_IV_OFFSET_MASK)) {
269 printf("Input file IV offset > 0x%X\n", FW_IV_OFFSET_MASK);
272 out->offset_size = in->reg;
273 if (in->size == to_be16(4)) {
274 out->offset_size |= to_be16(FW_IV_32BIT);
275 out->data.d32 = in->val;
277 out_size += sizeof(be16_t) + sizeof(be32_t);
278 out = (struct b43_iv *)((uint8_t *)out + sizeof(be16_t) + sizeof(be32_t));
279 } else if (in->size == to_be16(2)) {
280 if (in->val & to_be32(~0xFFFF)) {
281 printf("Input file 16bit IV value overflow\n");
284 out->data.d16 = to_be16(from_be32(in->val));
286 out_size += sizeof(be16_t) + sizeof(be16_t);
287 out = (struct b43_iv *)((uint8_t *)out + sizeof(be16_t) + sizeof(be16_t));
289 printf("Input file IV size != 2|4\n");
293 hdr->size = to_be32(i);
294 *_out_size = out_size;
297 static void write_file(const char *name, uint8_t *buf, uint32_t len,
298 const struct fw_header *hdr, uint32_t flags)
305 if (flags & FW_FLAG_V4)
310 r = snprintf(nbuf, sizeof(nbuf),
311 "%s/%s", cmdargs.target_dir, dir);
312 if (r >= sizeof(nbuf)) {
313 fprintf(stderr, "name too long");
317 r = mkdir(nbuf, 0770);
318 if (r && errno != EEXIST) {
319 perror("failed to create output directory");
323 r = snprintf(nbuf, sizeof(nbuf),
324 "%s/%s/%s.fw", cmdargs.target_dir, dir, name);
325 if (r >= sizeof(nbuf)) {
326 fprintf(stderr, "name too long");
329 f = fopen(nbuf, "w");
331 perror("failed to open file");
334 if (fwrite(hdr, sizeof(*hdr), 1, f) != 1) {
335 perror("failed to write file");
338 if (fwrite(buf, 1, len, f) != len) {
339 perror("failed to write file");
345 static void extract_or_identify(FILE *f, const struct extract *extract,
351 struct fw_header hdr;
353 memset(&hdr, 0, sizeof(hdr));
354 hdr.ver = FW_HDR_VER;
356 if (fseek(f, extract->offset, SEEK_SET)) {
357 perror("failed to seek on file");
361 buf = malloc(extract->length);
363 perror("failed to allocate buffer");
366 if (fread(buf, 1, extract->length, f) != extract->length) {
367 perror("failed to read complete data");
371 switch (extract->type) {
378 data_length = extract->length;
379 if (flags & FW_FLAG_LE)
380 swap_endianness_ucode(buf, data_length);
381 analyse_ucode(ucode_rev, buf, data_length);
382 hdr.type = FW_TYPE_UCODE;
383 hdr.size = to_be32(data_length);
386 data_length = extract->length;
387 if (flags & FW_FLAG_LE)
388 swap_endianness_pcm(buf, data_length);
389 hdr.type = FW_TYPE_PCM;
390 hdr.size = to_be32(data_length);
395 hdr.type = FW_TYPE_IV;
396 build_ivs(&ivs, &data_length,
397 (struct iv *)buf, extract->length,
400 buf = (uint8_t *)ivs;
407 if (cmdargs.mode == FWCM_EXTRACT)
408 write_file(extract->name, buf, data_length, &hdr, flags);
413 static void print_banner(void)
415 printf("b43-fwcutter version " FWCUTTER_VERSION "\n");
418 static void print_file(const struct file *file)
423 if (file->flags & FW_FLAG_V4)
424 printf(V4_FW_DIRNAME "\t\t");
426 printf(V3_FW_DIRNAME "\t");
428 if (strlen(file->name) > 20) {
429 strncpy(shortname, file->name, 20);
430 shortname[20] = '\0';
431 snprintf(filename, sizeof(filename), "%s..", shortname);
433 strcpy (filename, file->name);
435 printf("%s\t", filename);
436 if (strlen(filename) < 8) printf("\t");
437 if (strlen(filename) < 16) printf("\t");
439 printf("%s\t", file->ucode_version);
440 if (strlen(file->ucode_version) < 8) printf("\t");
442 printf("%s\t", file->id);
444 printf("%s\n", file->md5);
447 static void print_supported_files(void)
452 printf("\nExtracting firmware is possible "
453 "from these binary driver files.\n"
454 "The <ID> column shows the unique identifier string "
455 "for your firmware.\nYou must select the firmware with the "
456 "same ID as printed by the kernel driver on modprobe.\n"
457 "Note that only recent drivers print such a message on modprobe.\n"
458 "Please read http://linuxwireless.org/en/users/Drivers/b43#devicefirmware\n\n");
463 "<MD5 checksum>\n\n");
464 /* print for legacy driver first */
465 for (i = 0; i < ARRAY_SIZE(files); i++)
466 if (file_ok(&files[i]) && !(files[i].flags & FW_FLAG_V4))
467 print_file(&files[i]);
468 for (i = 0; i < ARRAY_SIZE(files); i++)
469 if (file_ok(&files[i]) && files[i].flags & FW_FLAG_V4)
470 print_file(&files[i]);
474 static const struct file *find_file(FILE *fd)
476 unsigned char buffer[16384], signature[16];
477 struct MD5Context md5c;
482 while ((i = (int) fread(buffer, 1, sizeof(buffer), fd)) > 0)
483 MD5Update(&md5c, buffer, (unsigned) i);
484 MD5Final(signature, &md5c);
486 snprintf(md5sig, sizeof(md5sig),
487 "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x"
488 "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x",
489 signature[0], signature[1], signature[2], signature[3],
490 signature[4], signature[5], signature[6], signature[7],
491 signature[8], signature[9], signature[10], signature[11],
492 signature[12], signature[13], signature[14], signature[15]);
494 for (i = 0; i < ARRAY_SIZE(files); i++) {
495 if (file_ok(&files[i]) &&
496 strcasecmp(md5sig, files[i].md5) == 0) {
497 printf("This file is recognised as:\n");
498 printf(" ID : %s\n", files[i].id);
499 printf(" filename : %s\n", files[i].name);
500 printf(" version : %s\n", files[i].ucode_version);
501 printf(" MD5 : %s\n", files[i].md5);
505 printf("Sorry, the input file is either wrong or "
506 "not supported by b43-fwcutter.\n");
507 printf("This file has an unknown MD5sum %s.\n", md5sig);
512 static void print_usage(int argc, char *argv[])
515 printf("\nA tool to extract firmware for a Broadcom 43xx device\n");
516 printf("from a proprietary Broadcom 43xx device driver file.\n");
517 printf("\nUsage: %s [OPTION] [proprietary-driver-file]\n", argv[0]);
518 printf(" --unsupported "
519 "Allow working on extractable but unsupported drivers\n");
521 "List supported driver versions\n");
522 printf(" -i|--identify "
523 "Only identify the driver file (don't extract)\n");
524 printf(" -w|--target-dir DIR "
525 "Extract and write firmware to DIR\n");
526 printf(" -v|--version "
527 "Print b43-fwcutter version\n");
529 "Print this help\n");
530 printf("\nExample: %s -w /lib/firmware wl_apsta.o\n"
531 " to extract the firmware blobs from wl_apsta.o and store\n"
532 " the resulting firmware in /lib/firmware\n",
536 static int do_cmp_arg(char **argv, int *pos,
537 const char *template,
543 size_t arg_len, template_len;
546 next_arg = argv[*pos + 1];
547 arg_len = strlen(arg);
548 template_len = strlen(template);
551 /* Maybe we have a merged parameter here.
552 * A merged parameter is "-pfoobar" for example.
554 if (allow_merged && arg_len > template_len) {
555 if (memcmp(arg, template, template_len) == 0) {
556 *param = arg + template_len;
560 } else if (arg_len != template_len)
564 if (strcmp(arg, template) == 0) {
566 /* Skip the parameter on the next iteration. */
569 printf("%s needs a parameter\n", arg);
579 /* Simple and lean command line argument parsing. */
580 static int cmp_arg(char **argv, int *pos,
581 const char *long_template,
582 const char *short_template,
588 err = do_cmp_arg(argv, pos, long_template, 0, param);
589 if (err == ARG_MATCH || err == ARG_ERROR)
594 err = do_cmp_arg(argv, pos, short_template, 1, param);
598 static int parse_args(int argc, char *argv[])
605 for (i = 1; i < argc; i++) {
606 res = cmp_arg(argv, &i, "--list", "-l", NULL);
607 if (res == ARG_MATCH) {
608 cmdargs.mode = FWCM_LIST;
610 } else if (res == ARG_ERROR)
613 res = cmp_arg(argv, &i, "--version", "-v", NULL);
614 if (res == ARG_MATCH) {
617 } else if (res == ARG_ERROR)
620 res = cmp_arg(argv, &i, "--help", "-h", NULL);
621 if (res == ARG_MATCH)
623 else if (res == ARG_ERROR)
626 res = cmp_arg(argv, &i, "--identify", "-i", NULL);
627 if (res == ARG_MATCH) {
628 cmdargs.mode = FWCM_IDENTIFY;
630 } else if (res == ARG_ERROR)
633 res = cmp_arg(argv, &i, "--unsupported", NULL, NULL);
634 if (res == ARG_MATCH) {
635 cmdargs.unsupported = 1;
637 } else if (res == ARG_ERROR)
640 res = cmp_arg(argv, &i, "--target-dir", "-w", ¶m);
641 if (res == ARG_MATCH) {
642 cmdargs.target_dir = param;
644 } else if (res == ARG_ERROR)
647 cmdargs.infile = argv[i];
651 if (!cmdargs.infile && cmdargs.mode != FWCM_LIST)
656 print_usage(argc, argv);
661 int main(int argc, char *argv[])
664 const struct file *file;
665 const struct extract *extract;
669 cmdargs.target_dir = ".";
670 err = parse_args(argc, argv);
676 if (cmdargs.mode == FWCM_LIST) {
677 print_supported_files();
681 fd = fopen(cmdargs.infile, "rb");
683 fprintf(stderr, "Cannot open input file %s\n", cmdargs.infile);
688 file = find_file(fd);
692 if (file->flags & FW_FLAG_V4)
697 extract = file->extract;
698 while (extract->name) {
699 printf("%s %s/%s.fw\n",
700 cmdargs.mode == FWCM_IDENTIFY ? "Contains" : "Extracting",
702 extract_or_identify(fd, extract, file->flags);