xref: /freebsd/sys/contrib/dev/athk/ath10k/swap.c (revision da8fa4e37a0c048a67d7baa3b5a9bed637d02564)
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