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