wifi: iwlwifi: fw: Add support for UATS table in UHB
[linux-2.6-block.git] / drivers / net / wireless / intel / iwlwifi / fw / uefi.c
1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 /*
3  * Copyright(c) 2021-2023 Intel Corporation
4  */
5
6 #include "iwl-drv.h"
7 #include "pnvm.h"
8 #include "iwl-prph.h"
9 #include "iwl-io.h"
10
11 #include "fw/uefi.h"
12 #include "fw/api/alive.h"
13 #include <linux/efi.h>
14 #include "fw/runtime.h"
15
16 #define IWL_EFI_VAR_GUID EFI_GUID(0x92daaf2f, 0xc02b, 0x455b,   \
17                                   0xb2, 0xec, 0xf5, 0xa3,       \
18                                   0x59, 0x4f, 0x4a, 0xea)
19
20 struct iwl_uefi_pnvm_mem_desc {
21         __le32 addr;
22         __le32 size;
23         const u8 data[];
24 } __packed;
25
26 static void *iwl_uefi_get_variable(efi_char16_t *name, efi_guid_t *guid,
27                                    unsigned long *data_size)
28 {
29         efi_status_t status;
30         void *data;
31
32         if (!data_size)
33                 return ERR_PTR(-EINVAL);
34
35         if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE))
36                 return ERR_PTR(-ENODEV);
37
38         /* first call with NULL data to get the exact entry size */
39         *data_size = 0;
40         status = efi.get_variable(name, guid, NULL, data_size, NULL);
41         if (status != EFI_BUFFER_TOO_SMALL || !*data_size)
42                 return ERR_PTR(-EIO);
43
44         data = kmalloc(*data_size, GFP_KERNEL);
45         if (!data)
46                 return ERR_PTR(-ENOMEM);
47
48         status = efi.get_variable(name, guid, NULL, data_size, data);
49         if (status != EFI_SUCCESS) {
50                 kfree(data);
51                 return ERR_PTR(-ENOENT);
52         }
53
54         return data;
55 }
56
57 void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len)
58 {
59         unsigned long package_size;
60         void *data;
61
62         *len = 0;
63
64         data = iwl_uefi_get_variable(IWL_UEFI_OEM_PNVM_NAME, &IWL_EFI_VAR_GUID,
65                                      &package_size);
66         if (IS_ERR(data)) {
67                 IWL_DEBUG_FW(trans,
68                              "PNVM UEFI variable not found 0x%lx (len %lu)\n",
69                              PTR_ERR(data), package_size);
70                 return data;
71         }
72
73         IWL_DEBUG_FW(trans, "Read PNVM from UEFI with size %lu\n", package_size);
74         *len = package_size;
75
76         return data;
77 }
78
79 int iwl_uefi_handle_tlv_mem_desc(struct iwl_trans *trans, const u8 *data,
80                                  u32 tlv_len, struct iwl_pnvm_image *pnvm_data)
81 {
82         const struct iwl_uefi_pnvm_mem_desc *desc = (const void *)data;
83         u32 data_len;
84
85         if (tlv_len < sizeof(*desc)) {
86                 IWL_DEBUG_FW(trans, "TLV len (%d) is too small\n", tlv_len);
87                 return -EINVAL;
88         }
89
90         data_len = tlv_len - sizeof(*desc);
91
92         IWL_DEBUG_FW(trans,
93                      "Handle IWL_UCODE_TLV_MEM_DESC, len %d data_len %d\n",
94                      tlv_len, data_len);
95
96         if (le32_to_cpu(desc->size) != data_len) {
97                 IWL_DEBUG_FW(trans, "invalid mem desc size %d\n", desc->size);
98                 return -EINVAL;
99         }
100
101         if (pnvm_data->n_chunks == IPC_DRAM_MAP_ENTRY_NUM_MAX) {
102                 IWL_DEBUG_FW(trans, "too many payloads to allocate in DRAM.\n");
103                 return -EINVAL;
104         }
105
106         IWL_DEBUG_FW(trans, "Adding data (size %d)\n", data_len);
107
108         pnvm_data->chunks[pnvm_data->n_chunks].data = desc->data;
109         pnvm_data->chunks[pnvm_data->n_chunks].len = data_len;
110         pnvm_data->n_chunks++;
111
112         return 0;
113 }
114
115 static int iwl_uefi_reduce_power_section(struct iwl_trans *trans,
116                                          const u8 *data, size_t len,
117                                          struct iwl_pnvm_image *pnvm_data)
118 {
119         const struct iwl_ucode_tlv *tlv;
120
121         IWL_DEBUG_FW(trans, "Handling REDUCE_POWER section\n");
122         memset(pnvm_data, 0, sizeof(*pnvm_data));
123
124         while (len >= sizeof(*tlv)) {
125                 u32 tlv_len, tlv_type;
126
127                 len -= sizeof(*tlv);
128                 tlv = (const void *)data;
129
130                 tlv_len = le32_to_cpu(tlv->length);
131                 tlv_type = le32_to_cpu(tlv->type);
132
133                 if (len < tlv_len) {
134                         IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
135                                 len, tlv_len);
136                         return -EINVAL;
137                 }
138
139                 data += sizeof(*tlv);
140
141                 switch (tlv_type) {
142                 case IWL_UCODE_TLV_MEM_DESC:
143                         if (iwl_uefi_handle_tlv_mem_desc(trans, data, tlv_len,
144                                                          pnvm_data))
145                                 return -EINVAL;
146                         break;
147                 case IWL_UCODE_TLV_PNVM_SKU:
148                         IWL_DEBUG_FW(trans,
149                                      "New REDUCE_POWER section started, stop parsing.\n");
150                         goto done;
151                 default:
152                         IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n",
153                                      tlv_type, tlv_len);
154                         break;
155                 }
156
157                 len -= ALIGN(tlv_len, 4);
158                 data += ALIGN(tlv_len, 4);
159         }
160
161 done:
162         if (!pnvm_data->n_chunks) {
163                 IWL_DEBUG_FW(trans, "Empty REDUCE_POWER, skipping.\n");
164                 return -ENOENT;
165         }
166         return 0;
167 }
168
169 int iwl_uefi_reduce_power_parse(struct iwl_trans *trans,
170                                 const u8 *data, size_t len,
171                                 struct iwl_pnvm_image *pnvm_data)
172 {
173         const struct iwl_ucode_tlv *tlv;
174
175         IWL_DEBUG_FW(trans, "Parsing REDUCE_POWER data\n");
176
177         while (len >= sizeof(*tlv)) {
178                 u32 tlv_len, tlv_type;
179
180                 len -= sizeof(*tlv);
181                 tlv = (const void *)data;
182
183                 tlv_len = le32_to_cpu(tlv->length);
184                 tlv_type = le32_to_cpu(tlv->type);
185
186                 if (len < tlv_len) {
187                         IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
188                                 len, tlv_len);
189                         return -EINVAL;
190                 }
191
192                 if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) {
193                         const struct iwl_sku_id *sku_id =
194                                 (const void *)(data + sizeof(*tlv));
195
196                         IWL_DEBUG_FW(trans,
197                                      "Got IWL_UCODE_TLV_PNVM_SKU len %d\n",
198                                      tlv_len);
199                         IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n",
200                                      le32_to_cpu(sku_id->data[0]),
201                                      le32_to_cpu(sku_id->data[1]),
202                                      le32_to_cpu(sku_id->data[2]));
203
204                         data += sizeof(*tlv) + ALIGN(tlv_len, 4);
205                         len -= ALIGN(tlv_len, 4);
206
207                         if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) &&
208                             trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) &&
209                             trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) {
210                                 int ret = iwl_uefi_reduce_power_section(trans,
211                                                                     data, len,
212                                                                     pnvm_data);
213                                 if (!ret)
214                                         return 0;
215                         } else {
216                                 IWL_DEBUG_FW(trans, "SKU ID didn't match!\n");
217                         }
218                 } else {
219                         data += sizeof(*tlv) + ALIGN(tlv_len, 4);
220                         len -= ALIGN(tlv_len, 4);
221                 }
222         }
223
224         return -ENOENT;
225 }
226
227 u8 *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len)
228 {
229         struct pnvm_sku_package *package;
230         unsigned long package_size;
231         u8 *data;
232
233         package = iwl_uefi_get_variable(IWL_UEFI_REDUCED_POWER_NAME,
234                                         &IWL_EFI_VAR_GUID, &package_size);
235
236         if (IS_ERR(package)) {
237                 IWL_DEBUG_FW(trans,
238                              "Reduced Power UEFI variable not found 0x%lx (len %lu)\n",
239                              PTR_ERR(package), package_size);
240                 return ERR_CAST(package);
241         }
242
243         if (package_size < sizeof(*package)) {
244                 IWL_DEBUG_FW(trans,
245                              "Invalid Reduced Power UEFI variable len (%lu)\n",
246                              package_size);
247                 kfree(package);
248                 return ERR_PTR(-EINVAL);
249         }
250
251         IWL_DEBUG_FW(trans, "Read reduced power from UEFI with size %lu\n",
252                      package_size);
253
254         IWL_DEBUG_FW(trans, "rev %d, total_size %d, n_skus %d\n",
255                      package->rev, package->total_size, package->n_skus);
256
257         *len = package_size - sizeof(*package);
258         data = kmemdup(package->data, *len, GFP_KERNEL);
259         if (!data) {
260                 kfree(package);
261                 return ERR_PTR(-ENOMEM);
262         }
263
264         kfree(package);
265
266         return data;
267 }
268
269 static int iwl_uefi_step_parse(struct uefi_cnv_common_step_data *common_step_data,
270                                struct iwl_trans *trans)
271 {
272         if (common_step_data->revision != 1)
273                 return -EINVAL;
274
275         trans->mbx_addr_0_step = (u32)common_step_data->revision |
276                 (u32)common_step_data->cnvi_eq_channel << 8 |
277                 (u32)common_step_data->cnvr_eq_channel << 16 |
278                 (u32)common_step_data->radio1 << 24;
279         trans->mbx_addr_1_step = (u32)common_step_data->radio2;
280         return 0;
281 }
282
283 void iwl_uefi_get_step_table(struct iwl_trans *trans)
284 {
285         struct uefi_cnv_common_step_data *data;
286         unsigned long package_size;
287         int ret;
288
289         if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210)
290                 return;
291
292         data = iwl_uefi_get_variable(IWL_UEFI_STEP_NAME, &IWL_EFI_VAR_GUID,
293                                      &package_size);
294
295         if (IS_ERR(data)) {
296                 IWL_DEBUG_FW(trans,
297                              "STEP UEFI variable not found 0x%lx\n",
298                              PTR_ERR(data));
299                 return;
300         }
301
302         if (package_size < sizeof(*data)) {
303                 IWL_DEBUG_FW(trans,
304                              "Invalid STEP table UEFI variable len (%lu)\n",
305                              package_size);
306                 kfree(data);
307                 return;
308         }
309
310         IWL_DEBUG_FW(trans, "Read STEP from UEFI with size %lu\n",
311                      package_size);
312
313         ret = iwl_uefi_step_parse(data, trans);
314         if (ret < 0)
315                 IWL_DEBUG_FW(trans, "Cannot read STEP tables. rev is invalid\n");
316
317         kfree(data);
318 }
319 IWL_EXPORT_SYMBOL(iwl_uefi_get_step_table);
320
321 #ifdef CONFIG_ACPI
322 static int iwl_uefi_sgom_parse(struct uefi_cnv_wlan_sgom_data *sgom_data,
323                                struct iwl_fw_runtime *fwrt)
324 {
325         int i, j;
326
327         if (sgom_data->revision != 1)
328                 return -EINVAL;
329
330         memcpy(fwrt->sgom_table.offset_map, sgom_data->offset_map,
331                sizeof(fwrt->sgom_table.offset_map));
332
333         for (i = 0; i < MCC_TO_SAR_OFFSET_TABLE_ROW_SIZE; i++) {
334                 for (j = 0; j < MCC_TO_SAR_OFFSET_TABLE_COL_SIZE; j++) {
335                         /* since each byte is composed of to values, */
336                         /* one for each letter, */
337                         /* extract and check each of them separately */
338                         u8 value = fwrt->sgom_table.offset_map[i][j];
339                         u8 low = value & 0xF;
340                         u8 high = (value & 0xF0) >> 4;
341
342                         if (high > fwrt->geo_num_profiles)
343                                 high = 0;
344                         if (low > fwrt->geo_num_profiles)
345                                 low = 0;
346                         fwrt->sgom_table.offset_map[i][j] = (high << 4) | low;
347                 }
348         }
349
350         fwrt->sgom_enabled = true;
351         return 0;
352 }
353
354 void iwl_uefi_get_sgom_table(struct iwl_trans *trans,
355                              struct iwl_fw_runtime *fwrt)
356 {
357         struct uefi_cnv_wlan_sgom_data *data;
358         unsigned long package_size;
359         int ret;
360
361         if (!fwrt->geo_enabled)
362                 return;
363
364         data = iwl_uefi_get_variable(IWL_UEFI_SGOM_NAME, &IWL_EFI_VAR_GUID,
365                                      &package_size);
366         if (IS_ERR(data)) {
367                 IWL_DEBUG_FW(trans,
368                              "SGOM UEFI variable not found 0x%lx\n",
369                              PTR_ERR(data));
370                 return;
371         }
372
373         if (package_size < sizeof(*data)) {
374                 IWL_DEBUG_FW(trans,
375                              "Invalid SGOM table UEFI variable len (%lu)\n",
376                              package_size);
377                 kfree(data);
378                 return;
379         }
380
381         IWL_DEBUG_FW(trans, "Read SGOM from UEFI with size %lu\n",
382                      package_size);
383
384         ret = iwl_uefi_sgom_parse(data, fwrt);
385         if (ret < 0)
386                 IWL_DEBUG_FW(trans, "Cannot read SGOM tables. rev is invalid\n");
387
388         kfree(data);
389 }
390 IWL_EXPORT_SYMBOL(iwl_uefi_get_sgom_table);
391
392 static int iwl_uefi_uats_parse(struct uefi_cnv_wlan_uats_data *uats_data,
393                                struct iwl_fw_runtime *fwrt)
394 {
395         if (uats_data->revision != 1)
396                 return -EINVAL;
397
398         memcpy(fwrt->uats_table.offset_map, uats_data->offset_map,
399                sizeof(fwrt->uats_table.offset_map));
400         return 0;
401 }
402
403 int iwl_uefi_get_uats_table(struct iwl_trans *trans,
404                             struct iwl_fw_runtime *fwrt)
405 {
406         struct uefi_cnv_wlan_uats_data *data;
407         unsigned long package_size;
408         int ret;
409
410         data = iwl_uefi_get_variable(IWL_UEFI_UATS_NAME, &IWL_EFI_VAR_GUID,
411                                      &package_size);
412         if (IS_ERR(data)) {
413                 IWL_DEBUG_FW(trans,
414                              "UATS UEFI variable not found 0x%lx\n",
415                              PTR_ERR(data));
416                 return -EINVAL;
417         }
418
419         if (package_size < sizeof(*data)) {
420                 IWL_DEBUG_FW(trans,
421                              "Invalid UATS table UEFI variable len (%lu)\n",
422                              package_size);
423                 kfree(data);
424                 return -EINVAL;
425         }
426
427         IWL_DEBUG_FW(trans, "Read UATS from UEFI with size %lu\n",
428                      package_size);
429
430         ret = iwl_uefi_uats_parse(data, fwrt);
431         if (ret < 0) {
432                 IWL_DEBUG_FW(trans, "Cannot read UATS table. rev is invalid\n");
433                 kfree(data);
434                 return ret;
435         }
436
437         kfree(data);
438         return 0;
439 }
440 IWL_EXPORT_SYMBOL(iwl_uefi_get_uats_table);
441 #endif /* CONFIG_ACPI */