Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/contrib/dev/iwlwifi/fw/pnvm.c
48287 views
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
18
struct iwl_pnvm_section {
19
__le32 offset;
20
const u8 data[];
21
} __packed;
22
23
static 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
36
static 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
152
done:
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
169
static 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
240
static int iwl_pnvm_get_from_fs(struct iwl_trans *trans, u8 **data, size_t *len)
241
{
242
const struct firmware *pnvm;
243
char pnvm_name[MAX_PNVM_NAME];
244
size_t new_len;
245
int ret;
246
247
iwl_pnvm_get_fs_name(trans, pnvm_name, sizeof(pnvm_name));
248
249
ret = firmware_request_nowarn(&pnvm, pnvm_name, trans->dev);
250
if (ret) {
251
IWL_DEBUG_FW(trans, "PNVM file %s not found %d\n",
252
pnvm_name, ret);
253
return ret;
254
}
255
256
new_len = pnvm->size;
257
*data = kvmemdup(pnvm->data, pnvm->size, GFP_KERNEL);
258
release_firmware(pnvm);
259
260
if (!*data)
261
return -ENOMEM;
262
263
*len = new_len;
264
265
return 0;
266
}
267
268
static const u8 *iwl_get_pnvm_image(struct iwl_trans *trans_p, size_t *len,
269
__le32 sku_id[3], const struct iwl_fw *fw)
270
{
271
struct pnvm_sku_package *package;
272
u8 *image = NULL;
273
274
/* Get PNVM from BIOS for non-Intel SKU */
275
if (sku_id[2]) {
276
package = iwl_uefi_get_pnvm(trans_p, len);
277
if (!IS_ERR_OR_NULL(package)) {
278
if (*len >= sizeof(*package)) {
279
/* we need only the data */
280
*len -= sizeof(*package);
281
image = kvmemdup(package->data,
282
*len, GFP_KERNEL);
283
}
284
/*
285
* free package regardless of whether kmemdup
286
* succeeded
287
*/
288
kfree(package);
289
if (image)
290
return image;
291
}
292
}
293
294
if (fw->pnvm_data) {
295
*len = fw->pnvm_size;
296
297
return fw->pnvm_data;
298
}
299
300
/* If it's not available, or for Intel SKU, try from the filesystem */
301
if (iwl_pnvm_get_from_fs(trans_p, &image, len))
302
return NULL;
303
return image;
304
}
305
306
static void
307
iwl_pnvm_load_pnvm_to_trans(struct iwl_trans *trans,
308
const struct iwl_fw *fw,
309
__le32 sku_id[3])
310
{
311
struct iwl_pnvm_image *pnvm_data = NULL;
312
const u8 *data = NULL;
313
size_t length;
314
int ret;
315
316
/* failed to get/parse the image in the past, no use trying again */
317
if (trans->fail_to_parse_pnvm_image)
318
return;
319
320
if (trans->pnvm_loaded)
321
goto set;
322
323
data = iwl_get_pnvm_image(trans, &length, sku_id, fw);
324
if (!data) {
325
trans->fail_to_parse_pnvm_image = true;
326
return;
327
}
328
329
pnvm_data = kzalloc(sizeof(*pnvm_data), GFP_KERNEL);
330
if (!pnvm_data)
331
goto free;
332
333
ret = iwl_pnvm_parse(trans, data, length, pnvm_data, sku_id);
334
if (ret) {
335
trans->fail_to_parse_pnvm_image = true;
336
goto free;
337
}
338
339
ret = iwl_trans_load_pnvm(trans, pnvm_data, &fw->ucode_capa);
340
if (ret)
341
goto free;
342
IWL_DEBUG_INFO(trans, "loaded PNVM version %08x\n", pnvm_data->version);
343
344
set:
345
iwl_trans_set_pnvm(trans, &fw->ucode_capa);
346
free:
347
/* free only if it was allocated, i.e. not just embedded PNVM data */
348
if (data != fw->pnvm_data)
349
kvfree(data);
350
kfree(pnvm_data);
351
}
352
353
static void
354
iwl_pnvm_load_reduce_power_to_trans(struct iwl_trans *trans,
355
const struct iwl_ucode_capabilities *capa,
356
__le32 sku_id[3])
357
{
358
struct iwl_pnvm_image *pnvm_data = NULL;
359
u8 *data = NULL;
360
size_t length;
361
int ret;
362
363
if (trans->failed_to_load_reduce_power_image)
364
return;
365
366
if (trans->reduce_power_loaded)
367
goto set;
368
369
data = iwl_uefi_get_reduced_power(trans, &length);
370
if (IS_ERR(data)) {
371
trans->failed_to_load_reduce_power_image = true;
372
return;
373
}
374
375
pnvm_data = kzalloc(sizeof(*pnvm_data), GFP_KERNEL);
376
if (!pnvm_data)
377
goto free;
378
379
ret = iwl_uefi_reduce_power_parse(trans, data, length, pnvm_data,
380
sku_id);
381
if (ret) {
382
trans->failed_to_load_reduce_power_image = true;
383
goto free;
384
}
385
386
ret = iwl_trans_load_reduce_power(trans, pnvm_data, capa);
387
if (ret) {
388
IWL_DEBUG_FW(trans,
389
"Failed to load reduce power table %d\n",
390
ret);
391
trans->failed_to_load_reduce_power_image = true;
392
goto free;
393
}
394
395
set:
396
iwl_trans_set_reduce_power(trans, capa);
397
free:
398
kfree(data);
399
kfree(pnvm_data);
400
}
401
402
int iwl_pnvm_load(struct iwl_trans *trans,
403
struct iwl_notif_wait_data *notif_wait,
404
const struct iwl_fw *fw, __le32 sku_id[3])
405
{
406
struct iwl_notification_wait pnvm_wait;
407
static const u16 ntf_cmds[] = { WIDE_ID(REGULATORY_AND_NVM_GROUP,
408
PNVM_INIT_COMPLETE_NTFY) };
409
410
/* if the SKU_ID is empty, there's nothing to do */
411
if (!sku_id[0] && !sku_id[1] && !sku_id[2])
412
return 0;
413
414
iwl_pnvm_load_pnvm_to_trans(trans, fw, sku_id);
415
iwl_pnvm_load_reduce_power_to_trans(trans, &fw->ucode_capa, sku_id);
416
417
iwl_init_notification_wait(notif_wait, &pnvm_wait,
418
ntf_cmds, ARRAY_SIZE(ntf_cmds),
419
iwl_pnvm_complete_fn, trans);
420
421
/* kick the doorbell */
422
iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6,
423
UREG_DOORBELL_TO_ISR6_PNVM);
424
425
return iwl_wait_notification(notif_wait, &pnvm_wait,
426
MVM_UCODE_PNVM_TIMEOUT);
427
}
428
IWL_EXPORT_SYMBOL(iwl_pnvm_load);
429
430