| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: ISC |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Copyright (c) 2014-2017 Qualcomm Atheros, Inc. |
|---|
| 3 | | - * Copyright (c) 2018, The Linux Foundation. All rights reserved. |
|---|
| 4 | | - * |
|---|
| 5 | | - * Permission to use, copy, modify, and/or distribute this software for any |
|---|
| 6 | | - * purpose with or without fee is hereby granted, provided that the above |
|---|
| 7 | | - * copyright notice and this permission notice appear in all copies. |
|---|
| 8 | | - * |
|---|
| 9 | | - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|---|
| 10 | | - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|---|
| 11 | | - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
|---|
| 12 | | - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|---|
| 13 | | - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
|---|
| 14 | | - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
|---|
| 15 | | - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|---|
| 4 | + * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. |
|---|
| 16 | 5 | */ |
|---|
| 17 | 6 | |
|---|
| 18 | 7 | /* Algorithmic part of the firmware download. |
|---|
| .. | .. |
|---|
| 156 | 145 | size_t size) |
|---|
| 157 | 146 | { |
|---|
| 158 | 147 | const struct wil_fw_record_brd_file *rec = data; |
|---|
| 148 | + u32 max_num_ent, i, ent_size; |
|---|
| 159 | 149 | |
|---|
| 160 | | - if (size < sizeof(*rec)) { |
|---|
| 161 | | - wil_err_fw(wil, "brd_file record too short: %zu\n", size); |
|---|
| 162 | | - return 0; |
|---|
| 150 | + if (size <= offsetof(struct wil_fw_record_brd_file, brd_info)) { |
|---|
| 151 | + wil_err(wil, "board record too short, size %zu\n", size); |
|---|
| 152 | + return -EINVAL; |
|---|
| 163 | 153 | } |
|---|
| 164 | 154 | |
|---|
| 165 | | - wil->brd_file_addr = le32_to_cpu(rec->base_addr); |
|---|
| 166 | | - wil->brd_file_max_size = le32_to_cpu(rec->max_size_bytes); |
|---|
| 155 | + ent_size = size - offsetof(struct wil_fw_record_brd_file, brd_info); |
|---|
| 156 | + max_num_ent = ent_size / sizeof(struct brd_info); |
|---|
| 167 | 157 | |
|---|
| 168 | | - wil_dbg_fw(wil, "brd_file_addr 0x%x, brd_file_max_size %d\n", |
|---|
| 169 | | - wil->brd_file_addr, wil->brd_file_max_size); |
|---|
| 158 | + if (!max_num_ent) { |
|---|
| 159 | + wil_err(wil, "brd info entries are missing\n"); |
|---|
| 160 | + return -EINVAL; |
|---|
| 161 | + } |
|---|
| 162 | + |
|---|
| 163 | + wil->brd_info = kcalloc(max_num_ent, sizeof(struct wil_brd_info), |
|---|
| 164 | + GFP_KERNEL); |
|---|
| 165 | + if (!wil->brd_info) |
|---|
| 166 | + return -ENOMEM; |
|---|
| 167 | + |
|---|
| 168 | + for (i = 0; i < max_num_ent; i++) { |
|---|
| 169 | + wil->brd_info[i].file_addr = |
|---|
| 170 | + le32_to_cpu(rec->brd_info[i].base_addr); |
|---|
| 171 | + wil->brd_info[i].file_max_size = |
|---|
| 172 | + le32_to_cpu(rec->brd_info[i].max_size_bytes); |
|---|
| 173 | + |
|---|
| 174 | + if (!wil->brd_info[i].file_addr) |
|---|
| 175 | + break; |
|---|
| 176 | + |
|---|
| 177 | + wil_dbg_fw(wil, |
|---|
| 178 | + "brd info %d: file_addr 0x%x, file_max_size %d\n", |
|---|
| 179 | + i, wil->brd_info[i].file_addr, |
|---|
| 180 | + wil->brd_info[i].file_max_size); |
|---|
| 181 | + } |
|---|
| 182 | + |
|---|
| 183 | + wil->num_of_brd_entries = i; |
|---|
| 184 | + if (wil->num_of_brd_entries == 0) { |
|---|
| 185 | + kfree(wil->brd_info); |
|---|
| 186 | + wil->brd_info = NULL; |
|---|
| 187 | + wil_dbg_fw(wil, |
|---|
| 188 | + "no valid brd info entries, using brd file addr\n"); |
|---|
| 189 | + |
|---|
| 190 | + } else { |
|---|
| 191 | + wil_dbg_fw(wil, "num of brd info entries %d\n", |
|---|
| 192 | + wil->num_of_brd_entries); |
|---|
| 193 | + } |
|---|
| 170 | 194 | |
|---|
| 171 | 195 | return 0; |
|---|
| 172 | 196 | } |
|---|
| .. | .. |
|---|
| 634 | 658 | } |
|---|
| 635 | 659 | wil_dbg_fw(wil, "Loading <%s>, %zu bytes\n", name, fw->size); |
|---|
| 636 | 660 | |
|---|
| 661 | + /* re-initialize board info params */ |
|---|
| 662 | + wil->num_of_brd_entries = 0; |
|---|
| 663 | + kfree(wil->brd_info); |
|---|
| 664 | + wil->brd_info = NULL; |
|---|
| 665 | + |
|---|
| 637 | 666 | for (sz = fw->size, d = fw->data; sz; sz -= rc1, d += rc1) { |
|---|
| 638 | 667 | rc1 = wil_fw_verify(wil, d, sz); |
|---|
| 639 | 668 | if (rc1 < 0) { |
|---|
| .. | .. |
|---|
| 647 | 676 | |
|---|
| 648 | 677 | out: |
|---|
| 649 | 678 | release_firmware(fw); |
|---|
| 679 | + if (rc) |
|---|
| 680 | + wil_err_fw(wil, "Loading <%s> failed, rc %d\n", name, rc); |
|---|
| 650 | 681 | return rc; |
|---|
| 651 | 682 | } |
|---|
| 652 | 683 | |
|---|
| .. | .. |
|---|
| 660 | 691 | { |
|---|
| 661 | 692 | int rc = 0; |
|---|
| 662 | 693 | const struct wil_fw_record_head *hdr = data; |
|---|
| 663 | | - size_t s, hdr_sz; |
|---|
| 694 | + size_t s, hdr_sz = 0; |
|---|
| 664 | 695 | u16 type; |
|---|
| 696 | + int i = 0; |
|---|
| 665 | 697 | |
|---|
| 666 | | - /* Assuming the board file includes only one header record and one data |
|---|
| 667 | | - * record. Each record starts with wil_fw_record_head. |
|---|
| 698 | + /* Assuming the board file includes only one file header |
|---|
| 699 | + * and one or several data records. |
|---|
| 700 | + * Each record starts with wil_fw_record_head. |
|---|
| 668 | 701 | */ |
|---|
| 669 | 702 | if (size < sizeof(*hdr)) |
|---|
| 670 | 703 | return -EINVAL; |
|---|
| .. | .. |
|---|
| 672 | 705 | if (s > size) |
|---|
| 673 | 706 | return -EINVAL; |
|---|
| 674 | 707 | |
|---|
| 675 | | - /* Skip the header record and handle the data record */ |
|---|
| 676 | | - hdr = (const void *)hdr + s; |
|---|
| 708 | + /* Skip the header record and handle the data records */ |
|---|
| 677 | 709 | size -= s; |
|---|
| 678 | | - if (size < sizeof(*hdr)) |
|---|
| 679 | | - return -EINVAL; |
|---|
| 680 | | - hdr_sz = le32_to_cpu(hdr->size); |
|---|
| 681 | 710 | |
|---|
| 682 | | - if (wil->brd_file_max_size && hdr_sz > wil->brd_file_max_size) |
|---|
| 683 | | - return -EINVAL; |
|---|
| 684 | | - if (sizeof(*hdr) + hdr_sz > size) |
|---|
| 685 | | - return -EINVAL; |
|---|
| 686 | | - if (hdr_sz % 4) { |
|---|
| 687 | | - wil_err_fw(wil, "unaligned record size: %zu\n", |
|---|
| 688 | | - hdr_sz); |
|---|
| 689 | | - return -EINVAL; |
|---|
| 711 | + for (hdr = data + s;; hdr = (const void *)hdr + s, size -= s, i++) { |
|---|
| 712 | + if (size < sizeof(*hdr)) |
|---|
| 713 | + break; |
|---|
| 714 | + |
|---|
| 715 | + if (i >= wil->num_of_brd_entries) { |
|---|
| 716 | + wil_err_fw(wil, |
|---|
| 717 | + "Too many brd records: %d, num of expected entries %d\n", |
|---|
| 718 | + i, wil->num_of_brd_entries); |
|---|
| 719 | + break; |
|---|
| 720 | + } |
|---|
| 721 | + |
|---|
| 722 | + hdr_sz = le32_to_cpu(hdr->size); |
|---|
| 723 | + s = sizeof(*hdr) + hdr_sz; |
|---|
| 724 | + if (wil->brd_info[i].file_max_size && |
|---|
| 725 | + hdr_sz > wil->brd_info[i].file_max_size) |
|---|
| 726 | + return -EINVAL; |
|---|
| 727 | + if (sizeof(*hdr) + hdr_sz > size) |
|---|
| 728 | + return -EINVAL; |
|---|
| 729 | + if (hdr_sz % 4) { |
|---|
| 730 | + wil_err_fw(wil, "unaligned record size: %zu\n", |
|---|
| 731 | + hdr_sz); |
|---|
| 732 | + return -EINVAL; |
|---|
| 733 | + } |
|---|
| 734 | + type = le16_to_cpu(hdr->type); |
|---|
| 735 | + if (type != wil_fw_type_data) { |
|---|
| 736 | + wil_err_fw(wil, |
|---|
| 737 | + "invalid record type for board file: %d\n", |
|---|
| 738 | + type); |
|---|
| 739 | + return -EINVAL; |
|---|
| 740 | + } |
|---|
| 741 | + if (hdr_sz < sizeof(struct wil_fw_record_data)) { |
|---|
| 742 | + wil_err_fw(wil, "data record too short: %zu\n", hdr_sz); |
|---|
| 743 | + return -EINVAL; |
|---|
| 744 | + } |
|---|
| 745 | + |
|---|
| 746 | + wil_dbg_fw(wil, |
|---|
| 747 | + "using info from fw file for record %d: addr[0x%08x], max size %d\n", |
|---|
| 748 | + i, wil->brd_info[i].file_addr, |
|---|
| 749 | + wil->brd_info[i].file_max_size); |
|---|
| 750 | + |
|---|
| 751 | + rc = __fw_handle_data(wil, &hdr[1], hdr_sz, |
|---|
| 752 | + cpu_to_le32(wil->brd_info[i].file_addr)); |
|---|
| 753 | + if (rc) |
|---|
| 754 | + return rc; |
|---|
| 690 | 755 | } |
|---|
| 691 | | - type = le16_to_cpu(hdr->type); |
|---|
| 692 | | - if (type != wil_fw_type_data) { |
|---|
| 693 | | - wil_err_fw(wil, "invalid record type for board file: %d\n", |
|---|
| 694 | | - type); |
|---|
| 695 | | - return -EINVAL; |
|---|
| 696 | | - } |
|---|
| 697 | | - if (hdr_sz < sizeof(struct wil_fw_record_data)) { |
|---|
| 698 | | - wil_err_fw(wil, "data record too short: %zu\n", hdr_sz); |
|---|
| 756 | + |
|---|
| 757 | + if (size) { |
|---|
| 758 | + wil_err_fw(wil, "unprocessed bytes: %zu\n", size); |
|---|
| 759 | + if (size >= sizeof(*hdr)) { |
|---|
| 760 | + wil_err_fw(wil, |
|---|
| 761 | + "Stop at offset %ld record type %d [%zd bytes]\n", |
|---|
| 762 | + (long)((const void *)hdr - data), |
|---|
| 763 | + le16_to_cpu(hdr->type), hdr_sz); |
|---|
| 764 | + } |
|---|
| 699 | 765 | return -EINVAL; |
|---|
| 700 | 766 | } |
|---|
| 701 | 767 | |
|---|
| 702 | | - wil_dbg_fw(wil, "using addr from fw file: [0x%08x]\n", |
|---|
| 703 | | - wil->brd_file_addr); |
|---|
| 704 | | - |
|---|
| 705 | | - rc = __fw_handle_data(wil, &hdr[1], hdr_sz, |
|---|
| 706 | | - cpu_to_le32(wil->brd_file_addr)); |
|---|
| 707 | | - |
|---|
| 708 | | - return rc; |
|---|
| 768 | + return 0; |
|---|
| 709 | 769 | } |
|---|
| 710 | 770 | |
|---|
| 711 | 771 | /** |
|---|
| .. | .. |
|---|
| 736 | 796 | rc = dlen; |
|---|
| 737 | 797 | goto out; |
|---|
| 738 | 798 | } |
|---|
| 739 | | - /* Process the data record */ |
|---|
| 799 | + |
|---|
| 800 | + /* Process the data records */ |
|---|
| 740 | 801 | rc = wil_brd_process(wil, brd->data, dlen); |
|---|
| 741 | 802 | |
|---|
| 742 | 803 | out: |
|---|
| 743 | 804 | release_firmware(brd); |
|---|
| 805 | + if (rc) |
|---|
| 806 | + wil_err_fw(wil, "Loading <%s> failed, rc %d\n", name, rc); |
|---|
| 744 | 807 | return rc; |
|---|
| 745 | 808 | } |
|---|
| 746 | 809 | |
|---|