162306a36Sopenharmony_ci// SPDX-License-Identifier: ISC 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2014-2017 Qualcomm Atheros, Inc. 462306a36Sopenharmony_ci * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci/* Algorithmic part of the firmware download. 862306a36Sopenharmony_ci * To be included in the container file providing framework 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#define wil_err_fw(wil, fmt, arg...) wil_err(wil, "ERR[ FW ]" fmt, ##arg) 1262306a36Sopenharmony_ci#define wil_dbg_fw(wil, fmt, arg...) wil_dbg(wil, "DBG[ FW ]" fmt, ##arg) 1362306a36Sopenharmony_ci#define wil_hex_dump_fw(prefix_str, prefix_type, rowsize, \ 1462306a36Sopenharmony_ci groupsize, buf, len, ascii) \ 1562306a36Sopenharmony_ci print_hex_dump_debug("DBG[ FW ]" prefix_str, \ 1662306a36Sopenharmony_ci prefix_type, rowsize, \ 1762306a36Sopenharmony_ci groupsize, buf, len, ascii) 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistatic bool wil_fw_addr_check(struct wil6210_priv *wil, 2062306a36Sopenharmony_ci void __iomem **ioaddr, __le32 val, 2162306a36Sopenharmony_ci u32 size, const char *msg) 2262306a36Sopenharmony_ci{ 2362306a36Sopenharmony_ci *ioaddr = wmi_buffer_block(wil, val, size); 2462306a36Sopenharmony_ci if (!(*ioaddr)) { 2562306a36Sopenharmony_ci wil_err_fw(wil, "bad %s: 0x%08x\n", msg, le32_to_cpu(val)); 2662306a36Sopenharmony_ci return false; 2762306a36Sopenharmony_ci } 2862306a36Sopenharmony_ci return true; 2962306a36Sopenharmony_ci} 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/** 3262306a36Sopenharmony_ci * wil_fw_verify - verify firmware file validity 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * perform various checks for the firmware file header. 3562306a36Sopenharmony_ci * records are not validated. 3662306a36Sopenharmony_ci * 3762306a36Sopenharmony_ci * Return file size or negative error 3862306a36Sopenharmony_ci */ 3962306a36Sopenharmony_cistatic int wil_fw_verify(struct wil6210_priv *wil, const u8 *data, size_t size) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci const struct wil_fw_record_head *hdr = (const void *)data; 4262306a36Sopenharmony_ci struct wil_fw_record_file_header fh; 4362306a36Sopenharmony_ci const struct wil_fw_record_file_header *fh_; 4462306a36Sopenharmony_ci u32 crc; 4562306a36Sopenharmony_ci u32 dlen; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci if (size % 4) { 4862306a36Sopenharmony_ci wil_err_fw(wil, "image size not aligned: %zu\n", size); 4962306a36Sopenharmony_ci return -EINVAL; 5062306a36Sopenharmony_ci } 5162306a36Sopenharmony_ci /* have enough data for the file header? */ 5262306a36Sopenharmony_ci if (size < sizeof(*hdr) + sizeof(fh)) { 5362306a36Sopenharmony_ci wil_err_fw(wil, "file too short: %zu bytes\n", size); 5462306a36Sopenharmony_ci return -EINVAL; 5562306a36Sopenharmony_ci } 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci /* start with the file header? */ 5862306a36Sopenharmony_ci if (le16_to_cpu(hdr->type) != wil_fw_type_file_header) { 5962306a36Sopenharmony_ci wil_err_fw(wil, "no file header\n"); 6062306a36Sopenharmony_ci return -EINVAL; 6162306a36Sopenharmony_ci } 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci /* data_len */ 6462306a36Sopenharmony_ci fh_ = (struct wil_fw_record_file_header *)&hdr[1]; 6562306a36Sopenharmony_ci dlen = le32_to_cpu(fh_->data_len); 6662306a36Sopenharmony_ci if (dlen % 4) { 6762306a36Sopenharmony_ci wil_err_fw(wil, "data length not aligned: %lu\n", (ulong)dlen); 6862306a36Sopenharmony_ci return -EINVAL; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci if (size < dlen) { 7162306a36Sopenharmony_ci wil_err_fw(wil, "file truncated at %zu/%lu\n", 7262306a36Sopenharmony_ci size, (ulong)dlen); 7362306a36Sopenharmony_ci return -EINVAL; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci if (dlen < sizeof(*hdr) + sizeof(fh)) { 7662306a36Sopenharmony_ci wil_err_fw(wil, "data length too short: %lu\n", (ulong)dlen); 7762306a36Sopenharmony_ci return -EINVAL; 7862306a36Sopenharmony_ci } 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci /* signature */ 8162306a36Sopenharmony_ci if (le32_to_cpu(fh_->signature) != WIL_FW_SIGNATURE) { 8262306a36Sopenharmony_ci wil_err_fw(wil, "bad header signature: 0x%08x\n", 8362306a36Sopenharmony_ci le32_to_cpu(fh_->signature)); 8462306a36Sopenharmony_ci return -EINVAL; 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci /* version */ 8862306a36Sopenharmony_ci if (le32_to_cpu(fh_->version) > WIL_FW_FMT_VERSION) { 8962306a36Sopenharmony_ci wil_err_fw(wil, "unsupported header version: %d\n", 9062306a36Sopenharmony_ci le32_to_cpu(fh_->version)); 9162306a36Sopenharmony_ci return -EINVAL; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci /* checksum. ~crc32(~0, data, size) when fh.crc set to 0*/ 9562306a36Sopenharmony_ci fh = *fh_; 9662306a36Sopenharmony_ci fh.crc = 0; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci crc = crc32_le(~0, (unsigned char const *)hdr, sizeof(*hdr)); 9962306a36Sopenharmony_ci crc = crc32_le(crc, (unsigned char const *)&fh, sizeof(fh)); 10062306a36Sopenharmony_ci crc = crc32_le(crc, (unsigned char const *)&fh_[1], 10162306a36Sopenharmony_ci dlen - sizeof(*hdr) - sizeof(fh)); 10262306a36Sopenharmony_ci crc = ~crc; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (crc != le32_to_cpu(fh_->crc)) { 10562306a36Sopenharmony_ci wil_err_fw(wil, "checksum mismatch:" 10662306a36Sopenharmony_ci " calculated for %lu bytes 0x%08x != 0x%08x\n", 10762306a36Sopenharmony_ci (ulong)dlen, crc, le32_to_cpu(fh_->crc)); 10862306a36Sopenharmony_ci return -EINVAL; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci return (int)dlen; 11262306a36Sopenharmony_ci} 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic int fw_ignore_section(struct wil6210_priv *wil, const void *data, 11562306a36Sopenharmony_ci size_t size) 11662306a36Sopenharmony_ci{ 11762306a36Sopenharmony_ci return 0; 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic int 12162306a36Sopenharmony_cifw_handle_capabilities(struct wil6210_priv *wil, const void *data, 12262306a36Sopenharmony_ci size_t size) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci const struct wil_fw_record_capabilities *rec = data; 12562306a36Sopenharmony_ci size_t capa_size; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci if (size < sizeof(*rec)) { 12862306a36Sopenharmony_ci wil_err_fw(wil, "capabilities record too short: %zu\n", size); 12962306a36Sopenharmony_ci /* let the FW load anyway */ 13062306a36Sopenharmony_ci return 0; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci capa_size = size - offsetof(struct wil_fw_record_capabilities, 13462306a36Sopenharmony_ci capabilities); 13562306a36Sopenharmony_ci bitmap_zero(wil->fw_capabilities, WMI_FW_CAPABILITY_MAX); 13662306a36Sopenharmony_ci memcpy(wil->fw_capabilities, rec->capabilities, 13762306a36Sopenharmony_ci min_t(size_t, sizeof(wil->fw_capabilities), capa_size)); 13862306a36Sopenharmony_ci wil_hex_dump_fw("CAPA", DUMP_PREFIX_OFFSET, 16, 1, 13962306a36Sopenharmony_ci rec->capabilities, capa_size, false); 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic int 14462306a36Sopenharmony_cifw_handle_brd_file(struct wil6210_priv *wil, const void *data, 14562306a36Sopenharmony_ci size_t size) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci const struct wil_fw_record_brd_file *rec = data; 14862306a36Sopenharmony_ci u32 max_num_ent, i, ent_size; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci if (size <= offsetof(struct wil_fw_record_brd_file, brd_info)) { 15162306a36Sopenharmony_ci wil_err(wil, "board record too short, size %zu\n", size); 15262306a36Sopenharmony_ci return -EINVAL; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci ent_size = size - offsetof(struct wil_fw_record_brd_file, brd_info); 15662306a36Sopenharmony_ci max_num_ent = ent_size / sizeof(struct brd_info); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci if (!max_num_ent) { 15962306a36Sopenharmony_ci wil_err(wil, "brd info entries are missing\n"); 16062306a36Sopenharmony_ci return -EINVAL; 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci wil->brd_info = kcalloc(max_num_ent, sizeof(struct wil_brd_info), 16462306a36Sopenharmony_ci GFP_KERNEL); 16562306a36Sopenharmony_ci if (!wil->brd_info) 16662306a36Sopenharmony_ci return -ENOMEM; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci for (i = 0; i < max_num_ent; i++) { 16962306a36Sopenharmony_ci wil->brd_info[i].file_addr = 17062306a36Sopenharmony_ci le32_to_cpu(rec->brd_info[i].base_addr); 17162306a36Sopenharmony_ci wil->brd_info[i].file_max_size = 17262306a36Sopenharmony_ci le32_to_cpu(rec->brd_info[i].max_size_bytes); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci if (!wil->brd_info[i].file_addr) 17562306a36Sopenharmony_ci break; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci wil_dbg_fw(wil, 17862306a36Sopenharmony_ci "brd info %d: file_addr 0x%x, file_max_size %d\n", 17962306a36Sopenharmony_ci i, wil->brd_info[i].file_addr, 18062306a36Sopenharmony_ci wil->brd_info[i].file_max_size); 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci wil->num_of_brd_entries = i; 18462306a36Sopenharmony_ci if (wil->num_of_brd_entries == 0) { 18562306a36Sopenharmony_ci kfree(wil->brd_info); 18662306a36Sopenharmony_ci wil->brd_info = NULL; 18762306a36Sopenharmony_ci wil_dbg_fw(wil, 18862306a36Sopenharmony_ci "no valid brd info entries, using brd file addr\n"); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci } else { 19162306a36Sopenharmony_ci wil_dbg_fw(wil, "num of brd info entries %d\n", 19262306a36Sopenharmony_ci wil->num_of_brd_entries); 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci return 0; 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic int 19962306a36Sopenharmony_cifw_handle_concurrency(struct wil6210_priv *wil, const void *data, 20062306a36Sopenharmony_ci size_t size) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci const struct wil_fw_record_concurrency *rec = data; 20362306a36Sopenharmony_ci const struct wil_fw_concurrency_combo *combo; 20462306a36Sopenharmony_ci const struct wil_fw_concurrency_limit *limit; 20562306a36Sopenharmony_ci size_t remain, lsize; 20662306a36Sopenharmony_ci int i, n_combos; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci if (size < sizeof(*rec)) { 20962306a36Sopenharmony_ci wil_err_fw(wil, "concurrency record too short: %zu\n", size); 21062306a36Sopenharmony_ci /* continue, let the FW load anyway */ 21162306a36Sopenharmony_ci return 0; 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci n_combos = le16_to_cpu(rec->n_combos); 21562306a36Sopenharmony_ci remain = size - offsetof(struct wil_fw_record_concurrency, combos); 21662306a36Sopenharmony_ci combo = rec->combos; 21762306a36Sopenharmony_ci for (i = 0; i < n_combos; i++) { 21862306a36Sopenharmony_ci if (remain < sizeof(*combo)) 21962306a36Sopenharmony_ci goto out_short; 22062306a36Sopenharmony_ci remain -= sizeof(*combo); 22162306a36Sopenharmony_ci limit = combo->limits; 22262306a36Sopenharmony_ci lsize = combo->n_limits * sizeof(*limit); 22362306a36Sopenharmony_ci if (remain < lsize) 22462306a36Sopenharmony_ci goto out_short; 22562306a36Sopenharmony_ci remain -= lsize; 22662306a36Sopenharmony_ci limit += combo->n_limits; 22762306a36Sopenharmony_ci combo = (struct wil_fw_concurrency_combo *)limit; 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci return wil_cfg80211_iface_combinations_from_fw(wil, rec); 23162306a36Sopenharmony_ciout_short: 23262306a36Sopenharmony_ci wil_err_fw(wil, "concurrency record truncated\n"); 23362306a36Sopenharmony_ci return 0; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cistatic int 23762306a36Sopenharmony_cifw_handle_comment(struct wil6210_priv *wil, const void *data, 23862306a36Sopenharmony_ci size_t size) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci const struct wil_fw_record_comment_hdr *hdr = data; 24162306a36Sopenharmony_ci u32 magic; 24262306a36Sopenharmony_ci int rc = 0; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci if (size < sizeof(*hdr)) 24562306a36Sopenharmony_ci return 0; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci magic = le32_to_cpu(hdr->magic); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci switch (magic) { 25062306a36Sopenharmony_ci case WIL_FW_CAPABILITIES_MAGIC: 25162306a36Sopenharmony_ci wil_dbg_fw(wil, "magic is WIL_FW_CAPABILITIES_MAGIC\n"); 25262306a36Sopenharmony_ci rc = fw_handle_capabilities(wil, data, size); 25362306a36Sopenharmony_ci break; 25462306a36Sopenharmony_ci case WIL_BRD_FILE_MAGIC: 25562306a36Sopenharmony_ci wil_dbg_fw(wil, "magic is WIL_BRD_FILE_MAGIC\n"); 25662306a36Sopenharmony_ci rc = fw_handle_brd_file(wil, data, size); 25762306a36Sopenharmony_ci break; 25862306a36Sopenharmony_ci case WIL_FW_CONCURRENCY_MAGIC: 25962306a36Sopenharmony_ci wil_dbg_fw(wil, "magic is WIL_FW_CONCURRENCY_MAGIC\n"); 26062306a36Sopenharmony_ci rc = fw_handle_concurrency(wil, data, size); 26162306a36Sopenharmony_ci break; 26262306a36Sopenharmony_ci default: 26362306a36Sopenharmony_ci wil_hex_dump_fw("", DUMP_PREFIX_OFFSET, 16, 1, 26462306a36Sopenharmony_ci data, size, true); 26562306a36Sopenharmony_ci } 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci return rc; 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cistatic int __fw_handle_data(struct wil6210_priv *wil, const void *data, 27162306a36Sopenharmony_ci size_t size, __le32 addr) 27262306a36Sopenharmony_ci{ 27362306a36Sopenharmony_ci const struct wil_fw_record_data *d = data; 27462306a36Sopenharmony_ci void __iomem *dst; 27562306a36Sopenharmony_ci size_t s = size - sizeof(*d); 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci if (size < sizeof(*d) + sizeof(u32)) { 27862306a36Sopenharmony_ci wil_err_fw(wil, "data record too short: %zu\n", size); 27962306a36Sopenharmony_ci return -EINVAL; 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci if (!wil_fw_addr_check(wil, &dst, addr, s, "address")) 28362306a36Sopenharmony_ci return -EINVAL; 28462306a36Sopenharmony_ci wil_dbg_fw(wil, "write [0x%08x] <== %zu bytes\n", le32_to_cpu(addr), s); 28562306a36Sopenharmony_ci wil_memcpy_toio_32(dst, d->data, s); 28662306a36Sopenharmony_ci wmb(); /* finish before processing next record */ 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci return 0; 28962306a36Sopenharmony_ci} 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_cistatic int fw_handle_data(struct wil6210_priv *wil, const void *data, 29262306a36Sopenharmony_ci size_t size) 29362306a36Sopenharmony_ci{ 29462306a36Sopenharmony_ci const struct wil_fw_record_data *d = data; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci return __fw_handle_data(wil, data, size, d->addr); 29762306a36Sopenharmony_ci} 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_cistatic int fw_handle_fill(struct wil6210_priv *wil, const void *data, 30062306a36Sopenharmony_ci size_t size) 30162306a36Sopenharmony_ci{ 30262306a36Sopenharmony_ci const struct wil_fw_record_fill *d = data; 30362306a36Sopenharmony_ci void __iomem *dst; 30462306a36Sopenharmony_ci u32 v; 30562306a36Sopenharmony_ci size_t s = (size_t)le32_to_cpu(d->size); 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci if (size != sizeof(*d)) { 30862306a36Sopenharmony_ci wil_err_fw(wil, "bad size for fill record: %zu\n", size); 30962306a36Sopenharmony_ci return -EINVAL; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci if (s < sizeof(u32)) { 31362306a36Sopenharmony_ci wil_err_fw(wil, "fill size too short: %zu\n", s); 31462306a36Sopenharmony_ci return -EINVAL; 31562306a36Sopenharmony_ci } 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci if (s % sizeof(u32)) { 31862306a36Sopenharmony_ci wil_err_fw(wil, "fill size not aligned: %zu\n", s); 31962306a36Sopenharmony_ci return -EINVAL; 32062306a36Sopenharmony_ci } 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if (!wil_fw_addr_check(wil, &dst, d->addr, s, "address")) 32362306a36Sopenharmony_ci return -EINVAL; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci v = le32_to_cpu(d->value); 32662306a36Sopenharmony_ci wil_dbg_fw(wil, "fill [0x%08x] <== 0x%08x, %zu bytes\n", 32762306a36Sopenharmony_ci le32_to_cpu(d->addr), v, s); 32862306a36Sopenharmony_ci wil_memset_toio_32(dst, v, s); 32962306a36Sopenharmony_ci wmb(); /* finish before processing next record */ 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci return 0; 33262306a36Sopenharmony_ci} 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_cistatic int fw_handle_file_header(struct wil6210_priv *wil, const void *data, 33562306a36Sopenharmony_ci size_t size) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci const struct wil_fw_record_file_header *d = data; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci if (size != sizeof(*d)) { 34062306a36Sopenharmony_ci wil_err_fw(wil, "file header length incorrect: %zu\n", size); 34162306a36Sopenharmony_ci return -EINVAL; 34262306a36Sopenharmony_ci } 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci wil_dbg_fw(wil, "new file, ver. %d, %i bytes\n", 34562306a36Sopenharmony_ci d->version, d->data_len); 34662306a36Sopenharmony_ci wil_hex_dump_fw("", DUMP_PREFIX_OFFSET, 16, 1, d->comment, 34762306a36Sopenharmony_ci sizeof(d->comment), true); 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci if (!memcmp(d->comment, WIL_FW_VERSION_PREFIX, 35062306a36Sopenharmony_ci WIL_FW_VERSION_PREFIX_LEN)) 35162306a36Sopenharmony_ci memcpy(wil->fw_version, 35262306a36Sopenharmony_ci d->comment + WIL_FW_VERSION_PREFIX_LEN, 35362306a36Sopenharmony_ci min(sizeof(d->comment) - WIL_FW_VERSION_PREFIX_LEN, 35462306a36Sopenharmony_ci sizeof(wil->fw_version) - 1)); 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci return 0; 35762306a36Sopenharmony_ci} 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_cistatic int fw_handle_direct_write(struct wil6210_priv *wil, const void *data, 36062306a36Sopenharmony_ci size_t size) 36162306a36Sopenharmony_ci{ 36262306a36Sopenharmony_ci const struct wil_fw_record_direct_write *d = data; 36362306a36Sopenharmony_ci const struct wil_fw_data_dwrite *block = d->data; 36462306a36Sopenharmony_ci int n, i; 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci if (size % sizeof(*block)) { 36762306a36Sopenharmony_ci wil_err_fw(wil, "record size not aligned on %zu: %zu\n", 36862306a36Sopenharmony_ci sizeof(*block), size); 36962306a36Sopenharmony_ci return -EINVAL; 37062306a36Sopenharmony_ci } 37162306a36Sopenharmony_ci n = size / sizeof(*block); 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci for (i = 0; i < n; i++) { 37462306a36Sopenharmony_ci void __iomem *dst; 37562306a36Sopenharmony_ci u32 m = le32_to_cpu(block[i].mask); 37662306a36Sopenharmony_ci u32 v = le32_to_cpu(block[i].value); 37762306a36Sopenharmony_ci u32 x, y; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci if (!wil_fw_addr_check(wil, &dst, block[i].addr, 0, "address")) 38062306a36Sopenharmony_ci return -EINVAL; 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci x = readl(dst); 38362306a36Sopenharmony_ci y = (x & m) | (v & ~m); 38462306a36Sopenharmony_ci wil_dbg_fw(wil, "write [0x%08x] <== 0x%08x " 38562306a36Sopenharmony_ci "(old 0x%08x val 0x%08x mask 0x%08x)\n", 38662306a36Sopenharmony_ci le32_to_cpu(block[i].addr), y, x, v, m); 38762306a36Sopenharmony_ci writel(y, dst); 38862306a36Sopenharmony_ci wmb(); /* finish before processing next record */ 38962306a36Sopenharmony_ci } 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci return 0; 39262306a36Sopenharmony_ci} 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_cistatic int gw_write(struct wil6210_priv *wil, void __iomem *gwa_addr, 39562306a36Sopenharmony_ci void __iomem *gwa_cmd, void __iomem *gwa_ctl, u32 gw_cmd, 39662306a36Sopenharmony_ci u32 a) 39762306a36Sopenharmony_ci{ 39862306a36Sopenharmony_ci unsigned delay = 0; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci writel(a, gwa_addr); 40162306a36Sopenharmony_ci writel(gw_cmd, gwa_cmd); 40262306a36Sopenharmony_ci wmb(); /* finish before activate gw */ 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci writel(WIL_FW_GW_CTL_RUN, gwa_ctl); /* activate gw */ 40562306a36Sopenharmony_ci do { 40662306a36Sopenharmony_ci udelay(1); /* typical time is few usec */ 40762306a36Sopenharmony_ci if (delay++ > 100) { 40862306a36Sopenharmony_ci wil_err_fw(wil, "gw timeout\n"); 40962306a36Sopenharmony_ci return -EINVAL; 41062306a36Sopenharmony_ci } 41162306a36Sopenharmony_ci } while (readl(gwa_ctl) & WIL_FW_GW_CTL_BUSY); /* gw done? */ 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci return 0; 41462306a36Sopenharmony_ci} 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_cistatic int fw_handle_gateway_data(struct wil6210_priv *wil, const void *data, 41762306a36Sopenharmony_ci size_t size) 41862306a36Sopenharmony_ci{ 41962306a36Sopenharmony_ci const struct wil_fw_record_gateway_data *d = data; 42062306a36Sopenharmony_ci const struct wil_fw_data_gw *block = d->data; 42162306a36Sopenharmony_ci void __iomem *gwa_addr; 42262306a36Sopenharmony_ci void __iomem *gwa_val; 42362306a36Sopenharmony_ci void __iomem *gwa_cmd; 42462306a36Sopenharmony_ci void __iomem *gwa_ctl; 42562306a36Sopenharmony_ci u32 gw_cmd; 42662306a36Sopenharmony_ci int n, i; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci if (size < sizeof(*d) + sizeof(*block)) { 42962306a36Sopenharmony_ci wil_err_fw(wil, "gateway record too short: %zu\n", size); 43062306a36Sopenharmony_ci return -EINVAL; 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci if ((size - sizeof(*d)) % sizeof(*block)) { 43462306a36Sopenharmony_ci wil_err_fw(wil, "gateway record data size" 43562306a36Sopenharmony_ci " not aligned on %zu: %zu\n", 43662306a36Sopenharmony_ci sizeof(*block), size - sizeof(*d)); 43762306a36Sopenharmony_ci return -EINVAL; 43862306a36Sopenharmony_ci } 43962306a36Sopenharmony_ci n = (size - sizeof(*d)) / sizeof(*block); 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci gw_cmd = le32_to_cpu(d->command); 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci wil_dbg_fw(wil, "gw write record [%3d] blocks, cmd 0x%08x\n", 44462306a36Sopenharmony_ci n, gw_cmd); 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci if (!wil_fw_addr_check(wil, &gwa_addr, d->gateway_addr_addr, 0, 44762306a36Sopenharmony_ci "gateway_addr_addr") || 44862306a36Sopenharmony_ci !wil_fw_addr_check(wil, &gwa_val, d->gateway_value_addr, 0, 44962306a36Sopenharmony_ci "gateway_value_addr") || 45062306a36Sopenharmony_ci !wil_fw_addr_check(wil, &gwa_cmd, d->gateway_cmd_addr, 0, 45162306a36Sopenharmony_ci "gateway_cmd_addr") || 45262306a36Sopenharmony_ci !wil_fw_addr_check(wil, &gwa_ctl, d->gateway_ctrl_address, 0, 45362306a36Sopenharmony_ci "gateway_ctrl_address")) 45462306a36Sopenharmony_ci return -EINVAL; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci wil_dbg_fw(wil, "gw addresses: addr 0x%08x val 0x%08x" 45762306a36Sopenharmony_ci " cmd 0x%08x ctl 0x%08x\n", 45862306a36Sopenharmony_ci le32_to_cpu(d->gateway_addr_addr), 45962306a36Sopenharmony_ci le32_to_cpu(d->gateway_value_addr), 46062306a36Sopenharmony_ci le32_to_cpu(d->gateway_cmd_addr), 46162306a36Sopenharmony_ci le32_to_cpu(d->gateway_ctrl_address)); 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci for (i = 0; i < n; i++) { 46462306a36Sopenharmony_ci int rc; 46562306a36Sopenharmony_ci u32 a = le32_to_cpu(block[i].addr); 46662306a36Sopenharmony_ci u32 v = le32_to_cpu(block[i].value); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci wil_dbg_fw(wil, " gw write[%3d] [0x%08x] <== 0x%08x\n", 46962306a36Sopenharmony_ci i, a, v); 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci writel(v, gwa_val); 47262306a36Sopenharmony_ci rc = gw_write(wil, gwa_addr, gwa_cmd, gwa_ctl, gw_cmd, a); 47362306a36Sopenharmony_ci if (rc) 47462306a36Sopenharmony_ci return rc; 47562306a36Sopenharmony_ci } 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci return 0; 47862306a36Sopenharmony_ci} 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_cistatic int fw_handle_gateway_data4(struct wil6210_priv *wil, const void *data, 48162306a36Sopenharmony_ci size_t size) 48262306a36Sopenharmony_ci{ 48362306a36Sopenharmony_ci const struct wil_fw_record_gateway_data4 *d = data; 48462306a36Sopenharmony_ci const struct wil_fw_data_gw4 *block = d->data; 48562306a36Sopenharmony_ci void __iomem *gwa_addr; 48662306a36Sopenharmony_ci void __iomem *gwa_val[ARRAY_SIZE(block->value)]; 48762306a36Sopenharmony_ci void __iomem *gwa_cmd; 48862306a36Sopenharmony_ci void __iomem *gwa_ctl; 48962306a36Sopenharmony_ci u32 gw_cmd; 49062306a36Sopenharmony_ci int n, i, k; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci if (size < sizeof(*d) + sizeof(*block)) { 49362306a36Sopenharmony_ci wil_err_fw(wil, "gateway4 record too short: %zu\n", size); 49462306a36Sopenharmony_ci return -EINVAL; 49562306a36Sopenharmony_ci } 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci if ((size - sizeof(*d)) % sizeof(*block)) { 49862306a36Sopenharmony_ci wil_err_fw(wil, "gateway4 record data size" 49962306a36Sopenharmony_ci " not aligned on %zu: %zu\n", 50062306a36Sopenharmony_ci sizeof(*block), size - sizeof(*d)); 50162306a36Sopenharmony_ci return -EINVAL; 50262306a36Sopenharmony_ci } 50362306a36Sopenharmony_ci n = (size - sizeof(*d)) / sizeof(*block); 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci gw_cmd = le32_to_cpu(d->command); 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci wil_dbg_fw(wil, "gw4 write record [%3d] blocks, cmd 0x%08x\n", 50862306a36Sopenharmony_ci n, gw_cmd); 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci if (!wil_fw_addr_check(wil, &gwa_addr, d->gateway_addr_addr, 0, 51162306a36Sopenharmony_ci "gateway_addr_addr")) 51262306a36Sopenharmony_ci return -EINVAL; 51362306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(block->value); k++) 51462306a36Sopenharmony_ci if (!wil_fw_addr_check(wil, &gwa_val[k], 51562306a36Sopenharmony_ci d->gateway_value_addr[k], 51662306a36Sopenharmony_ci 0, "gateway_value_addr")) 51762306a36Sopenharmony_ci return -EINVAL; 51862306a36Sopenharmony_ci if (!wil_fw_addr_check(wil, &gwa_cmd, d->gateway_cmd_addr, 0, 51962306a36Sopenharmony_ci "gateway_cmd_addr") || 52062306a36Sopenharmony_ci !wil_fw_addr_check(wil, &gwa_ctl, d->gateway_ctrl_address, 0, 52162306a36Sopenharmony_ci "gateway_ctrl_address")) 52262306a36Sopenharmony_ci return -EINVAL; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci wil_dbg_fw(wil, "gw4 addresses: addr 0x%08x cmd 0x%08x ctl 0x%08x\n", 52562306a36Sopenharmony_ci le32_to_cpu(d->gateway_addr_addr), 52662306a36Sopenharmony_ci le32_to_cpu(d->gateway_cmd_addr), 52762306a36Sopenharmony_ci le32_to_cpu(d->gateway_ctrl_address)); 52862306a36Sopenharmony_ci wil_hex_dump_fw("val addresses: ", DUMP_PREFIX_NONE, 16, 4, 52962306a36Sopenharmony_ci d->gateway_value_addr, sizeof(d->gateway_value_addr), 53062306a36Sopenharmony_ci false); 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci for (i = 0; i < n; i++) { 53362306a36Sopenharmony_ci int rc; 53462306a36Sopenharmony_ci u32 a = le32_to_cpu(block[i].addr); 53562306a36Sopenharmony_ci u32 v[ARRAY_SIZE(block->value)]; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(block->value); k++) 53862306a36Sopenharmony_ci v[k] = le32_to_cpu(block[i].value[k]); 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci wil_dbg_fw(wil, " gw4 write[%3d] [0x%08x] <==\n", i, a); 54162306a36Sopenharmony_ci wil_hex_dump_fw(" val ", DUMP_PREFIX_NONE, 16, 4, v, 54262306a36Sopenharmony_ci sizeof(v), false); 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(block->value); k++) 54562306a36Sopenharmony_ci writel(v[k], gwa_val[k]); 54662306a36Sopenharmony_ci rc = gw_write(wil, gwa_addr, gwa_cmd, gwa_ctl, gw_cmd, a); 54762306a36Sopenharmony_ci if (rc) 54862306a36Sopenharmony_ci return rc; 54962306a36Sopenharmony_ci } 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci return 0; 55262306a36Sopenharmony_ci} 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_cistatic const struct { 55562306a36Sopenharmony_ci int type; 55662306a36Sopenharmony_ci int (*load_handler)(struct wil6210_priv *wil, const void *data, 55762306a36Sopenharmony_ci size_t size); 55862306a36Sopenharmony_ci int (*parse_handler)(struct wil6210_priv *wil, const void *data, 55962306a36Sopenharmony_ci size_t size); 56062306a36Sopenharmony_ci} wil_fw_handlers[] = { 56162306a36Sopenharmony_ci {wil_fw_type_comment, fw_handle_comment, fw_handle_comment}, 56262306a36Sopenharmony_ci {wil_fw_type_data, fw_handle_data, fw_ignore_section}, 56362306a36Sopenharmony_ci {wil_fw_type_fill, fw_handle_fill, fw_ignore_section}, 56462306a36Sopenharmony_ci /* wil_fw_type_action */ 56562306a36Sopenharmony_ci /* wil_fw_type_verify */ 56662306a36Sopenharmony_ci {wil_fw_type_file_header, fw_handle_file_header, 56762306a36Sopenharmony_ci fw_handle_file_header}, 56862306a36Sopenharmony_ci {wil_fw_type_direct_write, fw_handle_direct_write, fw_ignore_section}, 56962306a36Sopenharmony_ci {wil_fw_type_gateway_data, fw_handle_gateway_data, fw_ignore_section}, 57062306a36Sopenharmony_ci {wil_fw_type_gateway_data4, fw_handle_gateway_data4, 57162306a36Sopenharmony_ci fw_ignore_section}, 57262306a36Sopenharmony_ci}; 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_cistatic int wil_fw_handle_record(struct wil6210_priv *wil, int type, 57562306a36Sopenharmony_ci const void *data, size_t size, bool load) 57662306a36Sopenharmony_ci{ 57762306a36Sopenharmony_ci int i; 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(wil_fw_handlers); i++) 58062306a36Sopenharmony_ci if (wil_fw_handlers[i].type == type) 58162306a36Sopenharmony_ci return load ? 58262306a36Sopenharmony_ci wil_fw_handlers[i].load_handler( 58362306a36Sopenharmony_ci wil, data, size) : 58462306a36Sopenharmony_ci wil_fw_handlers[i].parse_handler( 58562306a36Sopenharmony_ci wil, data, size); 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci wil_err_fw(wil, "unknown record type: %d\n", type); 58862306a36Sopenharmony_ci return -EINVAL; 58962306a36Sopenharmony_ci} 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci/** 59262306a36Sopenharmony_ci * wil_fw_process - process section from FW file 59362306a36Sopenharmony_ci * if load is true: Load the FW and uCode code and data to the 59462306a36Sopenharmony_ci * corresponding device memory regions, 59562306a36Sopenharmony_ci * otherwise only parse and look for capabilities 59662306a36Sopenharmony_ci * 59762306a36Sopenharmony_ci * Return error code 59862306a36Sopenharmony_ci */ 59962306a36Sopenharmony_cistatic int wil_fw_process(struct wil6210_priv *wil, const void *data, 60062306a36Sopenharmony_ci size_t size, bool load) 60162306a36Sopenharmony_ci{ 60262306a36Sopenharmony_ci int rc = 0; 60362306a36Sopenharmony_ci const struct wil_fw_record_head *hdr; 60462306a36Sopenharmony_ci size_t s, hdr_sz; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci for (hdr = data;; hdr = (const void *)hdr + s, size -= s) { 60762306a36Sopenharmony_ci if (size < sizeof(*hdr)) 60862306a36Sopenharmony_ci break; 60962306a36Sopenharmony_ci hdr_sz = le32_to_cpu(hdr->size); 61062306a36Sopenharmony_ci s = sizeof(*hdr) + hdr_sz; 61162306a36Sopenharmony_ci if (s > size) 61262306a36Sopenharmony_ci break; 61362306a36Sopenharmony_ci if (hdr_sz % 4) { 61462306a36Sopenharmony_ci wil_err_fw(wil, "unaligned record size: %zu\n", 61562306a36Sopenharmony_ci hdr_sz); 61662306a36Sopenharmony_ci return -EINVAL; 61762306a36Sopenharmony_ci } 61862306a36Sopenharmony_ci rc = wil_fw_handle_record(wil, le16_to_cpu(hdr->type), 61962306a36Sopenharmony_ci &hdr[1], hdr_sz, load); 62062306a36Sopenharmony_ci if (rc) 62162306a36Sopenharmony_ci return rc; 62262306a36Sopenharmony_ci } 62362306a36Sopenharmony_ci if (size) { 62462306a36Sopenharmony_ci wil_err_fw(wil, "unprocessed bytes: %zu\n", size); 62562306a36Sopenharmony_ci if (size >= sizeof(*hdr)) { 62662306a36Sopenharmony_ci wil_err_fw(wil, "Stop at offset %ld" 62762306a36Sopenharmony_ci " record type %d [%zd bytes]\n", 62862306a36Sopenharmony_ci (long)((const void *)hdr - data), 62962306a36Sopenharmony_ci le16_to_cpu(hdr->type), hdr_sz); 63062306a36Sopenharmony_ci } 63162306a36Sopenharmony_ci return -EINVAL; 63262306a36Sopenharmony_ci } 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ci return rc; 63562306a36Sopenharmony_ci} 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci/** 63862306a36Sopenharmony_ci * wil_request_firmware - Request firmware 63962306a36Sopenharmony_ci * 64062306a36Sopenharmony_ci * Request firmware image from the file 64162306a36Sopenharmony_ci * If load is true, load firmware to device, otherwise 64262306a36Sopenharmony_ci * only parse and extract capabilities 64362306a36Sopenharmony_ci * 64462306a36Sopenharmony_ci * Return error code 64562306a36Sopenharmony_ci */ 64662306a36Sopenharmony_ciint wil_request_firmware(struct wil6210_priv *wil, const char *name, 64762306a36Sopenharmony_ci bool load) 64862306a36Sopenharmony_ci{ 64962306a36Sopenharmony_ci int rc, rc1; 65062306a36Sopenharmony_ci const struct firmware *fw; 65162306a36Sopenharmony_ci size_t sz; 65262306a36Sopenharmony_ci const void *d; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci rc = request_firmware(&fw, name, wil_to_dev(wil)); 65562306a36Sopenharmony_ci if (rc) { 65662306a36Sopenharmony_ci wil_err_fw(wil, "Failed to load firmware %s rc %d\n", name, rc); 65762306a36Sopenharmony_ci return rc; 65862306a36Sopenharmony_ci } 65962306a36Sopenharmony_ci wil_dbg_fw(wil, "Loading <%s>, %zu bytes\n", name, fw->size); 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci /* re-initialize board info params */ 66262306a36Sopenharmony_ci wil->num_of_brd_entries = 0; 66362306a36Sopenharmony_ci kfree(wil->brd_info); 66462306a36Sopenharmony_ci wil->brd_info = NULL; 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci for (sz = fw->size, d = fw->data; sz; sz -= rc1, d += rc1) { 66762306a36Sopenharmony_ci rc1 = wil_fw_verify(wil, d, sz); 66862306a36Sopenharmony_ci if (rc1 < 0) { 66962306a36Sopenharmony_ci rc = rc1; 67062306a36Sopenharmony_ci goto out; 67162306a36Sopenharmony_ci } 67262306a36Sopenharmony_ci rc = wil_fw_process(wil, d, rc1, load); 67362306a36Sopenharmony_ci if (rc < 0) 67462306a36Sopenharmony_ci goto out; 67562306a36Sopenharmony_ci } 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ciout: 67862306a36Sopenharmony_ci release_firmware(fw); 67962306a36Sopenharmony_ci if (rc) 68062306a36Sopenharmony_ci wil_err_fw(wil, "Loading <%s> failed, rc %d\n", name, rc); 68162306a36Sopenharmony_ci return rc; 68262306a36Sopenharmony_ci} 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci/** 68562306a36Sopenharmony_ci * wil_brd_process - process section from BRD file 68662306a36Sopenharmony_ci * 68762306a36Sopenharmony_ci * Return error code 68862306a36Sopenharmony_ci */ 68962306a36Sopenharmony_cistatic int wil_brd_process(struct wil6210_priv *wil, const void *data, 69062306a36Sopenharmony_ci size_t size) 69162306a36Sopenharmony_ci{ 69262306a36Sopenharmony_ci int rc = 0; 69362306a36Sopenharmony_ci const struct wil_fw_record_head *hdr = data; 69462306a36Sopenharmony_ci size_t s, hdr_sz = 0; 69562306a36Sopenharmony_ci u16 type; 69662306a36Sopenharmony_ci int i = 0; 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci /* Assuming the board file includes only one file header 69962306a36Sopenharmony_ci * and one or several data records. 70062306a36Sopenharmony_ci * Each record starts with wil_fw_record_head. 70162306a36Sopenharmony_ci */ 70262306a36Sopenharmony_ci if (size < sizeof(*hdr)) 70362306a36Sopenharmony_ci return -EINVAL; 70462306a36Sopenharmony_ci s = sizeof(*hdr) + le32_to_cpu(hdr->size); 70562306a36Sopenharmony_ci if (s > size) 70662306a36Sopenharmony_ci return -EINVAL; 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci /* Skip the header record and handle the data records */ 70962306a36Sopenharmony_ci size -= s; 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci for (hdr = data + s;; hdr = (const void *)hdr + s, size -= s, i++) { 71262306a36Sopenharmony_ci if (size < sizeof(*hdr)) 71362306a36Sopenharmony_ci break; 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci if (i >= wil->num_of_brd_entries) { 71662306a36Sopenharmony_ci wil_err_fw(wil, 71762306a36Sopenharmony_ci "Too many brd records: %d, num of expected entries %d\n", 71862306a36Sopenharmony_ci i, wil->num_of_brd_entries); 71962306a36Sopenharmony_ci break; 72062306a36Sopenharmony_ci } 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci hdr_sz = le32_to_cpu(hdr->size); 72362306a36Sopenharmony_ci s = sizeof(*hdr) + hdr_sz; 72462306a36Sopenharmony_ci if (wil->brd_info[i].file_max_size && 72562306a36Sopenharmony_ci hdr_sz > wil->brd_info[i].file_max_size) 72662306a36Sopenharmony_ci return -EINVAL; 72762306a36Sopenharmony_ci if (sizeof(*hdr) + hdr_sz > size) 72862306a36Sopenharmony_ci return -EINVAL; 72962306a36Sopenharmony_ci if (hdr_sz % 4) { 73062306a36Sopenharmony_ci wil_err_fw(wil, "unaligned record size: %zu\n", 73162306a36Sopenharmony_ci hdr_sz); 73262306a36Sopenharmony_ci return -EINVAL; 73362306a36Sopenharmony_ci } 73462306a36Sopenharmony_ci type = le16_to_cpu(hdr->type); 73562306a36Sopenharmony_ci if (type != wil_fw_type_data) { 73662306a36Sopenharmony_ci wil_err_fw(wil, 73762306a36Sopenharmony_ci "invalid record type for board file: %d\n", 73862306a36Sopenharmony_ci type); 73962306a36Sopenharmony_ci return -EINVAL; 74062306a36Sopenharmony_ci } 74162306a36Sopenharmony_ci if (hdr_sz < sizeof(struct wil_fw_record_data)) { 74262306a36Sopenharmony_ci wil_err_fw(wil, "data record too short: %zu\n", hdr_sz); 74362306a36Sopenharmony_ci return -EINVAL; 74462306a36Sopenharmony_ci } 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci wil_dbg_fw(wil, 74762306a36Sopenharmony_ci "using info from fw file for record %d: addr[0x%08x], max size %d\n", 74862306a36Sopenharmony_ci i, wil->brd_info[i].file_addr, 74962306a36Sopenharmony_ci wil->brd_info[i].file_max_size); 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci rc = __fw_handle_data(wil, &hdr[1], hdr_sz, 75262306a36Sopenharmony_ci cpu_to_le32(wil->brd_info[i].file_addr)); 75362306a36Sopenharmony_ci if (rc) 75462306a36Sopenharmony_ci return rc; 75562306a36Sopenharmony_ci } 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci if (size) { 75862306a36Sopenharmony_ci wil_err_fw(wil, "unprocessed bytes: %zu\n", size); 75962306a36Sopenharmony_ci if (size >= sizeof(*hdr)) { 76062306a36Sopenharmony_ci wil_err_fw(wil, 76162306a36Sopenharmony_ci "Stop at offset %ld record type %d [%zd bytes]\n", 76262306a36Sopenharmony_ci (long)((const void *)hdr - data), 76362306a36Sopenharmony_ci le16_to_cpu(hdr->type), hdr_sz); 76462306a36Sopenharmony_ci } 76562306a36Sopenharmony_ci return -EINVAL; 76662306a36Sopenharmony_ci } 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_ci return 0; 76962306a36Sopenharmony_ci} 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_ci/** 77262306a36Sopenharmony_ci * wil_request_board - Request board file 77362306a36Sopenharmony_ci * 77462306a36Sopenharmony_ci * Request board image from the file 77562306a36Sopenharmony_ci * board file address and max size are read from FW file 77662306a36Sopenharmony_ci * during initialization. 77762306a36Sopenharmony_ci * brd file shall include one header and one data section. 77862306a36Sopenharmony_ci * 77962306a36Sopenharmony_ci * Return error code 78062306a36Sopenharmony_ci */ 78162306a36Sopenharmony_ciint wil_request_board(struct wil6210_priv *wil, const char *name) 78262306a36Sopenharmony_ci{ 78362306a36Sopenharmony_ci int rc, dlen; 78462306a36Sopenharmony_ci const struct firmware *brd; 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_ci rc = request_firmware(&brd, name, wil_to_dev(wil)); 78762306a36Sopenharmony_ci if (rc) { 78862306a36Sopenharmony_ci wil_err_fw(wil, "Failed to load brd %s\n", name); 78962306a36Sopenharmony_ci return rc; 79062306a36Sopenharmony_ci } 79162306a36Sopenharmony_ci wil_dbg_fw(wil, "Loading <%s>, %zu bytes\n", name, brd->size); 79262306a36Sopenharmony_ci 79362306a36Sopenharmony_ci /* Verify the header */ 79462306a36Sopenharmony_ci dlen = wil_fw_verify(wil, brd->data, brd->size); 79562306a36Sopenharmony_ci if (dlen < 0) { 79662306a36Sopenharmony_ci rc = dlen; 79762306a36Sopenharmony_ci goto out; 79862306a36Sopenharmony_ci } 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_ci /* Process the data records */ 80162306a36Sopenharmony_ci rc = wil_brd_process(wil, brd->data, dlen); 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ciout: 80462306a36Sopenharmony_ci release_firmware(brd); 80562306a36Sopenharmony_ci if (rc) 80662306a36Sopenharmony_ci wil_err_fw(wil, "Loading <%s> failed, rc %d\n", name, rc); 80762306a36Sopenharmony_ci return rc; 80862306a36Sopenharmony_ci} 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_ci/** 81162306a36Sopenharmony_ci * wil_fw_verify_file_exists - checks if firmware file exist 81262306a36Sopenharmony_ci * 81362306a36Sopenharmony_ci * @wil: driver context 81462306a36Sopenharmony_ci * @name: firmware file name 81562306a36Sopenharmony_ci * 81662306a36Sopenharmony_ci * return value - boolean, true for success, false for failure 81762306a36Sopenharmony_ci */ 81862306a36Sopenharmony_cibool wil_fw_verify_file_exists(struct wil6210_priv *wil, const char *name) 81962306a36Sopenharmony_ci{ 82062306a36Sopenharmony_ci const struct firmware *fw; 82162306a36Sopenharmony_ci int rc; 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci rc = request_firmware(&fw, name, wil_to_dev(wil)); 82462306a36Sopenharmony_ci if (!rc) 82562306a36Sopenharmony_ci release_firmware(fw); 82662306a36Sopenharmony_ci else 82762306a36Sopenharmony_ci wil_dbg_fw(wil, "<%s> not available: %d\n", name, rc); 82862306a36Sopenharmony_ci return !rc; 82962306a36Sopenharmony_ci} 830