• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 /******************************************************************************
3  *
4  * Copyright(c) 2020 Intel Corporation
5  *
6  *****************************************************************************/
7 
8 #include "iwl-drv.h"
9 #include "pnvm.h"
10 #include "iwl-prph.h"
11 #include "iwl-io.h"
12 #include "fw/api/commands.h"
13 #include "fw/api/nvm-reg.h"
14 #include "fw/api/alive.h"
15 
16 struct iwl_pnvm_section {
17 	__le32 offset;
18 	const u8 data[];
19 } __packed;
20 
iwl_pnvm_complete_fn(struct iwl_notif_wait_data * notif_wait,struct iwl_rx_packet * pkt,void * data)21 static bool iwl_pnvm_complete_fn(struct iwl_notif_wait_data *notif_wait,
22 				 struct iwl_rx_packet *pkt, void *data)
23 {
24 	struct iwl_trans *trans = (struct iwl_trans *)data;
25 	struct iwl_pnvm_init_complete_ntfy *pnvm_ntf = (void *)pkt->data;
26 
27 	IWL_DEBUG_FW(trans,
28 		     "PNVM complete notification received with status %d\n",
29 		     le32_to_cpu(pnvm_ntf->status));
30 
31 	return true;
32 }
33 
iwl_pnvm_handle_section(struct iwl_trans * trans,const u8 * data,size_t len)34 static int iwl_pnvm_handle_section(struct iwl_trans *trans, const u8 *data,
35 				   size_t len)
36 {
37 	struct iwl_ucode_tlv *tlv;
38 	u32 sha1 = 0;
39 	u16 mac_type = 0, rf_id = 0;
40 	u8 *pnvm_data = NULL, *tmp;
41 	bool hw_match = false;
42 	u32 size = 0;
43 	int ret;
44 
45 	IWL_DEBUG_FW(trans, "Handling PNVM section\n");
46 
47 	while (len >= sizeof(*tlv)) {
48 		u32 tlv_len, tlv_type;
49 
50 		len -= sizeof(*tlv);
51 		tlv = (void *)data;
52 
53 		tlv_len = le32_to_cpu(tlv->length);
54 		tlv_type = le32_to_cpu(tlv->type);
55 
56 		if (len < tlv_len) {
57 			IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
58 				len, tlv_len);
59 			ret = -EINVAL;
60 			goto out;
61 		}
62 
63 		data += sizeof(*tlv);
64 
65 		switch (tlv_type) {
66 		case IWL_UCODE_TLV_PNVM_VERSION:
67 			if (tlv_len < sizeof(__le32)) {
68 				IWL_DEBUG_FW(trans,
69 					     "Invalid size for IWL_UCODE_TLV_PNVM_VERSION (expected %zd, got %d)\n",
70 					     sizeof(__le32), tlv_len);
71 				break;
72 			}
73 
74 			sha1 = le32_to_cpup((__le32 *)data);
75 
76 			IWL_DEBUG_FW(trans,
77 				     "Got IWL_UCODE_TLV_PNVM_VERSION %0x\n",
78 				     sha1);
79 			break;
80 		case IWL_UCODE_TLV_HW_TYPE:
81 			if (tlv_len < 2 * sizeof(__le16)) {
82 				IWL_DEBUG_FW(trans,
83 					     "Invalid size for IWL_UCODE_TLV_HW_TYPE (expected %zd, got %d)\n",
84 					     2 * sizeof(__le16), tlv_len);
85 				break;
86 			}
87 
88 			if (hw_match)
89 				break;
90 
91 			mac_type = le16_to_cpup((__le16 *)data);
92 			rf_id = le16_to_cpup((__le16 *)(data + sizeof(__le16)));
93 
94 			IWL_DEBUG_FW(trans,
95 				     "Got IWL_UCODE_TLV_HW_TYPE mac_type 0x%0x rf_id 0x%0x\n",
96 				     mac_type, rf_id);
97 
98 			if (mac_type == CSR_HW_REV_TYPE(trans->hw_rev) &&
99 			    rf_id == CSR_HW_RFID_TYPE(trans->hw_rf_id))
100 				hw_match = true;
101 			break;
102 		case IWL_UCODE_TLV_SEC_RT: {
103 			struct iwl_pnvm_section *section = (void *)data;
104 			u32 data_len = tlv_len - sizeof(*section);
105 
106 			IWL_DEBUG_FW(trans,
107 				     "Got IWL_UCODE_TLV_SEC_RT len %d\n",
108 				     tlv_len);
109 
110 			/* TODO: remove, this is a deprecated separator */
111 			if (le32_to_cpup((__le32 *)data) == 0xddddeeee) {
112 				IWL_DEBUG_FW(trans, "Ignoring separator.\n");
113 				break;
114 			}
115 
116 			IWL_DEBUG_FW(trans, "Adding data (size %d)\n",
117 				     data_len);
118 
119 			tmp = krealloc(pnvm_data, size + data_len, GFP_KERNEL);
120 			if (!tmp) {
121 				IWL_DEBUG_FW(trans,
122 					     "Couldn't allocate (more) pnvm_data\n");
123 
124 				ret = -ENOMEM;
125 				goto out;
126 			}
127 
128 			pnvm_data = tmp;
129 
130 			memcpy(pnvm_data + size, section->data, data_len);
131 
132 			size += data_len;
133 
134 			break;
135 		}
136 		case IWL_UCODE_TLV_PNVM_SKU:
137 			IWL_DEBUG_FW(trans,
138 				     "New PNVM section started, stop parsing.\n");
139 			goto done;
140 		default:
141 			IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n",
142 				     tlv_type, tlv_len);
143 			break;
144 		}
145 
146 		len -= ALIGN(tlv_len, 4);
147 		data += ALIGN(tlv_len, 4);
148 	}
149 
150 done:
151 	if (!hw_match) {
152 		IWL_DEBUG_FW(trans,
153 			     "HW mismatch, skipping PNVM section (need mac_type 0x%x rf_id 0x%x)\n",
154 			     CSR_HW_REV_TYPE(trans->hw_rev),
155 			     CSR_HW_RFID_TYPE(trans->hw_rf_id));
156 		ret = -ENOENT;
157 		goto out;
158 	}
159 
160 	if (!size) {
161 		IWL_DEBUG_FW(trans, "Empty PNVM, skipping.\n");
162 		ret = -ENOENT;
163 		goto out;
164 	}
165 
166 	IWL_INFO(trans, "loaded PNVM version 0x%0x\n", sha1);
167 
168 	ret = iwl_trans_set_pnvm(trans, pnvm_data, size);
169 out:
170 	kfree(pnvm_data);
171 	return ret;
172 }
173 
iwl_pnvm_parse(struct iwl_trans * trans,const u8 * data,size_t len)174 static int iwl_pnvm_parse(struct iwl_trans *trans, const u8 *data,
175 			  size_t len)
176 {
177 	struct iwl_ucode_tlv *tlv;
178 
179 	IWL_DEBUG_FW(trans, "Parsing PNVM file\n");
180 
181 	while (len >= sizeof(*tlv)) {
182 		u32 tlv_len, tlv_type;
183 
184 		len -= sizeof(*tlv);
185 		tlv = (void *)data;
186 
187 		tlv_len = le32_to_cpu(tlv->length);
188 		tlv_type = le32_to_cpu(tlv->type);
189 
190 		if (len < tlv_len) {
191 			IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
192 				len, tlv_len);
193 			return -EINVAL;
194 		}
195 
196 		if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) {
197 			struct iwl_sku_id *sku_id =
198 				(void *)(data + sizeof(*tlv));
199 
200 			IWL_DEBUG_FW(trans,
201 				     "Got IWL_UCODE_TLV_PNVM_SKU len %d\n",
202 				     tlv_len);
203 			IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n",
204 				     le32_to_cpu(sku_id->data[0]),
205 				     le32_to_cpu(sku_id->data[1]),
206 				     le32_to_cpu(sku_id->data[2]));
207 
208 			data += sizeof(*tlv) + ALIGN(tlv_len, 4);
209 			len -= ALIGN(tlv_len, 4);
210 
211 			if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) &&
212 			    trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) &&
213 			    trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) {
214 				int ret;
215 
216 				ret = iwl_pnvm_handle_section(trans, data, len);
217 				if (!ret)
218 					return 0;
219 			} else {
220 				IWL_DEBUG_FW(trans, "SKU ID didn't match!\n");
221 			}
222 		} else {
223 			data += sizeof(*tlv) + ALIGN(tlv_len, 4);
224 			len -= ALIGN(tlv_len, 4);
225 		}
226 	}
227 
228 	return -ENOENT;
229 }
230 
iwl_pnvm_load(struct iwl_trans * trans,struct iwl_notif_wait_data * notif_wait)231 int iwl_pnvm_load(struct iwl_trans *trans,
232 		  struct iwl_notif_wait_data *notif_wait)
233 {
234 	struct iwl_notification_wait pnvm_wait;
235 	static const u16 ntf_cmds[] = { WIDE_ID(REGULATORY_AND_NVM_GROUP,
236 						PNVM_INIT_COMPLETE_NTFY) };
237 	int ret;
238 
239 	/* if the SKU_ID is empty, there's nothing to do */
240 	if (!trans->sku_id[0] && !trans->sku_id[1] && !trans->sku_id[2])
241 		return 0;
242 
243 	/* load from disk only if we haven't done it (or tried) before */
244 	if (!trans->pnvm_loaded) {
245 		const struct firmware *pnvm;
246 		char pnvm_name[64];
247 
248 		/*
249 		 * The prefix unfortunately includes a hyphen at the end, so
250 		 * don't add the dot here...
251 		 */
252 		snprintf(pnvm_name, sizeof(pnvm_name), "%spnvm",
253 			 trans->cfg->fw_name_pre);
254 
255 		/* ...but replace the hyphen with the dot here. */
256 		if (strlen(trans->cfg->fw_name_pre) < sizeof(pnvm_name))
257 			pnvm_name[strlen(trans->cfg->fw_name_pre) - 1] = '.';
258 
259 		ret = firmware_request_nowarn(&pnvm, pnvm_name, trans->dev);
260 		if (ret) {
261 			IWL_DEBUG_FW(trans, "PNVM file %s not found %d\n",
262 				     pnvm_name, ret);
263 			/*
264 			 * Pretend we've loaded it - at least we've tried and
265 			 * couldn't load it at all, so there's no point in
266 			 * trying again over and over.
267 			 */
268 			trans->pnvm_loaded = true;
269 		} else {
270 			iwl_pnvm_parse(trans, pnvm->data, pnvm->size);
271 
272 			release_firmware(pnvm);
273 		}
274 	} else {
275 		/* if we already loaded, we need to set it again */
276 		ret = iwl_trans_set_pnvm(trans, NULL, 0);
277 		if (ret)
278 			return ret;
279 	}
280 
281 	iwl_init_notification_wait(notif_wait, &pnvm_wait,
282 				   ntf_cmds, ARRAY_SIZE(ntf_cmds),
283 				   iwl_pnvm_complete_fn, trans);
284 
285 	/* kick the doorbell */
286 	iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6,
287 			    UREG_DOORBELL_TO_ISR6_PNVM);
288 
289 	return iwl_wait_notification(notif_wait, &pnvm_wait,
290 				     MVM_UCODE_PNVM_TIMEOUT);
291 }
292 IWL_EXPORT_SYMBOL(iwl_pnvm_load);
293