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
16struct iwl_pnvm_section {
17	__le32 offset;
18	const u8 data[];
19} __packed;
20
21static 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
34static 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
150done:
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);
169out:
170	kfree(pnvm_data);
171	return ret;
172}
173
174static 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
231int 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}
292IWL_EXPORT_SYMBOL(iwl_pnvm_load);
293