18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: ISC 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2014-2017 Qualcomm Atheros, Inc. 48c2ecf20Sopenharmony_ci * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci/* Algorithmic part of the firmware download. 88c2ecf20Sopenharmony_ci * To be included in the container file providing framework 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#define wil_err_fw(wil, fmt, arg...) wil_err(wil, "ERR[ FW ]" fmt, ##arg) 128c2ecf20Sopenharmony_ci#define wil_dbg_fw(wil, fmt, arg...) wil_dbg(wil, "DBG[ FW ]" fmt, ##arg) 138c2ecf20Sopenharmony_ci#define wil_hex_dump_fw(prefix_str, prefix_type, rowsize, \ 148c2ecf20Sopenharmony_ci groupsize, buf, len, ascii) \ 158c2ecf20Sopenharmony_ci print_hex_dump_debug("DBG[ FW ]" prefix_str, \ 168c2ecf20Sopenharmony_ci prefix_type, rowsize, \ 178c2ecf20Sopenharmony_ci groupsize, buf, len, ascii) 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistatic bool wil_fw_addr_check(struct wil6210_priv *wil, 208c2ecf20Sopenharmony_ci void __iomem **ioaddr, __le32 val, 218c2ecf20Sopenharmony_ci u32 size, const char *msg) 228c2ecf20Sopenharmony_ci{ 238c2ecf20Sopenharmony_ci *ioaddr = wmi_buffer_block(wil, val, size); 248c2ecf20Sopenharmony_ci if (!(*ioaddr)) { 258c2ecf20Sopenharmony_ci wil_err_fw(wil, "bad %s: 0x%08x\n", msg, le32_to_cpu(val)); 268c2ecf20Sopenharmony_ci return false; 278c2ecf20Sopenharmony_ci } 288c2ecf20Sopenharmony_ci return true; 298c2ecf20Sopenharmony_ci} 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci/** 328c2ecf20Sopenharmony_ci * wil_fw_verify - verify firmware file validity 338c2ecf20Sopenharmony_ci * 348c2ecf20Sopenharmony_ci * perform various checks for the firmware file header. 358c2ecf20Sopenharmony_ci * records are not validated. 368c2ecf20Sopenharmony_ci * 378c2ecf20Sopenharmony_ci * Return file size or negative error 388c2ecf20Sopenharmony_ci */ 398c2ecf20Sopenharmony_cistatic int wil_fw_verify(struct wil6210_priv *wil, const u8 *data, size_t size) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci const struct wil_fw_record_head *hdr = (const void *)data; 428c2ecf20Sopenharmony_ci struct wil_fw_record_file_header fh; 438c2ecf20Sopenharmony_ci const struct wil_fw_record_file_header *fh_; 448c2ecf20Sopenharmony_ci u32 crc; 458c2ecf20Sopenharmony_ci u32 dlen; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci if (size % 4) { 488c2ecf20Sopenharmony_ci wil_err_fw(wil, "image size not aligned: %zu\n", size); 498c2ecf20Sopenharmony_ci return -EINVAL; 508c2ecf20Sopenharmony_ci } 518c2ecf20Sopenharmony_ci /* have enough data for the file header? */ 528c2ecf20Sopenharmony_ci if (size < sizeof(*hdr) + sizeof(fh)) { 538c2ecf20Sopenharmony_ci wil_err_fw(wil, "file too short: %zu bytes\n", size); 548c2ecf20Sopenharmony_ci return -EINVAL; 558c2ecf20Sopenharmony_ci } 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci /* start with the file header? */ 588c2ecf20Sopenharmony_ci if (le16_to_cpu(hdr->type) != wil_fw_type_file_header) { 598c2ecf20Sopenharmony_ci wil_err_fw(wil, "no file header\n"); 608c2ecf20Sopenharmony_ci return -EINVAL; 618c2ecf20Sopenharmony_ci } 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci /* data_len */ 648c2ecf20Sopenharmony_ci fh_ = (struct wil_fw_record_file_header *)&hdr[1]; 658c2ecf20Sopenharmony_ci dlen = le32_to_cpu(fh_->data_len); 668c2ecf20Sopenharmony_ci if (dlen % 4) { 678c2ecf20Sopenharmony_ci wil_err_fw(wil, "data length not aligned: %lu\n", (ulong)dlen); 688c2ecf20Sopenharmony_ci return -EINVAL; 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci if (size < dlen) { 718c2ecf20Sopenharmony_ci wil_err_fw(wil, "file truncated at %zu/%lu\n", 728c2ecf20Sopenharmony_ci size, (ulong)dlen); 738c2ecf20Sopenharmony_ci return -EINVAL; 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci if (dlen < sizeof(*hdr) + sizeof(fh)) { 768c2ecf20Sopenharmony_ci wil_err_fw(wil, "data length too short: %lu\n", (ulong)dlen); 778c2ecf20Sopenharmony_ci return -EINVAL; 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci /* signature */ 818c2ecf20Sopenharmony_ci if (le32_to_cpu(fh_->signature) != WIL_FW_SIGNATURE) { 828c2ecf20Sopenharmony_ci wil_err_fw(wil, "bad header signature: 0x%08x\n", 838c2ecf20Sopenharmony_ci le32_to_cpu(fh_->signature)); 848c2ecf20Sopenharmony_ci return -EINVAL; 858c2ecf20Sopenharmony_ci } 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* version */ 888c2ecf20Sopenharmony_ci if (le32_to_cpu(fh_->version) > WIL_FW_FMT_VERSION) { 898c2ecf20Sopenharmony_ci wil_err_fw(wil, "unsupported header version: %d\n", 908c2ecf20Sopenharmony_ci le32_to_cpu(fh_->version)); 918c2ecf20Sopenharmony_ci return -EINVAL; 928c2ecf20Sopenharmony_ci } 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci /* checksum. ~crc32(~0, data, size) when fh.crc set to 0*/ 958c2ecf20Sopenharmony_ci fh = *fh_; 968c2ecf20Sopenharmony_ci fh.crc = 0; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci crc = crc32_le(~0, (unsigned char const *)hdr, sizeof(*hdr)); 998c2ecf20Sopenharmony_ci crc = crc32_le(crc, (unsigned char const *)&fh, sizeof(fh)); 1008c2ecf20Sopenharmony_ci crc = crc32_le(crc, (unsigned char const *)&fh_[1], 1018c2ecf20Sopenharmony_ci dlen - sizeof(*hdr) - sizeof(fh)); 1028c2ecf20Sopenharmony_ci crc = ~crc; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci if (crc != le32_to_cpu(fh_->crc)) { 1058c2ecf20Sopenharmony_ci wil_err_fw(wil, "checksum mismatch:" 1068c2ecf20Sopenharmony_ci " calculated for %lu bytes 0x%08x != 0x%08x\n", 1078c2ecf20Sopenharmony_ci (ulong)dlen, crc, le32_to_cpu(fh_->crc)); 1088c2ecf20Sopenharmony_ci return -EINVAL; 1098c2ecf20Sopenharmony_ci } 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci return (int)dlen; 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic int fw_ignore_section(struct wil6210_priv *wil, const void *data, 1158c2ecf20Sopenharmony_ci size_t size) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci return 0; 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic int 1218c2ecf20Sopenharmony_cifw_handle_capabilities(struct wil6210_priv *wil, const void *data, 1228c2ecf20Sopenharmony_ci size_t size) 1238c2ecf20Sopenharmony_ci{ 1248c2ecf20Sopenharmony_ci const struct wil_fw_record_capabilities *rec = data; 1258c2ecf20Sopenharmony_ci size_t capa_size; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci if (size < sizeof(*rec)) { 1288c2ecf20Sopenharmony_ci wil_err_fw(wil, "capabilities record too short: %zu\n", size); 1298c2ecf20Sopenharmony_ci /* let the FW load anyway */ 1308c2ecf20Sopenharmony_ci return 0; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci capa_size = size - offsetof(struct wil_fw_record_capabilities, 1348c2ecf20Sopenharmony_ci capabilities); 1358c2ecf20Sopenharmony_ci bitmap_zero(wil->fw_capabilities, WMI_FW_CAPABILITY_MAX); 1368c2ecf20Sopenharmony_ci memcpy(wil->fw_capabilities, rec->capabilities, 1378c2ecf20Sopenharmony_ci min_t(size_t, sizeof(wil->fw_capabilities), capa_size)); 1388c2ecf20Sopenharmony_ci wil_hex_dump_fw("CAPA", DUMP_PREFIX_OFFSET, 16, 1, 1398c2ecf20Sopenharmony_ci rec->capabilities, capa_size, false); 1408c2ecf20Sopenharmony_ci return 0; 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic int 1448c2ecf20Sopenharmony_cifw_handle_brd_file(struct wil6210_priv *wil, const void *data, 1458c2ecf20Sopenharmony_ci size_t size) 1468c2ecf20Sopenharmony_ci{ 1478c2ecf20Sopenharmony_ci const struct wil_fw_record_brd_file *rec = data; 1488c2ecf20Sopenharmony_ci u32 max_num_ent, i, ent_size; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci if (size <= offsetof(struct wil_fw_record_brd_file, brd_info)) { 1518c2ecf20Sopenharmony_ci wil_err(wil, "board record too short, size %zu\n", size); 1528c2ecf20Sopenharmony_ci return -EINVAL; 1538c2ecf20Sopenharmony_ci } 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci ent_size = size - offsetof(struct wil_fw_record_brd_file, brd_info); 1568c2ecf20Sopenharmony_ci max_num_ent = ent_size / sizeof(struct brd_info); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci if (!max_num_ent) { 1598c2ecf20Sopenharmony_ci wil_err(wil, "brd info entries are missing\n"); 1608c2ecf20Sopenharmony_ci return -EINVAL; 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci wil->brd_info = kcalloc(max_num_ent, sizeof(struct wil_brd_info), 1648c2ecf20Sopenharmony_ci GFP_KERNEL); 1658c2ecf20Sopenharmony_ci if (!wil->brd_info) 1668c2ecf20Sopenharmony_ci return -ENOMEM; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci for (i = 0; i < max_num_ent; i++) { 1698c2ecf20Sopenharmony_ci wil->brd_info[i].file_addr = 1708c2ecf20Sopenharmony_ci le32_to_cpu(rec->brd_info[i].base_addr); 1718c2ecf20Sopenharmony_ci wil->brd_info[i].file_max_size = 1728c2ecf20Sopenharmony_ci le32_to_cpu(rec->brd_info[i].max_size_bytes); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci if (!wil->brd_info[i].file_addr) 1758c2ecf20Sopenharmony_ci break; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci wil_dbg_fw(wil, 1788c2ecf20Sopenharmony_ci "brd info %d: file_addr 0x%x, file_max_size %d\n", 1798c2ecf20Sopenharmony_ci i, wil->brd_info[i].file_addr, 1808c2ecf20Sopenharmony_ci wil->brd_info[i].file_max_size); 1818c2ecf20Sopenharmony_ci } 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci wil->num_of_brd_entries = i; 1848c2ecf20Sopenharmony_ci if (wil->num_of_brd_entries == 0) { 1858c2ecf20Sopenharmony_ci kfree(wil->brd_info); 1868c2ecf20Sopenharmony_ci wil->brd_info = NULL; 1878c2ecf20Sopenharmony_ci wil_dbg_fw(wil, 1888c2ecf20Sopenharmony_ci "no valid brd info entries, using brd file addr\n"); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci } else { 1918c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "num of brd info entries %d\n", 1928c2ecf20Sopenharmony_ci wil->num_of_brd_entries); 1938c2ecf20Sopenharmony_ci } 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci return 0; 1968c2ecf20Sopenharmony_ci} 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cistatic int 1998c2ecf20Sopenharmony_cifw_handle_concurrency(struct wil6210_priv *wil, const void *data, 2008c2ecf20Sopenharmony_ci size_t size) 2018c2ecf20Sopenharmony_ci{ 2028c2ecf20Sopenharmony_ci const struct wil_fw_record_concurrency *rec = data; 2038c2ecf20Sopenharmony_ci const struct wil_fw_concurrency_combo *combo; 2048c2ecf20Sopenharmony_ci const struct wil_fw_concurrency_limit *limit; 2058c2ecf20Sopenharmony_ci size_t remain, lsize; 2068c2ecf20Sopenharmony_ci int i, n_combos; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci if (size < sizeof(*rec)) { 2098c2ecf20Sopenharmony_ci wil_err_fw(wil, "concurrency record too short: %zu\n", size); 2108c2ecf20Sopenharmony_ci /* continue, let the FW load anyway */ 2118c2ecf20Sopenharmony_ci return 0; 2128c2ecf20Sopenharmony_ci } 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci n_combos = le16_to_cpu(rec->n_combos); 2158c2ecf20Sopenharmony_ci remain = size - offsetof(struct wil_fw_record_concurrency, combos); 2168c2ecf20Sopenharmony_ci combo = rec->combos; 2178c2ecf20Sopenharmony_ci for (i = 0; i < n_combos; i++) { 2188c2ecf20Sopenharmony_ci if (remain < sizeof(*combo)) 2198c2ecf20Sopenharmony_ci goto out_short; 2208c2ecf20Sopenharmony_ci remain -= sizeof(*combo); 2218c2ecf20Sopenharmony_ci limit = combo->limits; 2228c2ecf20Sopenharmony_ci lsize = combo->n_limits * sizeof(*limit); 2238c2ecf20Sopenharmony_ci if (remain < lsize) 2248c2ecf20Sopenharmony_ci goto out_short; 2258c2ecf20Sopenharmony_ci remain -= lsize; 2268c2ecf20Sopenharmony_ci limit += combo->n_limits; 2278c2ecf20Sopenharmony_ci combo = (struct wil_fw_concurrency_combo *)limit; 2288c2ecf20Sopenharmony_ci } 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci return wil_cfg80211_iface_combinations_from_fw(wil, rec); 2318c2ecf20Sopenharmony_ciout_short: 2328c2ecf20Sopenharmony_ci wil_err_fw(wil, "concurrency record truncated\n"); 2338c2ecf20Sopenharmony_ci return 0; 2348c2ecf20Sopenharmony_ci} 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_cistatic int 2378c2ecf20Sopenharmony_cifw_handle_comment(struct wil6210_priv *wil, const void *data, 2388c2ecf20Sopenharmony_ci size_t size) 2398c2ecf20Sopenharmony_ci{ 2408c2ecf20Sopenharmony_ci const struct wil_fw_record_comment_hdr *hdr = data; 2418c2ecf20Sopenharmony_ci u32 magic; 2428c2ecf20Sopenharmony_ci int rc = 0; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci if (size < sizeof(*hdr)) 2458c2ecf20Sopenharmony_ci return 0; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci magic = le32_to_cpu(hdr->magic); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci switch (magic) { 2508c2ecf20Sopenharmony_ci case WIL_FW_CAPABILITIES_MAGIC: 2518c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "magic is WIL_FW_CAPABILITIES_MAGIC\n"); 2528c2ecf20Sopenharmony_ci rc = fw_handle_capabilities(wil, data, size); 2538c2ecf20Sopenharmony_ci break; 2548c2ecf20Sopenharmony_ci case WIL_BRD_FILE_MAGIC: 2558c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "magic is WIL_BRD_FILE_MAGIC\n"); 2568c2ecf20Sopenharmony_ci rc = fw_handle_brd_file(wil, data, size); 2578c2ecf20Sopenharmony_ci break; 2588c2ecf20Sopenharmony_ci case WIL_FW_CONCURRENCY_MAGIC: 2598c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "magic is WIL_FW_CONCURRENCY_MAGIC\n"); 2608c2ecf20Sopenharmony_ci rc = fw_handle_concurrency(wil, data, size); 2618c2ecf20Sopenharmony_ci break; 2628c2ecf20Sopenharmony_ci default: 2638c2ecf20Sopenharmony_ci wil_hex_dump_fw("", DUMP_PREFIX_OFFSET, 16, 1, 2648c2ecf20Sopenharmony_ci data, size, true); 2658c2ecf20Sopenharmony_ci } 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci return rc; 2688c2ecf20Sopenharmony_ci} 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_cistatic int __fw_handle_data(struct wil6210_priv *wil, const void *data, 2718c2ecf20Sopenharmony_ci size_t size, __le32 addr) 2728c2ecf20Sopenharmony_ci{ 2738c2ecf20Sopenharmony_ci const struct wil_fw_record_data *d = data; 2748c2ecf20Sopenharmony_ci void __iomem *dst; 2758c2ecf20Sopenharmony_ci size_t s = size - sizeof(*d); 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci if (size < sizeof(*d) + sizeof(u32)) { 2788c2ecf20Sopenharmony_ci wil_err_fw(wil, "data record too short: %zu\n", size); 2798c2ecf20Sopenharmony_ci return -EINVAL; 2808c2ecf20Sopenharmony_ci } 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci if (!wil_fw_addr_check(wil, &dst, addr, s, "address")) 2838c2ecf20Sopenharmony_ci return -EINVAL; 2848c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "write [0x%08x] <== %zu bytes\n", le32_to_cpu(addr), s); 2858c2ecf20Sopenharmony_ci wil_memcpy_toio_32(dst, d->data, s); 2868c2ecf20Sopenharmony_ci wmb(); /* finish before processing next record */ 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci return 0; 2898c2ecf20Sopenharmony_ci} 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_cistatic int fw_handle_data(struct wil6210_priv *wil, const void *data, 2928c2ecf20Sopenharmony_ci size_t size) 2938c2ecf20Sopenharmony_ci{ 2948c2ecf20Sopenharmony_ci const struct wil_fw_record_data *d = data; 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci return __fw_handle_data(wil, data, size, d->addr); 2978c2ecf20Sopenharmony_ci} 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_cistatic int fw_handle_fill(struct wil6210_priv *wil, const void *data, 3008c2ecf20Sopenharmony_ci size_t size) 3018c2ecf20Sopenharmony_ci{ 3028c2ecf20Sopenharmony_ci const struct wil_fw_record_fill *d = data; 3038c2ecf20Sopenharmony_ci void __iomem *dst; 3048c2ecf20Sopenharmony_ci u32 v; 3058c2ecf20Sopenharmony_ci size_t s = (size_t)le32_to_cpu(d->size); 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci if (size != sizeof(*d)) { 3088c2ecf20Sopenharmony_ci wil_err_fw(wil, "bad size for fill record: %zu\n", size); 3098c2ecf20Sopenharmony_ci return -EINVAL; 3108c2ecf20Sopenharmony_ci } 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci if (s < sizeof(u32)) { 3138c2ecf20Sopenharmony_ci wil_err_fw(wil, "fill size too short: %zu\n", s); 3148c2ecf20Sopenharmony_ci return -EINVAL; 3158c2ecf20Sopenharmony_ci } 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci if (s % sizeof(u32)) { 3188c2ecf20Sopenharmony_ci wil_err_fw(wil, "fill size not aligned: %zu\n", s); 3198c2ecf20Sopenharmony_ci return -EINVAL; 3208c2ecf20Sopenharmony_ci } 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci if (!wil_fw_addr_check(wil, &dst, d->addr, s, "address")) 3238c2ecf20Sopenharmony_ci return -EINVAL; 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci v = le32_to_cpu(d->value); 3268c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "fill [0x%08x] <== 0x%08x, %zu bytes\n", 3278c2ecf20Sopenharmony_ci le32_to_cpu(d->addr), v, s); 3288c2ecf20Sopenharmony_ci wil_memset_toio_32(dst, v, s); 3298c2ecf20Sopenharmony_ci wmb(); /* finish before processing next record */ 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci return 0; 3328c2ecf20Sopenharmony_ci} 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_cistatic int fw_handle_file_header(struct wil6210_priv *wil, const void *data, 3358c2ecf20Sopenharmony_ci size_t size) 3368c2ecf20Sopenharmony_ci{ 3378c2ecf20Sopenharmony_ci const struct wil_fw_record_file_header *d = data; 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci if (size != sizeof(*d)) { 3408c2ecf20Sopenharmony_ci wil_err_fw(wil, "file header length incorrect: %zu\n", size); 3418c2ecf20Sopenharmony_ci return -EINVAL; 3428c2ecf20Sopenharmony_ci } 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "new file, ver. %d, %i bytes\n", 3458c2ecf20Sopenharmony_ci d->version, d->data_len); 3468c2ecf20Sopenharmony_ci wil_hex_dump_fw("", DUMP_PREFIX_OFFSET, 16, 1, d->comment, 3478c2ecf20Sopenharmony_ci sizeof(d->comment), true); 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci if (!memcmp(d->comment, WIL_FW_VERSION_PREFIX, 3508c2ecf20Sopenharmony_ci WIL_FW_VERSION_PREFIX_LEN)) 3518c2ecf20Sopenharmony_ci memcpy(wil->fw_version, 3528c2ecf20Sopenharmony_ci d->comment + WIL_FW_VERSION_PREFIX_LEN, 3538c2ecf20Sopenharmony_ci min(sizeof(d->comment) - WIL_FW_VERSION_PREFIX_LEN, 3548c2ecf20Sopenharmony_ci sizeof(wil->fw_version) - 1)); 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci return 0; 3578c2ecf20Sopenharmony_ci} 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_cistatic int fw_handle_direct_write(struct wil6210_priv *wil, const void *data, 3608c2ecf20Sopenharmony_ci size_t size) 3618c2ecf20Sopenharmony_ci{ 3628c2ecf20Sopenharmony_ci const struct wil_fw_record_direct_write *d = data; 3638c2ecf20Sopenharmony_ci const struct wil_fw_data_dwrite *block = d->data; 3648c2ecf20Sopenharmony_ci int n, i; 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci if (size % sizeof(*block)) { 3678c2ecf20Sopenharmony_ci wil_err_fw(wil, "record size not aligned on %zu: %zu\n", 3688c2ecf20Sopenharmony_ci sizeof(*block), size); 3698c2ecf20Sopenharmony_ci return -EINVAL; 3708c2ecf20Sopenharmony_ci } 3718c2ecf20Sopenharmony_ci n = size / sizeof(*block); 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci for (i = 0; i < n; i++) { 3748c2ecf20Sopenharmony_ci void __iomem *dst; 3758c2ecf20Sopenharmony_ci u32 m = le32_to_cpu(block[i].mask); 3768c2ecf20Sopenharmony_ci u32 v = le32_to_cpu(block[i].value); 3778c2ecf20Sopenharmony_ci u32 x, y; 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_ci if (!wil_fw_addr_check(wil, &dst, block[i].addr, 0, "address")) 3808c2ecf20Sopenharmony_ci return -EINVAL; 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci x = readl(dst); 3838c2ecf20Sopenharmony_ci y = (x & m) | (v & ~m); 3848c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "write [0x%08x] <== 0x%08x " 3858c2ecf20Sopenharmony_ci "(old 0x%08x val 0x%08x mask 0x%08x)\n", 3868c2ecf20Sopenharmony_ci le32_to_cpu(block[i].addr), y, x, v, m); 3878c2ecf20Sopenharmony_ci writel(y, dst); 3888c2ecf20Sopenharmony_ci wmb(); /* finish before processing next record */ 3898c2ecf20Sopenharmony_ci } 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci return 0; 3928c2ecf20Sopenharmony_ci} 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_cistatic int gw_write(struct wil6210_priv *wil, void __iomem *gwa_addr, 3958c2ecf20Sopenharmony_ci void __iomem *gwa_cmd, void __iomem *gwa_ctl, u32 gw_cmd, 3968c2ecf20Sopenharmony_ci u32 a) 3978c2ecf20Sopenharmony_ci{ 3988c2ecf20Sopenharmony_ci unsigned delay = 0; 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_ci writel(a, gwa_addr); 4018c2ecf20Sopenharmony_ci writel(gw_cmd, gwa_cmd); 4028c2ecf20Sopenharmony_ci wmb(); /* finish before activate gw */ 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci writel(WIL_FW_GW_CTL_RUN, gwa_ctl); /* activate gw */ 4058c2ecf20Sopenharmony_ci do { 4068c2ecf20Sopenharmony_ci udelay(1); /* typical time is few usec */ 4078c2ecf20Sopenharmony_ci if (delay++ > 100) { 4088c2ecf20Sopenharmony_ci wil_err_fw(wil, "gw timeout\n"); 4098c2ecf20Sopenharmony_ci return -EINVAL; 4108c2ecf20Sopenharmony_ci } 4118c2ecf20Sopenharmony_ci } while (readl(gwa_ctl) & WIL_FW_GW_CTL_BUSY); /* gw done? */ 4128c2ecf20Sopenharmony_ci 4138c2ecf20Sopenharmony_ci return 0; 4148c2ecf20Sopenharmony_ci} 4158c2ecf20Sopenharmony_ci 4168c2ecf20Sopenharmony_cistatic int fw_handle_gateway_data(struct wil6210_priv *wil, const void *data, 4178c2ecf20Sopenharmony_ci size_t size) 4188c2ecf20Sopenharmony_ci{ 4198c2ecf20Sopenharmony_ci const struct wil_fw_record_gateway_data *d = data; 4208c2ecf20Sopenharmony_ci const struct wil_fw_data_gw *block = d->data; 4218c2ecf20Sopenharmony_ci void __iomem *gwa_addr; 4228c2ecf20Sopenharmony_ci void __iomem *gwa_val; 4238c2ecf20Sopenharmony_ci void __iomem *gwa_cmd; 4248c2ecf20Sopenharmony_ci void __iomem *gwa_ctl; 4258c2ecf20Sopenharmony_ci u32 gw_cmd; 4268c2ecf20Sopenharmony_ci int n, i; 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci if (size < sizeof(*d) + sizeof(*block)) { 4298c2ecf20Sopenharmony_ci wil_err_fw(wil, "gateway record too short: %zu\n", size); 4308c2ecf20Sopenharmony_ci return -EINVAL; 4318c2ecf20Sopenharmony_ci } 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci if ((size - sizeof(*d)) % sizeof(*block)) { 4348c2ecf20Sopenharmony_ci wil_err_fw(wil, "gateway record data size" 4358c2ecf20Sopenharmony_ci " not aligned on %zu: %zu\n", 4368c2ecf20Sopenharmony_ci sizeof(*block), size - sizeof(*d)); 4378c2ecf20Sopenharmony_ci return -EINVAL; 4388c2ecf20Sopenharmony_ci } 4398c2ecf20Sopenharmony_ci n = (size - sizeof(*d)) / sizeof(*block); 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci gw_cmd = le32_to_cpu(d->command); 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "gw write record [%3d] blocks, cmd 0x%08x\n", 4448c2ecf20Sopenharmony_ci n, gw_cmd); 4458c2ecf20Sopenharmony_ci 4468c2ecf20Sopenharmony_ci if (!wil_fw_addr_check(wil, &gwa_addr, d->gateway_addr_addr, 0, 4478c2ecf20Sopenharmony_ci "gateway_addr_addr") || 4488c2ecf20Sopenharmony_ci !wil_fw_addr_check(wil, &gwa_val, d->gateway_value_addr, 0, 4498c2ecf20Sopenharmony_ci "gateway_value_addr") || 4508c2ecf20Sopenharmony_ci !wil_fw_addr_check(wil, &gwa_cmd, d->gateway_cmd_addr, 0, 4518c2ecf20Sopenharmony_ci "gateway_cmd_addr") || 4528c2ecf20Sopenharmony_ci !wil_fw_addr_check(wil, &gwa_ctl, d->gateway_ctrl_address, 0, 4538c2ecf20Sopenharmony_ci "gateway_ctrl_address")) 4548c2ecf20Sopenharmony_ci return -EINVAL; 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "gw addresses: addr 0x%08x val 0x%08x" 4578c2ecf20Sopenharmony_ci " cmd 0x%08x ctl 0x%08x\n", 4588c2ecf20Sopenharmony_ci le32_to_cpu(d->gateway_addr_addr), 4598c2ecf20Sopenharmony_ci le32_to_cpu(d->gateway_value_addr), 4608c2ecf20Sopenharmony_ci le32_to_cpu(d->gateway_cmd_addr), 4618c2ecf20Sopenharmony_ci le32_to_cpu(d->gateway_ctrl_address)); 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci for (i = 0; i < n; i++) { 4648c2ecf20Sopenharmony_ci int rc; 4658c2ecf20Sopenharmony_ci u32 a = le32_to_cpu(block[i].addr); 4668c2ecf20Sopenharmony_ci u32 v = le32_to_cpu(block[i].value); 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_ci wil_dbg_fw(wil, " gw write[%3d] [0x%08x] <== 0x%08x\n", 4698c2ecf20Sopenharmony_ci i, a, v); 4708c2ecf20Sopenharmony_ci 4718c2ecf20Sopenharmony_ci writel(v, gwa_val); 4728c2ecf20Sopenharmony_ci rc = gw_write(wil, gwa_addr, gwa_cmd, gwa_ctl, gw_cmd, a); 4738c2ecf20Sopenharmony_ci if (rc) 4748c2ecf20Sopenharmony_ci return rc; 4758c2ecf20Sopenharmony_ci } 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci return 0; 4788c2ecf20Sopenharmony_ci} 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_cistatic int fw_handle_gateway_data4(struct wil6210_priv *wil, const void *data, 4818c2ecf20Sopenharmony_ci size_t size) 4828c2ecf20Sopenharmony_ci{ 4838c2ecf20Sopenharmony_ci const struct wil_fw_record_gateway_data4 *d = data; 4848c2ecf20Sopenharmony_ci const struct wil_fw_data_gw4 *block = d->data; 4858c2ecf20Sopenharmony_ci void __iomem *gwa_addr; 4868c2ecf20Sopenharmony_ci void __iomem *gwa_val[ARRAY_SIZE(block->value)]; 4878c2ecf20Sopenharmony_ci void __iomem *gwa_cmd; 4888c2ecf20Sopenharmony_ci void __iomem *gwa_ctl; 4898c2ecf20Sopenharmony_ci u32 gw_cmd; 4908c2ecf20Sopenharmony_ci int n, i, k; 4918c2ecf20Sopenharmony_ci 4928c2ecf20Sopenharmony_ci if (size < sizeof(*d) + sizeof(*block)) { 4938c2ecf20Sopenharmony_ci wil_err_fw(wil, "gateway4 record too short: %zu\n", size); 4948c2ecf20Sopenharmony_ci return -EINVAL; 4958c2ecf20Sopenharmony_ci } 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_ci if ((size - sizeof(*d)) % sizeof(*block)) { 4988c2ecf20Sopenharmony_ci wil_err_fw(wil, "gateway4 record data size" 4998c2ecf20Sopenharmony_ci " not aligned on %zu: %zu\n", 5008c2ecf20Sopenharmony_ci sizeof(*block), size - sizeof(*d)); 5018c2ecf20Sopenharmony_ci return -EINVAL; 5028c2ecf20Sopenharmony_ci } 5038c2ecf20Sopenharmony_ci n = (size - sizeof(*d)) / sizeof(*block); 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ci gw_cmd = le32_to_cpu(d->command); 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "gw4 write record [%3d] blocks, cmd 0x%08x\n", 5088c2ecf20Sopenharmony_ci n, gw_cmd); 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci if (!wil_fw_addr_check(wil, &gwa_addr, d->gateway_addr_addr, 0, 5118c2ecf20Sopenharmony_ci "gateway_addr_addr")) 5128c2ecf20Sopenharmony_ci return -EINVAL; 5138c2ecf20Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(block->value); k++) 5148c2ecf20Sopenharmony_ci if (!wil_fw_addr_check(wil, &gwa_val[k], 5158c2ecf20Sopenharmony_ci d->gateway_value_addr[k], 5168c2ecf20Sopenharmony_ci 0, "gateway_value_addr")) 5178c2ecf20Sopenharmony_ci return -EINVAL; 5188c2ecf20Sopenharmony_ci if (!wil_fw_addr_check(wil, &gwa_cmd, d->gateway_cmd_addr, 0, 5198c2ecf20Sopenharmony_ci "gateway_cmd_addr") || 5208c2ecf20Sopenharmony_ci !wil_fw_addr_check(wil, &gwa_ctl, d->gateway_ctrl_address, 0, 5218c2ecf20Sopenharmony_ci "gateway_ctrl_address")) 5228c2ecf20Sopenharmony_ci return -EINVAL; 5238c2ecf20Sopenharmony_ci 5248c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "gw4 addresses: addr 0x%08x cmd 0x%08x ctl 0x%08x\n", 5258c2ecf20Sopenharmony_ci le32_to_cpu(d->gateway_addr_addr), 5268c2ecf20Sopenharmony_ci le32_to_cpu(d->gateway_cmd_addr), 5278c2ecf20Sopenharmony_ci le32_to_cpu(d->gateway_ctrl_address)); 5288c2ecf20Sopenharmony_ci wil_hex_dump_fw("val addresses: ", DUMP_PREFIX_NONE, 16, 4, 5298c2ecf20Sopenharmony_ci d->gateway_value_addr, sizeof(d->gateway_value_addr), 5308c2ecf20Sopenharmony_ci false); 5318c2ecf20Sopenharmony_ci 5328c2ecf20Sopenharmony_ci for (i = 0; i < n; i++) { 5338c2ecf20Sopenharmony_ci int rc; 5348c2ecf20Sopenharmony_ci u32 a = le32_to_cpu(block[i].addr); 5358c2ecf20Sopenharmony_ci u32 v[ARRAY_SIZE(block->value)]; 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(block->value); k++) 5388c2ecf20Sopenharmony_ci v[k] = le32_to_cpu(block[i].value[k]); 5398c2ecf20Sopenharmony_ci 5408c2ecf20Sopenharmony_ci wil_dbg_fw(wil, " gw4 write[%3d] [0x%08x] <==\n", i, a); 5418c2ecf20Sopenharmony_ci wil_hex_dump_fw(" val ", DUMP_PREFIX_NONE, 16, 4, v, 5428c2ecf20Sopenharmony_ci sizeof(v), false); 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(block->value); k++) 5458c2ecf20Sopenharmony_ci writel(v[k], gwa_val[k]); 5468c2ecf20Sopenharmony_ci rc = gw_write(wil, gwa_addr, gwa_cmd, gwa_ctl, gw_cmd, a); 5478c2ecf20Sopenharmony_ci if (rc) 5488c2ecf20Sopenharmony_ci return rc; 5498c2ecf20Sopenharmony_ci } 5508c2ecf20Sopenharmony_ci 5518c2ecf20Sopenharmony_ci return 0; 5528c2ecf20Sopenharmony_ci} 5538c2ecf20Sopenharmony_ci 5548c2ecf20Sopenharmony_cistatic const struct { 5558c2ecf20Sopenharmony_ci int type; 5568c2ecf20Sopenharmony_ci int (*load_handler)(struct wil6210_priv *wil, const void *data, 5578c2ecf20Sopenharmony_ci size_t size); 5588c2ecf20Sopenharmony_ci int (*parse_handler)(struct wil6210_priv *wil, const void *data, 5598c2ecf20Sopenharmony_ci size_t size); 5608c2ecf20Sopenharmony_ci} wil_fw_handlers[] = { 5618c2ecf20Sopenharmony_ci {wil_fw_type_comment, fw_handle_comment, fw_handle_comment}, 5628c2ecf20Sopenharmony_ci {wil_fw_type_data, fw_handle_data, fw_ignore_section}, 5638c2ecf20Sopenharmony_ci {wil_fw_type_fill, fw_handle_fill, fw_ignore_section}, 5648c2ecf20Sopenharmony_ci /* wil_fw_type_action */ 5658c2ecf20Sopenharmony_ci /* wil_fw_type_verify */ 5668c2ecf20Sopenharmony_ci {wil_fw_type_file_header, fw_handle_file_header, 5678c2ecf20Sopenharmony_ci fw_handle_file_header}, 5688c2ecf20Sopenharmony_ci {wil_fw_type_direct_write, fw_handle_direct_write, fw_ignore_section}, 5698c2ecf20Sopenharmony_ci {wil_fw_type_gateway_data, fw_handle_gateway_data, fw_ignore_section}, 5708c2ecf20Sopenharmony_ci {wil_fw_type_gateway_data4, fw_handle_gateway_data4, 5718c2ecf20Sopenharmony_ci fw_ignore_section}, 5728c2ecf20Sopenharmony_ci}; 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_cistatic int wil_fw_handle_record(struct wil6210_priv *wil, int type, 5758c2ecf20Sopenharmony_ci const void *data, size_t size, bool load) 5768c2ecf20Sopenharmony_ci{ 5778c2ecf20Sopenharmony_ci int i; 5788c2ecf20Sopenharmony_ci 5798c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(wil_fw_handlers); i++) 5808c2ecf20Sopenharmony_ci if (wil_fw_handlers[i].type == type) 5818c2ecf20Sopenharmony_ci return load ? 5828c2ecf20Sopenharmony_ci wil_fw_handlers[i].load_handler( 5838c2ecf20Sopenharmony_ci wil, data, size) : 5848c2ecf20Sopenharmony_ci wil_fw_handlers[i].parse_handler( 5858c2ecf20Sopenharmony_ci wil, data, size); 5868c2ecf20Sopenharmony_ci 5878c2ecf20Sopenharmony_ci wil_err_fw(wil, "unknown record type: %d\n", type); 5888c2ecf20Sopenharmony_ci return -EINVAL; 5898c2ecf20Sopenharmony_ci} 5908c2ecf20Sopenharmony_ci 5918c2ecf20Sopenharmony_ci/** 5928c2ecf20Sopenharmony_ci * wil_fw_process - process section from FW file 5938c2ecf20Sopenharmony_ci * if load is true: Load the FW and uCode code and data to the 5948c2ecf20Sopenharmony_ci * corresponding device memory regions, 5958c2ecf20Sopenharmony_ci * otherwise only parse and look for capabilities 5968c2ecf20Sopenharmony_ci * 5978c2ecf20Sopenharmony_ci * Return error code 5988c2ecf20Sopenharmony_ci */ 5998c2ecf20Sopenharmony_cistatic int wil_fw_process(struct wil6210_priv *wil, const void *data, 6008c2ecf20Sopenharmony_ci size_t size, bool load) 6018c2ecf20Sopenharmony_ci{ 6028c2ecf20Sopenharmony_ci int rc = 0; 6038c2ecf20Sopenharmony_ci const struct wil_fw_record_head *hdr; 6048c2ecf20Sopenharmony_ci size_t s, hdr_sz; 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ci for (hdr = data;; hdr = (const void *)hdr + s, size -= s) { 6078c2ecf20Sopenharmony_ci if (size < sizeof(*hdr)) 6088c2ecf20Sopenharmony_ci break; 6098c2ecf20Sopenharmony_ci hdr_sz = le32_to_cpu(hdr->size); 6108c2ecf20Sopenharmony_ci s = sizeof(*hdr) + hdr_sz; 6118c2ecf20Sopenharmony_ci if (s > size) 6128c2ecf20Sopenharmony_ci break; 6138c2ecf20Sopenharmony_ci if (hdr_sz % 4) { 6148c2ecf20Sopenharmony_ci wil_err_fw(wil, "unaligned record size: %zu\n", 6158c2ecf20Sopenharmony_ci hdr_sz); 6168c2ecf20Sopenharmony_ci return -EINVAL; 6178c2ecf20Sopenharmony_ci } 6188c2ecf20Sopenharmony_ci rc = wil_fw_handle_record(wil, le16_to_cpu(hdr->type), 6198c2ecf20Sopenharmony_ci &hdr[1], hdr_sz, load); 6208c2ecf20Sopenharmony_ci if (rc) 6218c2ecf20Sopenharmony_ci return rc; 6228c2ecf20Sopenharmony_ci } 6238c2ecf20Sopenharmony_ci if (size) { 6248c2ecf20Sopenharmony_ci wil_err_fw(wil, "unprocessed bytes: %zu\n", size); 6258c2ecf20Sopenharmony_ci if (size >= sizeof(*hdr)) { 6268c2ecf20Sopenharmony_ci wil_err_fw(wil, "Stop at offset %ld" 6278c2ecf20Sopenharmony_ci " record type %d [%zd bytes]\n", 6288c2ecf20Sopenharmony_ci (long)((const void *)hdr - data), 6298c2ecf20Sopenharmony_ci le16_to_cpu(hdr->type), hdr_sz); 6308c2ecf20Sopenharmony_ci } 6318c2ecf20Sopenharmony_ci return -EINVAL; 6328c2ecf20Sopenharmony_ci } 6338c2ecf20Sopenharmony_ci 6348c2ecf20Sopenharmony_ci return rc; 6358c2ecf20Sopenharmony_ci} 6368c2ecf20Sopenharmony_ci 6378c2ecf20Sopenharmony_ci/** 6388c2ecf20Sopenharmony_ci * wil_request_firmware - Request firmware 6398c2ecf20Sopenharmony_ci * 6408c2ecf20Sopenharmony_ci * Request firmware image from the file 6418c2ecf20Sopenharmony_ci * If load is true, load firmware to device, otherwise 6428c2ecf20Sopenharmony_ci * only parse and extract capabilities 6438c2ecf20Sopenharmony_ci * 6448c2ecf20Sopenharmony_ci * Return error code 6458c2ecf20Sopenharmony_ci */ 6468c2ecf20Sopenharmony_ciint wil_request_firmware(struct wil6210_priv *wil, const char *name, 6478c2ecf20Sopenharmony_ci bool load) 6488c2ecf20Sopenharmony_ci{ 6498c2ecf20Sopenharmony_ci int rc, rc1; 6508c2ecf20Sopenharmony_ci const struct firmware *fw; 6518c2ecf20Sopenharmony_ci size_t sz; 6528c2ecf20Sopenharmony_ci const void *d; 6538c2ecf20Sopenharmony_ci 6548c2ecf20Sopenharmony_ci rc = request_firmware(&fw, name, wil_to_dev(wil)); 6558c2ecf20Sopenharmony_ci if (rc) { 6568c2ecf20Sopenharmony_ci wil_err_fw(wil, "Failed to load firmware %s rc %d\n", name, rc); 6578c2ecf20Sopenharmony_ci return rc; 6588c2ecf20Sopenharmony_ci } 6598c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "Loading <%s>, %zu bytes\n", name, fw->size); 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_ci /* re-initialize board info params */ 6628c2ecf20Sopenharmony_ci wil->num_of_brd_entries = 0; 6638c2ecf20Sopenharmony_ci kfree(wil->brd_info); 6648c2ecf20Sopenharmony_ci wil->brd_info = NULL; 6658c2ecf20Sopenharmony_ci 6668c2ecf20Sopenharmony_ci for (sz = fw->size, d = fw->data; sz; sz -= rc1, d += rc1) { 6678c2ecf20Sopenharmony_ci rc1 = wil_fw_verify(wil, d, sz); 6688c2ecf20Sopenharmony_ci if (rc1 < 0) { 6698c2ecf20Sopenharmony_ci rc = rc1; 6708c2ecf20Sopenharmony_ci goto out; 6718c2ecf20Sopenharmony_ci } 6728c2ecf20Sopenharmony_ci rc = wil_fw_process(wil, d, rc1, load); 6738c2ecf20Sopenharmony_ci if (rc < 0) 6748c2ecf20Sopenharmony_ci goto out; 6758c2ecf20Sopenharmony_ci } 6768c2ecf20Sopenharmony_ci 6778c2ecf20Sopenharmony_ciout: 6788c2ecf20Sopenharmony_ci release_firmware(fw); 6798c2ecf20Sopenharmony_ci if (rc) 6808c2ecf20Sopenharmony_ci wil_err_fw(wil, "Loading <%s> failed, rc %d\n", name, rc); 6818c2ecf20Sopenharmony_ci return rc; 6828c2ecf20Sopenharmony_ci} 6838c2ecf20Sopenharmony_ci 6848c2ecf20Sopenharmony_ci/** 6858c2ecf20Sopenharmony_ci * wil_brd_process - process section from BRD file 6868c2ecf20Sopenharmony_ci * 6878c2ecf20Sopenharmony_ci * Return error code 6888c2ecf20Sopenharmony_ci */ 6898c2ecf20Sopenharmony_cistatic int wil_brd_process(struct wil6210_priv *wil, const void *data, 6908c2ecf20Sopenharmony_ci size_t size) 6918c2ecf20Sopenharmony_ci{ 6928c2ecf20Sopenharmony_ci int rc = 0; 6938c2ecf20Sopenharmony_ci const struct wil_fw_record_head *hdr = data; 6948c2ecf20Sopenharmony_ci size_t s, hdr_sz = 0; 6958c2ecf20Sopenharmony_ci u16 type; 6968c2ecf20Sopenharmony_ci int i = 0; 6978c2ecf20Sopenharmony_ci 6988c2ecf20Sopenharmony_ci /* Assuming the board file includes only one file header 6998c2ecf20Sopenharmony_ci * and one or several data records. 7008c2ecf20Sopenharmony_ci * Each record starts with wil_fw_record_head. 7018c2ecf20Sopenharmony_ci */ 7028c2ecf20Sopenharmony_ci if (size < sizeof(*hdr)) 7038c2ecf20Sopenharmony_ci return -EINVAL; 7048c2ecf20Sopenharmony_ci s = sizeof(*hdr) + le32_to_cpu(hdr->size); 7058c2ecf20Sopenharmony_ci if (s > size) 7068c2ecf20Sopenharmony_ci return -EINVAL; 7078c2ecf20Sopenharmony_ci 7088c2ecf20Sopenharmony_ci /* Skip the header record and handle the data records */ 7098c2ecf20Sopenharmony_ci size -= s; 7108c2ecf20Sopenharmony_ci 7118c2ecf20Sopenharmony_ci for (hdr = data + s;; hdr = (const void *)hdr + s, size -= s, i++) { 7128c2ecf20Sopenharmony_ci if (size < sizeof(*hdr)) 7138c2ecf20Sopenharmony_ci break; 7148c2ecf20Sopenharmony_ci 7158c2ecf20Sopenharmony_ci if (i >= wil->num_of_brd_entries) { 7168c2ecf20Sopenharmony_ci wil_err_fw(wil, 7178c2ecf20Sopenharmony_ci "Too many brd records: %d, num of expected entries %d\n", 7188c2ecf20Sopenharmony_ci i, wil->num_of_brd_entries); 7198c2ecf20Sopenharmony_ci break; 7208c2ecf20Sopenharmony_ci } 7218c2ecf20Sopenharmony_ci 7228c2ecf20Sopenharmony_ci hdr_sz = le32_to_cpu(hdr->size); 7238c2ecf20Sopenharmony_ci s = sizeof(*hdr) + hdr_sz; 7248c2ecf20Sopenharmony_ci if (wil->brd_info[i].file_max_size && 7258c2ecf20Sopenharmony_ci hdr_sz > wil->brd_info[i].file_max_size) 7268c2ecf20Sopenharmony_ci return -EINVAL; 7278c2ecf20Sopenharmony_ci if (sizeof(*hdr) + hdr_sz > size) 7288c2ecf20Sopenharmony_ci return -EINVAL; 7298c2ecf20Sopenharmony_ci if (hdr_sz % 4) { 7308c2ecf20Sopenharmony_ci wil_err_fw(wil, "unaligned record size: %zu\n", 7318c2ecf20Sopenharmony_ci hdr_sz); 7328c2ecf20Sopenharmony_ci return -EINVAL; 7338c2ecf20Sopenharmony_ci } 7348c2ecf20Sopenharmony_ci type = le16_to_cpu(hdr->type); 7358c2ecf20Sopenharmony_ci if (type != wil_fw_type_data) { 7368c2ecf20Sopenharmony_ci wil_err_fw(wil, 7378c2ecf20Sopenharmony_ci "invalid record type for board file: %d\n", 7388c2ecf20Sopenharmony_ci type); 7398c2ecf20Sopenharmony_ci return -EINVAL; 7408c2ecf20Sopenharmony_ci } 7418c2ecf20Sopenharmony_ci if (hdr_sz < sizeof(struct wil_fw_record_data)) { 7428c2ecf20Sopenharmony_ci wil_err_fw(wil, "data record too short: %zu\n", hdr_sz); 7438c2ecf20Sopenharmony_ci return -EINVAL; 7448c2ecf20Sopenharmony_ci } 7458c2ecf20Sopenharmony_ci 7468c2ecf20Sopenharmony_ci wil_dbg_fw(wil, 7478c2ecf20Sopenharmony_ci "using info from fw file for record %d: addr[0x%08x], max size %d\n", 7488c2ecf20Sopenharmony_ci i, wil->brd_info[i].file_addr, 7498c2ecf20Sopenharmony_ci wil->brd_info[i].file_max_size); 7508c2ecf20Sopenharmony_ci 7518c2ecf20Sopenharmony_ci rc = __fw_handle_data(wil, &hdr[1], hdr_sz, 7528c2ecf20Sopenharmony_ci cpu_to_le32(wil->brd_info[i].file_addr)); 7538c2ecf20Sopenharmony_ci if (rc) 7548c2ecf20Sopenharmony_ci return rc; 7558c2ecf20Sopenharmony_ci } 7568c2ecf20Sopenharmony_ci 7578c2ecf20Sopenharmony_ci if (size) { 7588c2ecf20Sopenharmony_ci wil_err_fw(wil, "unprocessed bytes: %zu\n", size); 7598c2ecf20Sopenharmony_ci if (size >= sizeof(*hdr)) { 7608c2ecf20Sopenharmony_ci wil_err_fw(wil, 7618c2ecf20Sopenharmony_ci "Stop at offset %ld record type %d [%zd bytes]\n", 7628c2ecf20Sopenharmony_ci (long)((const void *)hdr - data), 7638c2ecf20Sopenharmony_ci le16_to_cpu(hdr->type), hdr_sz); 7648c2ecf20Sopenharmony_ci } 7658c2ecf20Sopenharmony_ci return -EINVAL; 7668c2ecf20Sopenharmony_ci } 7678c2ecf20Sopenharmony_ci 7688c2ecf20Sopenharmony_ci return 0; 7698c2ecf20Sopenharmony_ci} 7708c2ecf20Sopenharmony_ci 7718c2ecf20Sopenharmony_ci/** 7728c2ecf20Sopenharmony_ci * wil_request_board - Request board file 7738c2ecf20Sopenharmony_ci * 7748c2ecf20Sopenharmony_ci * Request board image from the file 7758c2ecf20Sopenharmony_ci * board file address and max size are read from FW file 7768c2ecf20Sopenharmony_ci * during initialization. 7778c2ecf20Sopenharmony_ci * brd file shall include one header and one data section. 7788c2ecf20Sopenharmony_ci * 7798c2ecf20Sopenharmony_ci * Return error code 7808c2ecf20Sopenharmony_ci */ 7818c2ecf20Sopenharmony_ciint wil_request_board(struct wil6210_priv *wil, const char *name) 7828c2ecf20Sopenharmony_ci{ 7838c2ecf20Sopenharmony_ci int rc, dlen; 7848c2ecf20Sopenharmony_ci const struct firmware *brd; 7858c2ecf20Sopenharmony_ci 7868c2ecf20Sopenharmony_ci rc = request_firmware(&brd, name, wil_to_dev(wil)); 7878c2ecf20Sopenharmony_ci if (rc) { 7888c2ecf20Sopenharmony_ci wil_err_fw(wil, "Failed to load brd %s\n", name); 7898c2ecf20Sopenharmony_ci return rc; 7908c2ecf20Sopenharmony_ci } 7918c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "Loading <%s>, %zu bytes\n", name, brd->size); 7928c2ecf20Sopenharmony_ci 7938c2ecf20Sopenharmony_ci /* Verify the header */ 7948c2ecf20Sopenharmony_ci dlen = wil_fw_verify(wil, brd->data, brd->size); 7958c2ecf20Sopenharmony_ci if (dlen < 0) { 7968c2ecf20Sopenharmony_ci rc = dlen; 7978c2ecf20Sopenharmony_ci goto out; 7988c2ecf20Sopenharmony_ci } 7998c2ecf20Sopenharmony_ci 8008c2ecf20Sopenharmony_ci /* Process the data records */ 8018c2ecf20Sopenharmony_ci rc = wil_brd_process(wil, brd->data, dlen); 8028c2ecf20Sopenharmony_ci 8038c2ecf20Sopenharmony_ciout: 8048c2ecf20Sopenharmony_ci release_firmware(brd); 8058c2ecf20Sopenharmony_ci if (rc) 8068c2ecf20Sopenharmony_ci wil_err_fw(wil, "Loading <%s> failed, rc %d\n", name, rc); 8078c2ecf20Sopenharmony_ci return rc; 8088c2ecf20Sopenharmony_ci} 8098c2ecf20Sopenharmony_ci 8108c2ecf20Sopenharmony_ci/** 8118c2ecf20Sopenharmony_ci * wil_fw_verify_file_exists - checks if firmware file exist 8128c2ecf20Sopenharmony_ci * 8138c2ecf20Sopenharmony_ci * @wil: driver context 8148c2ecf20Sopenharmony_ci * @name: firmware file name 8158c2ecf20Sopenharmony_ci * 8168c2ecf20Sopenharmony_ci * return value - boolean, true for success, false for failure 8178c2ecf20Sopenharmony_ci */ 8188c2ecf20Sopenharmony_cibool wil_fw_verify_file_exists(struct wil6210_priv *wil, const char *name) 8198c2ecf20Sopenharmony_ci{ 8208c2ecf20Sopenharmony_ci const struct firmware *fw; 8218c2ecf20Sopenharmony_ci int rc; 8228c2ecf20Sopenharmony_ci 8238c2ecf20Sopenharmony_ci rc = request_firmware(&fw, name, wil_to_dev(wil)); 8248c2ecf20Sopenharmony_ci if (!rc) 8258c2ecf20Sopenharmony_ci release_firmware(fw); 8268c2ecf20Sopenharmony_ci else 8278c2ecf20Sopenharmony_ci wil_dbg_fw(wil, "<%s> not available: %d\n", name, rc); 8288c2ecf20Sopenharmony_ci return !rc; 8298c2ecf20Sopenharmony_ci} 830