162306a36Sopenharmony_ci// SPDX-License-Identifier: ISC 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2015-2016 Qualcomm Atheros, Inc. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci/* This file has implementation for code swap logic. With code swap feature, 762306a36Sopenharmony_ci * target can run the fw binary with even smaller IRAM size by using host 862306a36Sopenharmony_ci * memory to store some of the code segments. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include "core.h" 1262306a36Sopenharmony_ci#include "bmi.h" 1362306a36Sopenharmony_ci#include "debug.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cistatic int ath10k_swap_code_seg_fill(struct ath10k *ar, 1662306a36Sopenharmony_ci struct ath10k_swap_code_seg_info *seg_info, 1762306a36Sopenharmony_ci const void *data, size_t data_len) 1862306a36Sopenharmony_ci{ 1962306a36Sopenharmony_ci u8 *virt_addr = seg_info->virt_address[0]; 2062306a36Sopenharmony_ci u8 swap_magic[ATH10K_SWAP_CODE_SEG_MAGIC_BYTES_SZ] = {}; 2162306a36Sopenharmony_ci const u8 *fw_data = data; 2262306a36Sopenharmony_ci union ath10k_swap_code_seg_item *swap_item; 2362306a36Sopenharmony_ci u32 length = 0; 2462306a36Sopenharmony_ci u32 payload_len; 2562306a36Sopenharmony_ci u32 total_payload_len = 0; 2662306a36Sopenharmony_ci u32 size_left = data_len; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci /* Parse swap bin and copy the content to host allocated memory. 2962306a36Sopenharmony_ci * The format is Address, length and value. The last 4-bytes is 3062306a36Sopenharmony_ci * target write address. Currently address field is not used. 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_ci seg_info->target_addr = -1; 3362306a36Sopenharmony_ci while (size_left >= sizeof(*swap_item)) { 3462306a36Sopenharmony_ci swap_item = (union ath10k_swap_code_seg_item *)fw_data; 3562306a36Sopenharmony_ci payload_len = __le32_to_cpu(swap_item->tlv.length); 3662306a36Sopenharmony_ci if ((payload_len > size_left) || 3762306a36Sopenharmony_ci (payload_len == 0 && 3862306a36Sopenharmony_ci size_left != sizeof(struct ath10k_swap_code_seg_tail))) { 3962306a36Sopenharmony_ci ath10k_err(ar, "refusing to parse invalid tlv length %d\n", 4062306a36Sopenharmony_ci payload_len); 4162306a36Sopenharmony_ci return -EINVAL; 4262306a36Sopenharmony_ci } 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci if (payload_len == 0) { 4562306a36Sopenharmony_ci if (memcmp(swap_item->tail.magic_signature, swap_magic, 4662306a36Sopenharmony_ci ATH10K_SWAP_CODE_SEG_MAGIC_BYTES_SZ)) { 4762306a36Sopenharmony_ci ath10k_err(ar, "refusing an invalid swap file\n"); 4862306a36Sopenharmony_ci return -EINVAL; 4962306a36Sopenharmony_ci } 5062306a36Sopenharmony_ci seg_info->target_addr = 5162306a36Sopenharmony_ci __le32_to_cpu(swap_item->tail.bmi_write_addr); 5262306a36Sopenharmony_ci break; 5362306a36Sopenharmony_ci } 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci memcpy(virt_addr, swap_item->tlv.data, payload_len); 5662306a36Sopenharmony_ci virt_addr += payload_len; 5762306a36Sopenharmony_ci length = payload_len + sizeof(struct ath10k_swap_code_seg_tlv); 5862306a36Sopenharmony_ci size_left -= length; 5962306a36Sopenharmony_ci fw_data += length; 6062306a36Sopenharmony_ci total_payload_len += payload_len; 6162306a36Sopenharmony_ci } 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (seg_info->target_addr == -1) { 6462306a36Sopenharmony_ci ath10k_err(ar, "failed to parse invalid swap file\n"); 6562306a36Sopenharmony_ci return -EINVAL; 6662306a36Sopenharmony_ci } 6762306a36Sopenharmony_ci seg_info->seg_hw_info.swap_size = __cpu_to_le32(total_payload_len); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci return 0; 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic void 7362306a36Sopenharmony_ciath10k_swap_code_seg_free(struct ath10k *ar, 7462306a36Sopenharmony_ci struct ath10k_swap_code_seg_info *seg_info) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci u32 seg_size; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci if (!seg_info) 7962306a36Sopenharmony_ci return; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci if (!seg_info->virt_address[0]) 8262306a36Sopenharmony_ci return; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci seg_size = __le32_to_cpu(seg_info->seg_hw_info.size); 8562306a36Sopenharmony_ci dma_free_coherent(ar->dev, seg_size, seg_info->virt_address[0], 8662306a36Sopenharmony_ci seg_info->paddr[0]); 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic struct ath10k_swap_code_seg_info * 9062306a36Sopenharmony_ciath10k_swap_code_seg_alloc(struct ath10k *ar, size_t swap_bin_len) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci struct ath10k_swap_code_seg_info *seg_info; 9362306a36Sopenharmony_ci void *virt_addr; 9462306a36Sopenharmony_ci dma_addr_t paddr; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci swap_bin_len = roundup(swap_bin_len, 2); 9762306a36Sopenharmony_ci if (swap_bin_len > ATH10K_SWAP_CODE_SEG_BIN_LEN_MAX) { 9862306a36Sopenharmony_ci ath10k_err(ar, "refusing code swap bin because it is too big %zu > %d\n", 9962306a36Sopenharmony_ci swap_bin_len, ATH10K_SWAP_CODE_SEG_BIN_LEN_MAX); 10062306a36Sopenharmony_ci return NULL; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci seg_info = devm_kzalloc(ar->dev, sizeof(*seg_info), GFP_KERNEL); 10462306a36Sopenharmony_ci if (!seg_info) 10562306a36Sopenharmony_ci return NULL; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci virt_addr = dma_alloc_coherent(ar->dev, swap_bin_len, &paddr, 10862306a36Sopenharmony_ci GFP_KERNEL); 10962306a36Sopenharmony_ci if (!virt_addr) 11062306a36Sopenharmony_ci return NULL; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci seg_info->seg_hw_info.bus_addr[0] = __cpu_to_le32(paddr); 11362306a36Sopenharmony_ci seg_info->seg_hw_info.size = __cpu_to_le32(swap_bin_len); 11462306a36Sopenharmony_ci seg_info->seg_hw_info.swap_size = __cpu_to_le32(swap_bin_len); 11562306a36Sopenharmony_ci seg_info->seg_hw_info.num_segs = 11662306a36Sopenharmony_ci __cpu_to_le32(ATH10K_SWAP_CODE_SEG_NUM_SUPPORTED); 11762306a36Sopenharmony_ci seg_info->seg_hw_info.size_log2 = __cpu_to_le32(ilog2(swap_bin_len)); 11862306a36Sopenharmony_ci seg_info->virt_address[0] = virt_addr; 11962306a36Sopenharmony_ci seg_info->paddr[0] = paddr; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci return seg_info; 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ciint ath10k_swap_code_seg_configure(struct ath10k *ar, 12562306a36Sopenharmony_ci const struct ath10k_fw_file *fw_file) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci int ret; 12862306a36Sopenharmony_ci struct ath10k_swap_code_seg_info *seg_info = NULL; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci if (!fw_file->firmware_swap_code_seg_info) 13162306a36Sopenharmony_ci return 0; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot found firmware code swap binary\n"); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci seg_info = fw_file->firmware_swap_code_seg_info; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci ret = ath10k_bmi_write_memory(ar, seg_info->target_addr, 13862306a36Sopenharmony_ci &seg_info->seg_hw_info, 13962306a36Sopenharmony_ci sizeof(seg_info->seg_hw_info)); 14062306a36Sopenharmony_ci if (ret) { 14162306a36Sopenharmony_ci ath10k_err(ar, "failed to write Code swap segment information (%d)\n", 14262306a36Sopenharmony_ci ret); 14362306a36Sopenharmony_ci return ret; 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci return 0; 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_civoid ath10k_swap_code_seg_release(struct ath10k *ar, 15062306a36Sopenharmony_ci struct ath10k_fw_file *fw_file) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci ath10k_swap_code_seg_free(ar, fw_file->firmware_swap_code_seg_info); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci /* FIXME: these two assignments look to bein wrong place! Shouldn't 15562306a36Sopenharmony_ci * they be in ath10k_core_free_firmware_files() like the rest? 15662306a36Sopenharmony_ci */ 15762306a36Sopenharmony_ci fw_file->codeswap_data = NULL; 15862306a36Sopenharmony_ci fw_file->codeswap_len = 0; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci fw_file->firmware_swap_code_seg_info = NULL; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ciint ath10k_swap_code_seg_init(struct ath10k *ar, struct ath10k_fw_file *fw_file) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci int ret; 16662306a36Sopenharmony_ci struct ath10k_swap_code_seg_info *seg_info; 16762306a36Sopenharmony_ci const void *codeswap_data; 16862306a36Sopenharmony_ci size_t codeswap_len; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci codeswap_data = fw_file->codeswap_data; 17162306a36Sopenharmony_ci codeswap_len = fw_file->codeswap_len; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci if (!codeswap_len || !codeswap_data) 17462306a36Sopenharmony_ci return 0; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci seg_info = ath10k_swap_code_seg_alloc(ar, codeswap_len); 17762306a36Sopenharmony_ci if (!seg_info) { 17862306a36Sopenharmony_ci ath10k_err(ar, "failed to allocate fw code swap segment\n"); 17962306a36Sopenharmony_ci return -ENOMEM; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci ret = ath10k_swap_code_seg_fill(ar, seg_info, 18362306a36Sopenharmony_ci codeswap_data, codeswap_len); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci if (ret) { 18662306a36Sopenharmony_ci ath10k_warn(ar, "failed to initialize fw code swap segment: %d\n", 18762306a36Sopenharmony_ci ret); 18862306a36Sopenharmony_ci ath10k_swap_code_seg_free(ar, seg_info); 18962306a36Sopenharmony_ci return ret; 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci fw_file->firmware_swap_code_seg_info = seg_info; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci return 0; 19562306a36Sopenharmony_ci} 196