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