File Index
Files and directories
Sign in
FILE

pnvm.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c C
  1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
  2/*
  3 * Copyright(c) 2020-2025 Intel Corporation
  4 */
  5
  6#include "iwl-drv.h"
  7#include "pnvm.h"
  8#include "iwl-prph.h"
  9#include "iwl-io.h"
 10#include "fw/api/commands.h"
 11#include "fw/api/nvm-reg.h"
 12#include "fw/api/alive.h"
 13#include "fw/uefi.h"
 14#include "fw/img.h"
 15
 16#define IWL_PNVM_REDUCED_CAP_BIT BIT(25)
 17
 18struct iwl_pnvm_section {
 19	__le32 offset;
 20	const u8 data[];
 21} __packed;
 22
 23static bool iwl_pnvm_complete_fn(struct iwl_notif_wait_data *notif_wait,
 24				 struct iwl_rx_packet *pkt, void *data)
 25{
 26	struct iwl_trans *trans = (struct iwl_trans *)data;
 27	struct iwl_pnvm_init_complete_ntfy *pnvm_ntf = (void *)pkt->data;
 28
 29	IWL_DEBUG_FW(trans,
 30		     "PNVM complete notification received with status 0x%0x\n",
 31		     le32_to_cpu(pnvm_ntf->status));
 32
 33	return true;
 34}
 35
 36static int iwl_pnvm_handle_section(struct iwl_trans *trans, const u8 *data,
 37				   size_t len,
 38				   struct iwl_pnvm_image *pnvm_data)
 39{
 40	const struct iwl_ucode_tlv *tlv;
 41	u32 sha1 = 0;
 42	u16 mac_type = 0, rf_id = 0;
 43	bool hw_match = false;
 44
 45	IWL_DEBUG_FW(trans, "Handling PNVM section\n");
 46
 47	memset(pnvm_data, 0, sizeof(*pnvm_data));
 48
 49	while (len >= sizeof(*tlv)) {
 50		u32 tlv_len, tlv_type;
 51
 52		len -= sizeof(*tlv);
 53		tlv = (const void *)data;
 54
 55		tlv_len = le32_to_cpu(tlv->length);
 56		tlv_type = le32_to_cpu(tlv->type);
 57
 58		if (len < tlv_len) {
 59			IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
 60				len, tlv_len);
 61			return -EINVAL;
 62		}
 63
 64		data += sizeof(*tlv);
 65
 66		switch (tlv_type) {
 67		case IWL_UCODE_TLV_PNVM_VERSION:
 68			if (tlv_len < sizeof(__le32)) {
 69				IWL_DEBUG_FW(trans,
 70					     "Invalid size for IWL_UCODE_TLV_PNVM_VERSION (expected %zd, got %d)\n",
 71					     sizeof(__le32), tlv_len);
 72				break;
 73			}
 74
 75			sha1 = le32_to_cpup((const __le32 *)data);
 76
 77			IWL_DEBUG_FW(trans,
 78				     "Got IWL_UCODE_TLV_PNVM_VERSION %0x\n",
 79				     sha1);
 80			pnvm_data->version = sha1;
 81			break;
 82		case IWL_UCODE_TLV_HW_TYPE:
 83			if (tlv_len < 2 * sizeof(__le16)) {
 84				IWL_DEBUG_FW(trans,
 85					     "Invalid size for IWL_UCODE_TLV_HW_TYPE (expected %zd, got %d)\n",
 86					     2 * sizeof(__le16), tlv_len);
 87				break;
 88			}
 89
 90			if (hw_match)
 91				break;
 92
 93			mac_type = le16_to_cpup((const __le16 *)data);
 94			rf_id = le16_to_cpup((const __le16 *)(data + sizeof(__le16)));
 95
 96			IWL_DEBUG_FW(trans,
 97				     "Got IWL_UCODE_TLV_HW_TYPE mac_type 0x%0x rf_id 0x%0x\n",
 98				     mac_type, rf_id);
 99
100			if (mac_type == CSR_HW_REV_TYPE(trans->info.hw_rev) &&
101			    rf_id == CSR_HW_RFID_TYPE(trans->info.hw_rf_id))
102				hw_match = true;
103			break;
104		case IWL_UCODE_TLV_SEC_RT: {
105			const struct iwl_pnvm_section *section = (const void *)data;
106			u32 data_len = tlv_len - sizeof(*section);
107
108			IWL_DEBUG_FW(trans,
109				     "Got IWL_UCODE_TLV_SEC_RT len %d\n",
110				     tlv_len);
111
112			/* TODO: remove, this is a deprecated separator */
113			if (le32_to_cpup((const __le32 *)data) == 0xddddeeee) {
114				IWL_DEBUG_FW(trans, "Ignoring separator.\n");
115				break;
116			}
117
118			if (pnvm_data->n_chunks == IPC_DRAM_MAP_ENTRY_NUM_MAX) {
119				IWL_DEBUG_FW(trans,
120					     "too many payloads to allocate in DRAM.\n");
121				return -EINVAL;
122			}
123
124			IWL_DEBUG_FW(trans, "Adding data (size %d)\n",
125				     data_len);
126
127			pnvm_data->chunks[pnvm_data->n_chunks].data = section->data;
128			pnvm_data->chunks[pnvm_data->n_chunks].len = data_len;
129			pnvm_data->n_chunks++;
130
131			break;
132		}
133		case IWL_UCODE_TLV_MEM_DESC:
134			if (iwl_uefi_handle_tlv_mem_desc(trans, data, tlv_len,
135							 pnvm_data))
136				return -EINVAL;
137			break;
138		case IWL_UCODE_TLV_PNVM_SKU:
139			IWL_DEBUG_FW(trans,
140				     "New PNVM section started, stop parsing.\n");
141			goto done;
142		default:
143			IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n",
144				     tlv_type, tlv_len);
145			break;
146		}
147
148		len -= ALIGN(tlv_len, 4);
149		data += ALIGN(tlv_len, 4);
150	}
151
152done:
153	if (!hw_match) {
154		IWL_DEBUG_FW(trans,
155			     "HW mismatch, skipping PNVM section (need mac_type 0x%x rf_id 0x%x)\n",
156			     CSR_HW_REV_TYPE(trans->info.hw_rev),
157			     CSR_HW_RFID_TYPE(trans->info.hw_rf_id));
158		return -ENOENT;
159	}
160
161	if (!pnvm_data->n_chunks) {
162		IWL_DEBUG_FW(trans, "Empty PNVM, skipping.\n");
163		return -ENOENT;
164	}
165
166	return 0;
167}
168
169static int iwl_pnvm_parse(struct iwl_trans *trans, const u8 *data,
170			  size_t len,
171			  struct iwl_pnvm_image *pnvm_data,
172			  __le32 sku_id[3])
173{
174	const struct iwl_ucode_tlv *tlv;
175
176	IWL_DEBUG_FW(trans, "Parsing PNVM file\n");
177
178	while (len >= sizeof(*tlv)) {
179		u32 tlv_len, tlv_type;
180		u32 rf_type;
181
182		len -= sizeof(*tlv);
183		tlv = (const void *)data;
184
185		tlv_len = le32_to_cpu(tlv->length);
186		tlv_type = le32_to_cpu(tlv->type);
187
188		if (len < tlv_len) {
189			IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
190				len, tlv_len);
191			return -EINVAL;
192		}
193
194		if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) {
195			const struct iwl_sku_id *tlv_sku_id =
196				(const void *)(data + sizeof(*tlv));
197
198			IWL_DEBUG_FW(trans,
199				     "Got IWL_UCODE_TLV_PNVM_SKU len %d\n",
200				     tlv_len);
201			IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n",
202				     le32_to_cpu(tlv_sku_id->data[0]),
203				     le32_to_cpu(tlv_sku_id->data[1]),
204				     le32_to_cpu(tlv_sku_id->data[2]));
205
206			data += sizeof(*tlv) + ALIGN(tlv_len, 4);
207			len -= ALIGN(tlv_len, 4);
208
209			trans->reduced_cap_sku = false;
210			rf_type = CSR_HW_RFID_TYPE(trans->info.hw_rf_id);
211			if ((sku_id[0] & cpu_to_le32(IWL_PNVM_REDUCED_CAP_BIT)) &&
212			    rf_type == IWL_CFG_RF_TYPE_FM)
213				trans->reduced_cap_sku = true;
214
215			IWL_DEBUG_FW(trans,
216				     "Reduced SKU device %d\n",
217				     trans->reduced_cap_sku);
218
219			if (sku_id[0] == tlv_sku_id->data[0] &&
220			    sku_id[1] == tlv_sku_id->data[1] &&
221			    sku_id[2] == tlv_sku_id->data[2]) {
222				int ret;
223
224				ret = iwl_pnvm_handle_section(trans, data, len,
225							      pnvm_data);
226				if (!ret)
227					return 0;
228			} else {
229				IWL_DEBUG_FW(trans, "SKU ID didn't match!\n");
230			}
231		} else {
232			data += sizeof(*tlv) + ALIGN(tlv_len, 4);
233			len -= ALIGN(tlv_len, 4);
234		}
235	}
236
237	return -ENOENT;
238}
239
240static u8 *iwl_pnvm_get_from_fs(struct iwl_trans *trans, size_t *len)
241{
242	const struct firmware *pnvm;
243	char pnvm_name[MAX_PNVM_NAME];
244	size_t new_len;
245	u8 *data;
246	int ret;
247
248	iwl_pnvm_get_fs_name(trans, pnvm_name, sizeof(pnvm_name));
249
250	ret = firmware_request_nowarn(&pnvm, pnvm_name, trans->dev);
251	if (ret) {
252		IWL_DEBUG_FW(trans, "PNVM file %s not found %d\n",
253			     pnvm_name, ret);
254		return NULL;
255	}
256
257	new_len = pnvm->size;
258	data = kvmemdup(pnvm->data, pnvm->size, GFP_KERNEL);
259	release_firmware(pnvm);
260
261	if (!data)
262		return NULL;
263
264	*len = new_len;
265
266	return data;
267}
268
269/**
270 * enum iwl_pnvm_source - different PNVM possible sources
271 *
272 * @IWL_PNVM_SOURCE_NONE: No PNVM.
273 * @IWL_PNVM_SOURCE_BIOS: PNVM should be read from BIOS.
274 * @IWL_PNVM_SOURCE_EXTERNAL: read .pnvm external file
275 * @IWL_PNVM_SOURCE_EMBEDDED: PNVM is embedded in the .ucode file.
276 */
277enum iwl_pnvm_source {
278	IWL_PNVM_SOURCE_NONE,
279	IWL_PNVM_SOURCE_BIOS,
280	IWL_PNVM_SOURCE_EXTERNAL,
281	IWL_PNVM_SOURCE_EMBEDDED
282};
283
284static enum iwl_pnvm_source iwl_select_pnvm_source(struct iwl_trans *trans,
285						   bool intel_sku)
286{
287
288	/* Get PNVM from BIOS for non-Intel SKU */
289	if (!intel_sku)
290		return IWL_PNVM_SOURCE_BIOS;
291
292	/* Before those devices, PNVM didn't exist at all */
293	if (trans->mac_cfg->device_family < IWL_DEVICE_FAMILY_AX210)
294		return IWL_PNVM_SOURCE_NONE;
295
296	/* After those devices, we moved to embedded PNVM */
297	if (trans->mac_cfg->device_family > IWL_DEVICE_FAMILY_AX210)
298		return IWL_PNVM_SOURCE_EMBEDDED;
299
300	/* For IWL_DEVICE_FAMILY_AX210, depends on the CRF */
301	if (CSR_HW_RFID_TYPE(trans->info.hw_rf_id) == IWL_CFG_RF_TYPE_GF)
302		return IWL_PNVM_SOURCE_EXTERNAL;
303
304	return IWL_PNVM_SOURCE_NONE;
305}
306
307static const u8 *iwl_get_pnvm_image(struct iwl_trans *trans_p, size_t *len,
308				    __le32 sku_id[3], const struct iwl_fw *fw)
309{
310	struct pnvm_sku_package *package;
311	enum iwl_pnvm_source pnvm_src =
312		iwl_select_pnvm_source(trans_p, sku_id[2] == 0);
313	u8 *image = NULL;
314
315	IWL_DEBUG_FW(trans_p, "PNVM source %d\n", pnvm_src);
316
317	if (pnvm_src == IWL_PNVM_SOURCE_NONE)
318		return NULL;
319
320	if (pnvm_src == IWL_PNVM_SOURCE_BIOS) {
321		package = iwl_uefi_get_pnvm(trans_p, len);
322		if (!IS_ERR_OR_NULL(package)) {
323			if (*len >= sizeof(*package)) {
324				/* we need only the data */
325				*len -= sizeof(*package);
326				image = kvmemdup(package->data,
327						 *len, GFP_KERNEL);
328			}
329			/*
330			 * free package regardless of whether kmemdup
331			 * succeeded
332			 */
333			kfree(package);
334			if (image)
335				return image;
336		}
337
338		/* PNVM doesn't exist in BIOS. Find the fallback source */
339		pnvm_src = iwl_select_pnvm_source(trans_p, true);
340		IWL_DEBUG_FW(trans_p, "PNVM in BIOS doesn't exist, try %d\n",
341			     pnvm_src);
342	}
343
344	if (pnvm_src == IWL_PNVM_SOURCE_EXTERNAL) {
345		image = iwl_pnvm_get_from_fs(trans_p, len);
346		if (image)
347			return image;
348	}
349
350	if (pnvm_src == IWL_PNVM_SOURCE_EMBEDDED && fw->pnvm_data) {
351		*len = fw->pnvm_size;
352		return fw->pnvm_data;
353	}
354
355	IWL_ERR(trans_p, "Couldn't get PNVM from required source: %d\n", pnvm_src);
356	return NULL;
357}
358
359static void
360iwl_pnvm_load_pnvm_to_trans(struct iwl_trans *trans,
361			    const struct iwl_fw *fw,
362			    __le32 sku_id[3])
363{
364	struct iwl_pnvm_image *pnvm_data = NULL;
365	const u8 *data = NULL;
366	size_t length;
367	int ret;
368
369	/* failed to get/parse the image in the past, no use trying again */
370	if (trans->fail_to_parse_pnvm_image)
371		return;
372
373	if (trans->pnvm_loaded)
374		goto set;
375
376	data = iwl_get_pnvm_image(trans, &length, sku_id, fw);
377	if (!data) {
378		trans->fail_to_parse_pnvm_image = true;
379		return;
380	}
381
382	pnvm_data = kzalloc(sizeof(*pnvm_data), GFP_KERNEL);
383	if (!pnvm_data)
384		goto free;
385
386	ret = iwl_pnvm_parse(trans, data, length, pnvm_data, sku_id);
387	if (ret) {
388		trans->fail_to_parse_pnvm_image = true;
389		goto free;
390	}
391
392	ret = iwl_trans_load_pnvm(trans, pnvm_data, &fw->ucode_capa);
393	if (ret)
394		goto free;
395	IWL_DEBUG_INFO(trans, "loaded PNVM version %08x\n", pnvm_data->version);
396
397set:
398	iwl_trans_set_pnvm(trans, &fw->ucode_capa);
399free:
400	/* free only if it was allocated, i.e. not just embedded PNVM data */
401	if (data != fw->pnvm_data)
402		kvfree(data);
403	kfree(pnvm_data);
404}
405
406static void
407iwl_pnvm_load_reduce_power_to_trans(struct iwl_trans *trans,
408				    const struct iwl_ucode_capabilities *capa,
409				    __le32 sku_id[3])
410{
411	struct iwl_pnvm_image *pnvm_data = NULL;
412	u8 *data = NULL;
413	size_t length;
414	int ret;
415
416	if (trans->failed_to_load_reduce_power_image)
417		return;
418
419	if (trans->reduce_power_loaded)
420		goto set;
421
422	data = iwl_uefi_get_reduced_power(trans, &length);
423	if (IS_ERR(data)) {
424		trans->failed_to_load_reduce_power_image = true;
425		return;
426	}
427
428	pnvm_data = kzalloc(sizeof(*pnvm_data), GFP_KERNEL);
429	if (!pnvm_data)
430		goto free;
431
432	ret = iwl_uefi_reduce_power_parse(trans, data, length, pnvm_data,
433					  sku_id);
434	if (ret) {
435		trans->failed_to_load_reduce_power_image = true;
436		goto free;
437	}
438
439	ret = iwl_trans_load_reduce_power(trans, pnvm_data, capa);
440	if (ret) {
441		IWL_DEBUG_FW(trans,
442			     "Failed to load reduce power table %d\n",
443			     ret);
444		trans->failed_to_load_reduce_power_image = true;
445		goto free;
446	}
447
448set:
449	iwl_trans_set_reduce_power(trans, capa);
450free:
451	kfree(data);
452	kfree(pnvm_data);
453}
454
455int iwl_pnvm_load(struct iwl_trans *trans,
456		  struct iwl_notif_wait_data *notif_wait,
457		  const struct iwl_fw *fw, __le32 sku_id[3])
458{
459	struct iwl_notification_wait pnvm_wait;
460	static const u16 ntf_cmds[] = { WIDE_ID(REGULATORY_AND_NVM_GROUP,
461						PNVM_INIT_COMPLETE_NTFY) };
462
463	/* if the SKU_ID is empty, there's nothing to do */
464	if (!sku_id[0] && !sku_id[1] && !sku_id[2])
465		return 0;
466
467	iwl_pnvm_load_pnvm_to_trans(trans, fw, sku_id);
468	iwl_pnvm_load_reduce_power_to_trans(trans, &fw->ucode_capa, sku_id);
469
470	iwl_init_notification_wait(notif_wait, &pnvm_wait,
471				   ntf_cmds, ARRAY_SIZE(ntf_cmds),
472				   iwl_pnvm_complete_fn, trans);
473
474	/* kick the doorbell */
475	iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6,
476			    UREG_DOORBELL_TO_ISR6_PNVM);
477
478	return iwl_wait_notification(notif_wait, &pnvm_wait,
479				     MVM_UCODE_PNVM_TIMEOUT);
480}
481IWL_EXPORT_SYMBOL(iwl_pnvm_load);
~/ Read-only file preview.