| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: ISC |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Copyright (c) 2013 Broadcom Corporation |
|---|
| 3 | | - * |
|---|
| 4 | | - * Permission to use, copy, modify, and/or distribute this software for any |
|---|
| 5 | | - * purpose with or without fee is hereby granted, provided that the above |
|---|
| 6 | | - * copyright notice and this permission notice appear in all copies. |
|---|
| 7 | | - * |
|---|
| 8 | | - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|---|
| 9 | | - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|---|
| 10 | | - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
|---|
| 11 | | - * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|---|
| 12 | | - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
|---|
| 13 | | - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|---|
| 14 | | - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|---|
| 15 | 4 | */ |
|---|
| 16 | 5 | |
|---|
| 6 | +#include <linux/efi.h> |
|---|
| 17 | 7 | #include <linux/kernel.h> |
|---|
| 18 | 8 | #include <linux/slab.h> |
|---|
| 19 | 9 | #include <linux/device.h> |
|---|
| .. | .. |
|---|
| 46 | 36 | * @state: current parser state. |
|---|
| 47 | 37 | * @data: input buffer being parsed. |
|---|
| 48 | 38 | * @nvram: output buffer with parse result. |
|---|
| 49 | | - * @nvram_len: lenght of parse result. |
|---|
| 39 | + * @nvram_len: length of parse result. |
|---|
| 50 | 40 | * @line: current line. |
|---|
| 51 | 41 | * @column: current column in line. |
|---|
| 52 | 42 | * @pos: byte offset in input buffer. |
|---|
| .. | .. |
|---|
| 69 | 59 | bool boardrev_found; |
|---|
| 70 | 60 | }; |
|---|
| 71 | 61 | |
|---|
| 72 | | -/** |
|---|
| 62 | +/* |
|---|
| 73 | 63 | * is_nvram_char() - check if char is a valid one for NVRAM entry |
|---|
| 74 | 64 | * |
|---|
| 75 | 65 | * It accepts all printable ASCII chars except for '#' which opens a comment. |
|---|
| .. | .. |
|---|
| 217 | 207 | size = BRCMF_FW_MAX_NVRAM_SIZE; |
|---|
| 218 | 208 | else |
|---|
| 219 | 209 | size = data_len; |
|---|
| 210 | + /* Add space for properties we may add */ |
|---|
| 211 | + size += strlen(BRCMF_FW_DEFAULT_BOARDREV) + 1; |
|---|
| 220 | 212 | /* Alloc for extra 0 byte + roundup by 4 + length field */ |
|---|
| 221 | 213 | size += 1 + 3 + sizeof(u32); |
|---|
| 222 | 214 | nvp->nvram = kzalloc(size, GFP_KERNEL); |
|---|
| .. | .. |
|---|
| 445 | 437 | |
|---|
| 446 | 438 | static void brcmf_fw_request_done(const struct firmware *fw, void *ctx); |
|---|
| 447 | 439 | |
|---|
| 440 | +#ifdef CONFIG_EFI |
|---|
| 441 | +/* In some cases the EFI-var stored nvram contains "ccode=ALL" or "ccode=XV" |
|---|
| 442 | + * to specify "worldwide" compatible settings, but these 2 ccode-s do not work |
|---|
| 443 | + * properly. "ccode=ALL" causes channels 12 and 13 to not be available, |
|---|
| 444 | + * "ccode=XV" causes all 5GHz channels to not be available. So we replace both |
|---|
| 445 | + * with "ccode=X2" which allows channels 12+13 and 5Ghz channels in |
|---|
| 446 | + * no-Initiate-Radiation mode. This means that we will never send on these |
|---|
| 447 | + * channels without first having received valid wifi traffic on the channel. |
|---|
| 448 | + */ |
|---|
| 449 | +static void brcmf_fw_fix_efi_nvram_ccode(char *data, unsigned long data_len) |
|---|
| 450 | +{ |
|---|
| 451 | + char *ccode; |
|---|
| 452 | + |
|---|
| 453 | + ccode = strnstr((char *)data, "ccode=ALL", data_len); |
|---|
| 454 | + if (!ccode) |
|---|
| 455 | + ccode = strnstr((char *)data, "ccode=XV\r", data_len); |
|---|
| 456 | + if (!ccode) |
|---|
| 457 | + return; |
|---|
| 458 | + |
|---|
| 459 | + ccode[6] = 'X'; |
|---|
| 460 | + ccode[7] = '2'; |
|---|
| 461 | + ccode[8] = '\r'; |
|---|
| 462 | +} |
|---|
| 463 | + |
|---|
| 464 | +static u8 *brcmf_fw_nvram_from_efi(size_t *data_len_ret) |
|---|
| 465 | +{ |
|---|
| 466 | + const u16 name[] = { 'n', 'v', 'r', 'a', 'm', 0 }; |
|---|
| 467 | + struct efivar_entry *nvram_efivar; |
|---|
| 468 | + unsigned long data_len = 0; |
|---|
| 469 | + u8 *data = NULL; |
|---|
| 470 | + int err; |
|---|
| 471 | + |
|---|
| 472 | + nvram_efivar = kzalloc(sizeof(*nvram_efivar), GFP_KERNEL); |
|---|
| 473 | + if (!nvram_efivar) |
|---|
| 474 | + return NULL; |
|---|
| 475 | + |
|---|
| 476 | + memcpy(&nvram_efivar->var.VariableName, name, sizeof(name)); |
|---|
| 477 | + nvram_efivar->var.VendorGuid = EFI_GUID(0x74b00bd9, 0x805a, 0x4d61, |
|---|
| 478 | + 0xb5, 0x1f, 0x43, 0x26, |
|---|
| 479 | + 0x81, 0x23, 0xd1, 0x13); |
|---|
| 480 | + |
|---|
| 481 | + err = efivar_entry_size(nvram_efivar, &data_len); |
|---|
| 482 | + if (err) |
|---|
| 483 | + goto fail; |
|---|
| 484 | + |
|---|
| 485 | + data = kmalloc(data_len, GFP_KERNEL); |
|---|
| 486 | + if (!data) |
|---|
| 487 | + goto fail; |
|---|
| 488 | + |
|---|
| 489 | + err = efivar_entry_get(nvram_efivar, NULL, &data_len, data); |
|---|
| 490 | + if (err) |
|---|
| 491 | + goto fail; |
|---|
| 492 | + |
|---|
| 493 | + brcmf_fw_fix_efi_nvram_ccode(data, data_len); |
|---|
| 494 | + brcmf_info("Using nvram EFI variable\n"); |
|---|
| 495 | + |
|---|
| 496 | + kfree(nvram_efivar); |
|---|
| 497 | + *data_len_ret = data_len; |
|---|
| 498 | + return data; |
|---|
| 499 | + |
|---|
| 500 | +fail: |
|---|
| 501 | + kfree(data); |
|---|
| 502 | + kfree(nvram_efivar); |
|---|
| 503 | + return NULL; |
|---|
| 504 | +} |
|---|
| 505 | +#else |
|---|
| 506 | +static inline u8 *brcmf_fw_nvram_from_efi(size_t *data_len) { return NULL; } |
|---|
| 507 | +#endif |
|---|
| 508 | + |
|---|
| 448 | 509 | static void brcmf_fw_free_request(struct brcmf_fw_request *req) |
|---|
| 449 | 510 | { |
|---|
| 450 | 511 | struct brcmf_fw_item *item; |
|---|
| .. | .. |
|---|
| 463 | 524 | { |
|---|
| 464 | 525 | struct brcmf_fw *fwctx = ctx; |
|---|
| 465 | 526 | struct brcmf_fw_item *cur; |
|---|
| 527 | + bool free_bcm47xx_nvram = false; |
|---|
| 528 | + bool kfree_nvram = false; |
|---|
| 466 | 529 | u32 nvram_length = 0; |
|---|
| 467 | 530 | void *nvram = NULL; |
|---|
| 468 | 531 | u8 *data = NULL; |
|---|
| 469 | 532 | size_t data_len; |
|---|
| 470 | | - bool raw_nvram; |
|---|
| 471 | 533 | |
|---|
| 472 | 534 | brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(fwctx->dev)); |
|---|
| 473 | 535 | |
|---|
| .. | .. |
|---|
| 476 | 538 | if (fw && fw->data) { |
|---|
| 477 | 539 | data = (u8 *)fw->data; |
|---|
| 478 | 540 | data_len = fw->size; |
|---|
| 479 | | - raw_nvram = false; |
|---|
| 480 | 541 | } else { |
|---|
| 481 | | - data = bcm47xx_nvram_get_contents(&data_len); |
|---|
| 482 | | - if (!data && !(cur->flags & BRCMF_FW_REQF_OPTIONAL)) |
|---|
| 542 | + if ((data = bcm47xx_nvram_get_contents(&data_len))) |
|---|
| 543 | + free_bcm47xx_nvram = true; |
|---|
| 544 | + else if ((data = brcmf_fw_nvram_from_efi(&data_len))) |
|---|
| 545 | + kfree_nvram = true; |
|---|
| 546 | + else if (!(cur->flags & BRCMF_FW_REQF_OPTIONAL)) |
|---|
| 483 | 547 | goto fail; |
|---|
| 484 | | - raw_nvram = true; |
|---|
| 485 | 548 | } |
|---|
| 486 | 549 | |
|---|
| 487 | 550 | if (data) |
|---|
| .. | .. |
|---|
| 489 | 552 | fwctx->req->domain_nr, |
|---|
| 490 | 553 | fwctx->req->bus_nr); |
|---|
| 491 | 554 | |
|---|
| 492 | | - if (raw_nvram) |
|---|
| 555 | + if (free_bcm47xx_nvram) |
|---|
| 493 | 556 | bcm47xx_nvram_release_contents(data); |
|---|
| 557 | + if (kfree_nvram) |
|---|
| 558 | + kfree(data); |
|---|
| 559 | + |
|---|
| 494 | 560 | release_firmware(fw); |
|---|
| 495 | 561 | if (!nvram && !(cur->flags & BRCMF_FW_REQF_OPTIONAL)) |
|---|
| 496 | 562 | goto fail; |
|---|
| .. | .. |
|---|
| 504 | 570 | return -ENOENT; |
|---|
| 505 | 571 | } |
|---|
| 506 | 572 | |
|---|
| 507 | | -static int brcmf_fw_request_next_item(struct brcmf_fw *fwctx, bool async) |
|---|
| 573 | +static int brcmf_fw_complete_request(const struct firmware *fw, |
|---|
| 574 | + struct brcmf_fw *fwctx) |
|---|
| 508 | 575 | { |
|---|
| 509 | | - struct brcmf_fw_item *cur; |
|---|
| 510 | | - const struct firmware *fw = NULL; |
|---|
| 511 | | - int ret; |
|---|
| 512 | | - |
|---|
| 513 | | - cur = &fwctx->req->items[fwctx->curpos]; |
|---|
| 514 | | - |
|---|
| 515 | | - brcmf_dbg(TRACE, "%srequest for %s\n", async ? "async " : "", |
|---|
| 516 | | - cur->path); |
|---|
| 517 | | - |
|---|
| 518 | | - if (async) |
|---|
| 519 | | - ret = request_firmware_nowait(THIS_MODULE, true, cur->path, |
|---|
| 520 | | - fwctx->dev, GFP_KERNEL, fwctx, |
|---|
| 521 | | - brcmf_fw_request_done); |
|---|
| 522 | | - else |
|---|
| 523 | | - ret = request_firmware(&fw, cur->path, fwctx->dev); |
|---|
| 524 | | - |
|---|
| 525 | | - if (ret < 0) { |
|---|
| 526 | | - brcmf_fw_request_done(NULL, fwctx); |
|---|
| 527 | | - } else if (!async && fw) { |
|---|
| 528 | | - brcmf_dbg(TRACE, "firmware %s %sfound\n", cur->path, |
|---|
| 529 | | - fw ? "" : "not "); |
|---|
| 530 | | - if (cur->type == BRCMF_FW_TYPE_BINARY) |
|---|
| 531 | | - cur->binary = fw; |
|---|
| 532 | | - else if (cur->type == BRCMF_FW_TYPE_NVRAM) |
|---|
| 533 | | - brcmf_fw_request_nvram_done(fw, fwctx); |
|---|
| 534 | | - else |
|---|
| 535 | | - release_firmware(fw); |
|---|
| 536 | | - |
|---|
| 537 | | - return -EAGAIN; |
|---|
| 538 | | - } |
|---|
| 539 | | - return 0; |
|---|
| 540 | | -} |
|---|
| 541 | | - |
|---|
| 542 | | -static void brcmf_fw_request_done(const struct firmware *fw, void *ctx) |
|---|
| 543 | | -{ |
|---|
| 544 | | - struct brcmf_fw *fwctx = ctx; |
|---|
| 545 | | - struct brcmf_fw_item *cur; |
|---|
| 576 | + struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos]; |
|---|
| 546 | 577 | int ret = 0; |
|---|
| 547 | 578 | |
|---|
| 548 | | - cur = &fwctx->req->items[fwctx->curpos]; |
|---|
| 549 | | - |
|---|
| 550 | | - brcmf_dbg(TRACE, "enter: firmware %s %sfound\n", cur->path, |
|---|
| 551 | | - fw ? "" : "not "); |
|---|
| 552 | | - |
|---|
| 553 | | - if (!fw) |
|---|
| 554 | | - ret = -ENOENT; |
|---|
| 579 | + brcmf_dbg(TRACE, "firmware %s %sfound\n", cur->path, fw ? "" : "not "); |
|---|
| 555 | 580 | |
|---|
| 556 | 581 | switch (cur->type) { |
|---|
| 557 | 582 | case BRCMF_FW_TYPE_NVRAM: |
|---|
| 558 | 583 | ret = brcmf_fw_request_nvram_done(fw, fwctx); |
|---|
| 559 | 584 | break; |
|---|
| 560 | 585 | case BRCMF_FW_TYPE_BINARY: |
|---|
| 561 | | - cur->binary = fw; |
|---|
| 586 | + if (fw) |
|---|
| 587 | + cur->binary = fw; |
|---|
| 588 | + else |
|---|
| 589 | + ret = -ENOENT; |
|---|
| 562 | 590 | break; |
|---|
| 563 | 591 | default: |
|---|
| 564 | 592 | /* something fishy here so bail out early */ |
|---|
| 565 | 593 | brcmf_err("unknown fw type: %d\n", cur->type); |
|---|
| 566 | 594 | release_firmware(fw); |
|---|
| 567 | 595 | ret = -EINVAL; |
|---|
| 568 | | - goto fail; |
|---|
| 569 | 596 | } |
|---|
| 570 | 597 | |
|---|
| 571 | | - if (ret < 0 && !(cur->flags & BRCMF_FW_REQF_OPTIONAL)) |
|---|
| 572 | | - goto fail; |
|---|
| 598 | + return (cur->flags & BRCMF_FW_REQF_OPTIONAL) ? 0 : ret; |
|---|
| 599 | +} |
|---|
| 573 | 600 | |
|---|
| 574 | | - do { |
|---|
| 575 | | - if (++fwctx->curpos == fwctx->req->n_items) { |
|---|
| 576 | | - ret = 0; |
|---|
| 577 | | - goto done; |
|---|
| 578 | | - } |
|---|
| 601 | +static int brcmf_fw_request_firmware(const struct firmware **fw, |
|---|
| 602 | + struct brcmf_fw *fwctx) |
|---|
| 603 | +{ |
|---|
| 604 | + struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos]; |
|---|
| 605 | + int ret; |
|---|
| 579 | 606 | |
|---|
| 580 | | - ret = brcmf_fw_request_next_item(fwctx, false); |
|---|
| 581 | | - } while (ret == -EAGAIN); |
|---|
| 607 | + /* nvram files are board-specific, first try a board-specific path */ |
|---|
| 608 | + if (cur->type == BRCMF_FW_TYPE_NVRAM && fwctx->req->board_type) { |
|---|
| 609 | + char alt_path[BRCMF_FW_NAME_LEN]; |
|---|
| 582 | 610 | |
|---|
| 583 | | - return; |
|---|
| 611 | + strlcpy(alt_path, cur->path, BRCMF_FW_NAME_LEN); |
|---|
| 612 | + /* strip .txt at the end */ |
|---|
| 613 | + alt_path[strlen(alt_path) - 4] = 0; |
|---|
| 614 | + strlcat(alt_path, ".", BRCMF_FW_NAME_LEN); |
|---|
| 615 | + strlcat(alt_path, fwctx->req->board_type, BRCMF_FW_NAME_LEN); |
|---|
| 616 | + strlcat(alt_path, ".txt", BRCMF_FW_NAME_LEN); |
|---|
| 584 | 617 | |
|---|
| 585 | | -fail: |
|---|
| 586 | | - brcmf_dbg(TRACE, "failed err=%d: dev=%s, fw=%s\n", ret, |
|---|
| 587 | | - dev_name(fwctx->dev), cur->path); |
|---|
| 588 | | - brcmf_fw_free_request(fwctx->req); |
|---|
| 589 | | - fwctx->req = NULL; |
|---|
| 590 | | -done: |
|---|
| 618 | + ret = request_firmware(fw, alt_path, fwctx->dev); |
|---|
| 619 | + if (ret == 0) |
|---|
| 620 | + return ret; |
|---|
| 621 | + } |
|---|
| 622 | + |
|---|
| 623 | + return request_firmware(fw, cur->path, fwctx->dev); |
|---|
| 624 | +} |
|---|
| 625 | + |
|---|
| 626 | +static void brcmf_fw_request_done(const struct firmware *fw, void *ctx) |
|---|
| 627 | +{ |
|---|
| 628 | + struct brcmf_fw *fwctx = ctx; |
|---|
| 629 | + int ret; |
|---|
| 630 | + |
|---|
| 631 | + ret = brcmf_fw_complete_request(fw, fwctx); |
|---|
| 632 | + |
|---|
| 633 | + while (ret == 0 && ++fwctx->curpos < fwctx->req->n_items) { |
|---|
| 634 | + brcmf_fw_request_firmware(&fw, fwctx); |
|---|
| 635 | + ret = brcmf_fw_complete_request(fw, ctx); |
|---|
| 636 | + } |
|---|
| 637 | + |
|---|
| 638 | + if (ret) { |
|---|
| 639 | + brcmf_fw_free_request(fwctx->req); |
|---|
| 640 | + fwctx->req = NULL; |
|---|
| 641 | + } |
|---|
| 591 | 642 | fwctx->done(fwctx->dev, ret, fwctx->req); |
|---|
| 592 | 643 | kfree(fwctx); |
|---|
| 593 | 644 | } |
|---|
| .. | .. |
|---|
| 611 | 662 | void (*fw_cb)(struct device *dev, int err, |
|---|
| 612 | 663 | struct brcmf_fw_request *req)) |
|---|
| 613 | 664 | { |
|---|
| 665 | + struct brcmf_fw_item *first = &req->items[0]; |
|---|
| 614 | 666 | struct brcmf_fw *fwctx; |
|---|
| 667 | + int ret; |
|---|
| 615 | 668 | |
|---|
| 616 | 669 | brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(dev)); |
|---|
| 617 | 670 | if (!fw_cb) |
|---|
| .. | .. |
|---|
| 628 | 681 | fwctx->req = req; |
|---|
| 629 | 682 | fwctx->done = fw_cb; |
|---|
| 630 | 683 | |
|---|
| 631 | | - brcmf_fw_request_next_item(fwctx, true); |
|---|
| 684 | + ret = request_firmware_nowait(THIS_MODULE, true, first->path, |
|---|
| 685 | + fwctx->dev, GFP_KERNEL, fwctx, |
|---|
| 686 | + brcmf_fw_request_done); |
|---|
| 687 | + if (ret < 0) |
|---|
| 688 | + brcmf_fw_request_done(NULL, fwctx); |
|---|
| 689 | + |
|---|
| 632 | 690 | return 0; |
|---|
| 633 | 691 | } |
|---|
| 634 | 692 | |
|---|
| .. | .. |
|---|
| 644 | 702 | size_t mp_path_len; |
|---|
| 645 | 703 | u32 i, j; |
|---|
| 646 | 704 | char end = '\0'; |
|---|
| 647 | | - size_t reqsz; |
|---|
| 648 | 705 | |
|---|
| 649 | 706 | for (i = 0; i < table_size; i++) { |
|---|
| 650 | 707 | if (mapping_table[i].chipid == chip && |
|---|
| .. | .. |
|---|
| 652 | 709 | break; |
|---|
| 653 | 710 | } |
|---|
| 654 | 711 | |
|---|
| 712 | + brcmf_chip_name(chip, chiprev, chipname, sizeof(chipname)); |
|---|
| 713 | + |
|---|
| 655 | 714 | if (i == table_size) { |
|---|
| 656 | | - brcmf_err("Unknown chipid %d [%d]\n", chip, chiprev); |
|---|
| 715 | + brcmf_err("Unknown chip %s\n", chipname); |
|---|
| 657 | 716 | return NULL; |
|---|
| 658 | 717 | } |
|---|
| 659 | 718 | |
|---|
| 660 | | - reqsz = sizeof(*fwreq) + n_fwnames * sizeof(struct brcmf_fw_item); |
|---|
| 661 | | - fwreq = kzalloc(reqsz, GFP_KERNEL); |
|---|
| 719 | + fwreq = kzalloc(struct_size(fwreq, items, n_fwnames), GFP_KERNEL); |
|---|
| 662 | 720 | if (!fwreq) |
|---|
| 663 | 721 | return NULL; |
|---|
| 664 | | - |
|---|
| 665 | | - brcmf_chip_name(chip, chiprev, chipname, sizeof(chipname)); |
|---|
| 666 | 722 | |
|---|
| 667 | 723 | brcmf_info("using %s for chip %s\n", |
|---|
| 668 | 724 | mapping_table[i].fw_base, chipname); |
|---|
| .. | .. |
|---|
| 676 | 732 | |
|---|
| 677 | 733 | for (j = 0; j < n_fwnames; j++) { |
|---|
| 678 | 734 | fwreq->items[j].path = fwnames[j].path; |
|---|
| 735 | + fwnames[j].path[0] = '\0'; |
|---|
| 679 | 736 | /* check if firmware path is provided by module parameter */ |
|---|
| 680 | 737 | if (brcmf_mp_global.firmware_path[0] != '\0') { |
|---|
| 681 | 738 | strlcpy(fwnames[j].path, mp_path, |
|---|