1*da8fa4e3SBjoern A. Zeeb // SPDX-License-Identifier: ISC
2*da8fa4e3SBjoern A. Zeeb /*
3*da8fa4e3SBjoern A. Zeeb * Copyright (c) 2015-2016 Qualcomm Atheros, Inc.
4*da8fa4e3SBjoern A. Zeeb */
5*da8fa4e3SBjoern A. Zeeb
6*da8fa4e3SBjoern A. Zeeb /* This file has implementation for code swap logic. With code swap feature,
7*da8fa4e3SBjoern A. Zeeb * target can run the fw binary with even smaller IRAM size by using host
8*da8fa4e3SBjoern A. Zeeb * memory to store some of the code segments.
9*da8fa4e3SBjoern A. Zeeb */
10*da8fa4e3SBjoern A. Zeeb
11*da8fa4e3SBjoern A. Zeeb #include "core.h"
12*da8fa4e3SBjoern A. Zeeb #include "bmi.h"
13*da8fa4e3SBjoern A. Zeeb #include "debug.h"
14*da8fa4e3SBjoern A. Zeeb
ath10k_swap_code_seg_fill(struct ath10k * ar,struct ath10k_swap_code_seg_info * seg_info,const void * data,size_t data_len)15*da8fa4e3SBjoern A. Zeeb static int ath10k_swap_code_seg_fill(struct ath10k *ar,
16*da8fa4e3SBjoern A. Zeeb struct ath10k_swap_code_seg_info *seg_info,
17*da8fa4e3SBjoern A. Zeeb const void *data, size_t data_len)
18*da8fa4e3SBjoern A. Zeeb {
19*da8fa4e3SBjoern A. Zeeb u8 *virt_addr = seg_info->virt_address[0];
20*da8fa4e3SBjoern A. Zeeb u8 swap_magic[ATH10K_SWAP_CODE_SEG_MAGIC_BYTES_SZ] = {};
21*da8fa4e3SBjoern A. Zeeb const u8 *fw_data = data;
22*da8fa4e3SBjoern A. Zeeb const union ath10k_swap_code_seg_item *swap_item;
23*da8fa4e3SBjoern A. Zeeb u32 length = 0;
24*da8fa4e3SBjoern A. Zeeb u32 payload_len;
25*da8fa4e3SBjoern A. Zeeb u32 total_payload_len = 0;
26*da8fa4e3SBjoern A. Zeeb u32 size_left = data_len;
27*da8fa4e3SBjoern A. Zeeb
28*da8fa4e3SBjoern A. Zeeb /* Parse swap bin and copy the content to host allocated memory.
29*da8fa4e3SBjoern A. Zeeb * The format is Address, length and value. The last 4-bytes is
30*da8fa4e3SBjoern A. Zeeb * target write address. Currently address field is not used.
31*da8fa4e3SBjoern A. Zeeb */
32*da8fa4e3SBjoern A. Zeeb seg_info->target_addr = -1;
33*da8fa4e3SBjoern A. Zeeb while (size_left >= sizeof(*swap_item)) {
34*da8fa4e3SBjoern A. Zeeb swap_item = (const union ath10k_swap_code_seg_item *)fw_data;
35*da8fa4e3SBjoern A. Zeeb payload_len = __le32_to_cpu(swap_item->tlv.length);
36*da8fa4e3SBjoern A. Zeeb if ((payload_len > size_left) ||
37*da8fa4e3SBjoern A. Zeeb (payload_len == 0 &&
38*da8fa4e3SBjoern A. Zeeb size_left != sizeof(struct ath10k_swap_code_seg_tail))) {
39*da8fa4e3SBjoern A. Zeeb ath10k_err(ar, "refusing to parse invalid tlv length %d\n",
40*da8fa4e3SBjoern A. Zeeb payload_len);
41*da8fa4e3SBjoern A. Zeeb return -EINVAL;
42*da8fa4e3SBjoern A. Zeeb }
43*da8fa4e3SBjoern A. Zeeb
44*da8fa4e3SBjoern A. Zeeb if (payload_len == 0) {
45*da8fa4e3SBjoern A. Zeeb if (memcmp(swap_item->tail.magic_signature, swap_magic,
46*da8fa4e3SBjoern A. Zeeb ATH10K_SWAP_CODE_SEG_MAGIC_BYTES_SZ)) {
47*da8fa4e3SBjoern A. Zeeb ath10k_err(ar, "refusing an invalid swap file\n");
48*da8fa4e3SBjoern A. Zeeb return -EINVAL;
49*da8fa4e3SBjoern A. Zeeb }
50*da8fa4e3SBjoern A. Zeeb seg_info->target_addr =
51*da8fa4e3SBjoern A. Zeeb __le32_to_cpu(swap_item->tail.bmi_write_addr);
52*da8fa4e3SBjoern A. Zeeb break;
53*da8fa4e3SBjoern A. Zeeb }
54*da8fa4e3SBjoern A. Zeeb
55*da8fa4e3SBjoern A. Zeeb memcpy(virt_addr, swap_item->tlv.data, payload_len);
56*da8fa4e3SBjoern A. Zeeb virt_addr += payload_len;
57*da8fa4e3SBjoern A. Zeeb length = payload_len + sizeof(struct ath10k_swap_code_seg_tlv);
58*da8fa4e3SBjoern A. Zeeb size_left -= length;
59*da8fa4e3SBjoern A. Zeeb fw_data += length;
60*da8fa4e3SBjoern A. Zeeb total_payload_len += payload_len;
61*da8fa4e3SBjoern A. Zeeb }
62*da8fa4e3SBjoern A. Zeeb
63*da8fa4e3SBjoern A. Zeeb if (seg_info->target_addr == -1) {
64*da8fa4e3SBjoern A. Zeeb ath10k_err(ar, "failed to parse invalid swap file\n");
65*da8fa4e3SBjoern A. Zeeb return -EINVAL;
66*da8fa4e3SBjoern A. Zeeb }
67*da8fa4e3SBjoern A. Zeeb seg_info->seg_hw_info.swap_size = __cpu_to_le32(total_payload_len);
68*da8fa4e3SBjoern A. Zeeb
69*da8fa4e3SBjoern A. Zeeb return 0;
70*da8fa4e3SBjoern A. Zeeb }
71*da8fa4e3SBjoern A. Zeeb
72*da8fa4e3SBjoern A. Zeeb static void
ath10k_swap_code_seg_free(struct ath10k * ar,struct ath10k_swap_code_seg_info * seg_info)73*da8fa4e3SBjoern A. Zeeb ath10k_swap_code_seg_free(struct ath10k *ar,
74*da8fa4e3SBjoern A. Zeeb struct ath10k_swap_code_seg_info *seg_info)
75*da8fa4e3SBjoern A. Zeeb {
76*da8fa4e3SBjoern A. Zeeb u32 seg_size;
77*da8fa4e3SBjoern A. Zeeb
78*da8fa4e3SBjoern A. Zeeb if (!seg_info)
79*da8fa4e3SBjoern A. Zeeb return;
80*da8fa4e3SBjoern A. Zeeb
81*da8fa4e3SBjoern A. Zeeb if (!seg_info->virt_address[0])
82*da8fa4e3SBjoern A. Zeeb return;
83*da8fa4e3SBjoern A. Zeeb
84*da8fa4e3SBjoern A. Zeeb seg_size = __le32_to_cpu(seg_info->seg_hw_info.size);
85*da8fa4e3SBjoern A. Zeeb dma_free_coherent(ar->dev, seg_size, seg_info->virt_address[0],
86*da8fa4e3SBjoern A. Zeeb seg_info->paddr[0]);
87*da8fa4e3SBjoern A. Zeeb }
88*da8fa4e3SBjoern A. Zeeb
89*da8fa4e3SBjoern A. Zeeb static struct ath10k_swap_code_seg_info *
ath10k_swap_code_seg_alloc(struct ath10k * ar,size_t swap_bin_len)90*da8fa4e3SBjoern A. Zeeb ath10k_swap_code_seg_alloc(struct ath10k *ar, size_t swap_bin_len)
91*da8fa4e3SBjoern A. Zeeb {
92*da8fa4e3SBjoern A. Zeeb struct ath10k_swap_code_seg_info *seg_info;
93*da8fa4e3SBjoern A. Zeeb void *virt_addr;
94*da8fa4e3SBjoern A. Zeeb dma_addr_t paddr;
95*da8fa4e3SBjoern A. Zeeb
96*da8fa4e3SBjoern A. Zeeb swap_bin_len = roundup(swap_bin_len, 2);
97*da8fa4e3SBjoern A. Zeeb if (swap_bin_len > ATH10K_SWAP_CODE_SEG_BIN_LEN_MAX) {
98*da8fa4e3SBjoern A. Zeeb ath10k_err(ar, "refusing code swap bin because it is too big %zu > %d\n",
99*da8fa4e3SBjoern A. Zeeb swap_bin_len, ATH10K_SWAP_CODE_SEG_BIN_LEN_MAX);
100*da8fa4e3SBjoern A. Zeeb return NULL;
101*da8fa4e3SBjoern A. Zeeb }
102*da8fa4e3SBjoern A. Zeeb
103*da8fa4e3SBjoern A. Zeeb seg_info = devm_kzalloc(ar->dev, sizeof(*seg_info), GFP_KERNEL);
104*da8fa4e3SBjoern A. Zeeb if (!seg_info)
105*da8fa4e3SBjoern A. Zeeb return NULL;
106*da8fa4e3SBjoern A. Zeeb
107*da8fa4e3SBjoern A. Zeeb virt_addr = dma_alloc_coherent(ar->dev, swap_bin_len, &paddr,
108*da8fa4e3SBjoern A. Zeeb GFP_KERNEL);
109*da8fa4e3SBjoern A. Zeeb if (!virt_addr)
110*da8fa4e3SBjoern A. Zeeb return NULL;
111*da8fa4e3SBjoern A. Zeeb
112*da8fa4e3SBjoern A. Zeeb seg_info->seg_hw_info.bus_addr[0] = __cpu_to_le32(paddr);
113*da8fa4e3SBjoern A. Zeeb seg_info->seg_hw_info.size = __cpu_to_le32(swap_bin_len);
114*da8fa4e3SBjoern A. Zeeb seg_info->seg_hw_info.swap_size = __cpu_to_le32(swap_bin_len);
115*da8fa4e3SBjoern A. Zeeb seg_info->seg_hw_info.num_segs =
116*da8fa4e3SBjoern A. Zeeb __cpu_to_le32(ATH10K_SWAP_CODE_SEG_NUM_SUPPORTED);
117*da8fa4e3SBjoern A. Zeeb seg_info->seg_hw_info.size_log2 = __cpu_to_le32(ilog2(swap_bin_len));
118*da8fa4e3SBjoern A. Zeeb seg_info->virt_address[0] = virt_addr;
119*da8fa4e3SBjoern A. Zeeb seg_info->paddr[0] = paddr;
120*da8fa4e3SBjoern A. Zeeb
121*da8fa4e3SBjoern A. Zeeb return seg_info;
122*da8fa4e3SBjoern A. Zeeb }
123*da8fa4e3SBjoern A. Zeeb
ath10k_swap_code_seg_configure(struct ath10k * ar,const struct ath10k_fw_file * fw_file)124*da8fa4e3SBjoern A. Zeeb int ath10k_swap_code_seg_configure(struct ath10k *ar,
125*da8fa4e3SBjoern A. Zeeb const struct ath10k_fw_file *fw_file)
126*da8fa4e3SBjoern A. Zeeb {
127*da8fa4e3SBjoern A. Zeeb int ret;
128*da8fa4e3SBjoern A. Zeeb struct ath10k_swap_code_seg_info *seg_info = NULL;
129*da8fa4e3SBjoern A. Zeeb
130*da8fa4e3SBjoern A. Zeeb if (!fw_file->firmware_swap_code_seg_info)
131*da8fa4e3SBjoern A. Zeeb return 0;
132*da8fa4e3SBjoern A. Zeeb
133*da8fa4e3SBjoern A. Zeeb ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot found firmware code swap binary\n");
134*da8fa4e3SBjoern A. Zeeb
135*da8fa4e3SBjoern A. Zeeb seg_info = fw_file->firmware_swap_code_seg_info;
136*da8fa4e3SBjoern A. Zeeb
137*da8fa4e3SBjoern A. Zeeb ret = ath10k_bmi_write_memory(ar, seg_info->target_addr,
138*da8fa4e3SBjoern A. Zeeb #if defined(__linux__)
139*da8fa4e3SBjoern A. Zeeb &seg_info->seg_hw_info,
140*da8fa4e3SBjoern A. Zeeb #elif defined(__FreeBSD__)
141*da8fa4e3SBjoern A. Zeeb (void *)&seg_info->seg_hw_info,
142*da8fa4e3SBjoern A. Zeeb #endif
143*da8fa4e3SBjoern A. Zeeb sizeof(seg_info->seg_hw_info));
144*da8fa4e3SBjoern A. Zeeb if (ret) {
145*da8fa4e3SBjoern A. Zeeb ath10k_err(ar, "failed to write Code swap segment information (%d)\n",
146*da8fa4e3SBjoern A. Zeeb ret);
147*da8fa4e3SBjoern A. Zeeb return ret;
148*da8fa4e3SBjoern A. Zeeb }
149*da8fa4e3SBjoern A. Zeeb
150*da8fa4e3SBjoern A. Zeeb return 0;
151*da8fa4e3SBjoern A. Zeeb }
152*da8fa4e3SBjoern A. Zeeb
ath10k_swap_code_seg_release(struct ath10k * ar,struct ath10k_fw_file * fw_file)153*da8fa4e3SBjoern A. Zeeb void ath10k_swap_code_seg_release(struct ath10k *ar,
154*da8fa4e3SBjoern A. Zeeb struct ath10k_fw_file *fw_file)
155*da8fa4e3SBjoern A. Zeeb {
156*da8fa4e3SBjoern A. Zeeb ath10k_swap_code_seg_free(ar, fw_file->firmware_swap_code_seg_info);
157*da8fa4e3SBjoern A. Zeeb
158*da8fa4e3SBjoern A. Zeeb /* FIXME: these two assignments look to bein wrong place! Shouldn't
159*da8fa4e3SBjoern A. Zeeb * they be in ath10k_core_free_firmware_files() like the rest?
160*da8fa4e3SBjoern A. Zeeb */
161*da8fa4e3SBjoern A. Zeeb fw_file->codeswap_data = NULL;
162*da8fa4e3SBjoern A. Zeeb fw_file->codeswap_len = 0;
163*da8fa4e3SBjoern A. Zeeb
164*da8fa4e3SBjoern A. Zeeb fw_file->firmware_swap_code_seg_info = NULL;
165*da8fa4e3SBjoern A. Zeeb }
166*da8fa4e3SBjoern A. Zeeb
ath10k_swap_code_seg_init(struct ath10k * ar,struct ath10k_fw_file * fw_file)167*da8fa4e3SBjoern A. Zeeb int ath10k_swap_code_seg_init(struct ath10k *ar, struct ath10k_fw_file *fw_file)
168*da8fa4e3SBjoern A. Zeeb {
169*da8fa4e3SBjoern A. Zeeb int ret;
170*da8fa4e3SBjoern A. Zeeb struct ath10k_swap_code_seg_info *seg_info;
171*da8fa4e3SBjoern A. Zeeb const void *codeswap_data;
172*da8fa4e3SBjoern A. Zeeb size_t codeswap_len;
173*da8fa4e3SBjoern A. Zeeb
174*da8fa4e3SBjoern A. Zeeb codeswap_data = fw_file->codeswap_data;
175*da8fa4e3SBjoern A. Zeeb codeswap_len = fw_file->codeswap_len;
176*da8fa4e3SBjoern A. Zeeb
177*da8fa4e3SBjoern A. Zeeb if (!codeswap_len || !codeswap_data)
178*da8fa4e3SBjoern A. Zeeb return 0;
179*da8fa4e3SBjoern A. Zeeb
180*da8fa4e3SBjoern A. Zeeb seg_info = ath10k_swap_code_seg_alloc(ar, codeswap_len);
181*da8fa4e3SBjoern A. Zeeb if (!seg_info) {
182*da8fa4e3SBjoern A. Zeeb ath10k_err(ar, "failed to allocate fw code swap segment\n");
183*da8fa4e3SBjoern A. Zeeb return -ENOMEM;
184*da8fa4e3SBjoern A. Zeeb }
185*da8fa4e3SBjoern A. Zeeb
186*da8fa4e3SBjoern A. Zeeb ret = ath10k_swap_code_seg_fill(ar, seg_info,
187*da8fa4e3SBjoern A. Zeeb codeswap_data, codeswap_len);
188*da8fa4e3SBjoern A. Zeeb
189*da8fa4e3SBjoern A. Zeeb if (ret) {
190*da8fa4e3SBjoern A. Zeeb ath10k_warn(ar, "failed to initialize fw code swap segment: %d\n",
191*da8fa4e3SBjoern A. Zeeb ret);
192*da8fa4e3SBjoern A. Zeeb ath10k_swap_code_seg_free(ar, seg_info);
193*da8fa4e3SBjoern A. Zeeb return ret;
194*da8fa4e3SBjoern A. Zeeb }
195*da8fa4e3SBjoern A. Zeeb
196*da8fa4e3SBjoern A. Zeeb fw_file->firmware_swap_code_seg_info = seg_info;
197*da8fa4e3SBjoern A. Zeeb
198*da8fa4e3SBjoern A. Zeeb return 0;
199*da8fa4e3SBjoern A. Zeeb }
200