1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 /*
3 * Copyright(c) 2021 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 void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len)
21 {
22 struct efivar_entry *pnvm_efivar;
23 void *data;
24 unsigned long package_size;
25 int err;
26
27 *len = 0;
28
29 pnvm_efivar = kzalloc(sizeof(*pnvm_efivar), GFP_KERNEL);
30 if (!pnvm_efivar)
31 return ERR_PTR(-ENOMEM);
32
33 memcpy(&pnvm_efivar->var.VariableName, IWL_UEFI_OEM_PNVM_NAME,
34 sizeof(IWL_UEFI_OEM_PNVM_NAME));
35 pnvm_efivar->var.VendorGuid = IWL_EFI_VAR_GUID;
36
37 /*
38 * TODO: we hardcode a maximum length here, because reading
39 * from the UEFI is not working. To implement this properly,
40 * we have to call efivar_entry_size().
41 */
42 package_size = IWL_HARDCODED_PNVM_SIZE;
43
44 data = kmalloc(package_size, GFP_KERNEL);
45 if (!data) {
46 data = ERR_PTR(-ENOMEM);
47 goto out;
48 }
49
50 err = efivar_entry_get(pnvm_efivar, NULL, &package_size, data);
51 if (err) {
52 IWL_DEBUG_FW(trans,
53 "PNVM UEFI variable not found %d (len %lu)\n",
54 err, package_size);
55 kfree(data);
56 data = ERR_PTR(err);
57 goto out;
58 }
59
60 IWL_DEBUG_FW(trans, "Read PNVM from UEFI with size %lu\n", package_size);
61 *len = package_size;
62
63 out:
64 kfree(pnvm_efivar);
65
66 return data;
67 }
68
69 static void *iwl_uefi_reduce_power_section(struct iwl_trans *trans,
70 const u8 *data, size_t len)
71 {
72 const struct iwl_ucode_tlv *tlv;
73 u8 *reduce_power_data = NULL, *tmp;
74 u32 size = 0;
75
76 IWL_DEBUG_FW(trans, "Handling REDUCE_POWER section\n");
77
78 while (len >= sizeof(*tlv)) {
79 u32 tlv_len, tlv_type;
80
81 len -= sizeof(*tlv);
82 tlv = (const void *)data;
83
84 tlv_len = le32_to_cpu(tlv->length);
85 tlv_type = le32_to_cpu(tlv->type);
86
87 if (len < tlv_len) {
88 IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
89 len, tlv_len);
90 kfree(reduce_power_data);
91 reduce_power_data = ERR_PTR(-EINVAL);
92 goto out;
93 }
94
95 data += sizeof(*tlv);
96
97 switch (tlv_type) {
98 case IWL_UCODE_TLV_MEM_DESC: {
99 IWL_DEBUG_FW(trans,
100 "Got IWL_UCODE_TLV_MEM_DESC len %d\n",
101 tlv_len);
102
103 IWL_DEBUG_FW(trans, "Adding data (size %d)\n", tlv_len);
104
105 tmp = krealloc(reduce_power_data, size + tlv_len, GFP_KERNEL);
106 if (!tmp) {
107 IWL_DEBUG_FW(trans,
108 "Couldn't allocate (more) reduce_power_data\n");
109
110 kfree(reduce_power_data);
111 reduce_power_data = ERR_PTR(-ENOMEM);
112 goto out;
113 }
114
115 reduce_power_data = tmp;
116
117 memcpy(reduce_power_data + size, data, tlv_len);
118
119 size += tlv_len;
120
121 break;
122 }
123 case IWL_UCODE_TLV_PNVM_SKU:
124 IWL_DEBUG_FW(trans,
125 "New REDUCE_POWER section started, stop parsing.\n");
126 goto done;
127 default:
128 IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n",
129 tlv_type, tlv_len);
130 break;
131 }
132
133 len -= ALIGN(tlv_len, 4);
134 data += ALIGN(tlv_len, 4);
135 }
136
137 done:
138 if (!size) {
139 IWL_DEBUG_FW(trans, "Empty REDUCE_POWER, skipping.\n");
140 /* Better safe than sorry, but 'reduce_power_data' should
141 * always be NULL if !size.
142 */
143 kfree(reduce_power_data);
144 reduce_power_data = ERR_PTR(-ENOENT);
145 goto out;
146 }
147
148 IWL_INFO(trans, "loaded REDUCE_POWER\n");
149
150 out:
151 return reduce_power_data;
152 }
153
154 static void *iwl_uefi_reduce_power_parse(struct iwl_trans *trans,
155 const u8 *data, size_t len)
156 {
157 const struct iwl_ucode_tlv *tlv;
158 void *sec_data;
159
160 IWL_DEBUG_FW(trans, "Parsing REDUCE_POWER data\n");
161
162 while (len >= sizeof(*tlv)) {
163 u32 tlv_len, tlv_type;
164
165 len -= sizeof(*tlv);
166 tlv = (const void *)data;
167
168 tlv_len = le32_to_cpu(tlv->length);
169 tlv_type = le32_to_cpu(tlv->type);
170
171 if (len < tlv_len) {
172 IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
173 len, tlv_len);
174 return ERR_PTR(-EINVAL);
175 }
176
177 if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) {
178 const struct iwl_sku_id *sku_id =
179 (const void *)(data + sizeof(*tlv));
180
181 IWL_DEBUG_FW(trans,
182 "Got IWL_UCODE_TLV_PNVM_SKU len %d\n",
183 tlv_len);
184 IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n",
185 le32_to_cpu(sku_id->data[0]),
186 le32_to_cpu(sku_id->data[1]),
187 le32_to_cpu(sku_id->data[2]));
188
189 data += sizeof(*tlv) + ALIGN(tlv_len, 4);
190 len -= ALIGN(tlv_len, 4);
191
192 if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) &&
193 trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) &&
194 trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) {
195 sec_data = iwl_uefi_reduce_power_section(trans,
196 data,
197 len);
198 if (!IS_ERR(sec_data))
199 return sec_data;
200 } else {
201 IWL_DEBUG_FW(trans, "SKU ID didn't match!\n");
202 }
203 } else {
204 data += sizeof(*tlv) + ALIGN(tlv_len, 4);
205 len -= ALIGN(tlv_len, 4);
206 }
207 }
208
209 return ERR_PTR(-ENOENT);
210 }
211
212 void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len)
213 {
214 struct efivar_entry *reduce_power_efivar;
215 struct pnvm_sku_package *package;
216 void *data = NULL;
217 unsigned long package_size;
218 int err;
219
220 *len = 0;
221
222 reduce_power_efivar = kzalloc(sizeof(*reduce_power_efivar), GFP_KERNEL);
223 if (!reduce_power_efivar)
224 return ERR_PTR(-ENOMEM);
225
226 memcpy(&reduce_power_efivar->var.VariableName, IWL_UEFI_REDUCED_POWER_NAME,
227 sizeof(IWL_UEFI_REDUCED_POWER_NAME));
228 reduce_power_efivar->var.VendorGuid = IWL_EFI_VAR_GUID;
229
230 /*
231 * TODO: we hardcode a maximum length here, because reading
232 * from the UEFI is not working. To implement this properly,
233 * we have to call efivar_entry_size().
234 */
235 package_size = IWL_HARDCODED_REDUCE_POWER_SIZE;
236
237 package = kmalloc(package_size, GFP_KERNEL);
238 if (!package) {
239 package = ERR_PTR(-ENOMEM);
240 goto out;
241 }
242
243 err = efivar_entry_get(reduce_power_efivar, NULL, &package_size, package);
244 if (err) {
245 IWL_DEBUG_FW(trans,
246 "Reduced Power UEFI variable not found %d (len %lu)\n",
247 err, package_size);
248 kfree(package);
249 data = ERR_PTR(err);
250 goto out;
251 }
252
253 IWL_DEBUG_FW(trans, "Read reduced power from UEFI with size %lu\n",
254 package_size);
255 *len = package_size;
256
257 IWL_DEBUG_FW(trans, "rev %d, total_size %d, n_skus %d\n",
258 package->rev, package->total_size, package->n_skus);
259
260 data = iwl_uefi_reduce_power_parse(trans, package->data,
261 *len - sizeof(*package));
262
263 kfree(package);
264
265 out:
266 kfree(reduce_power_efivar);
267
268 return data;
269 }
270
271 #ifdef CONFIG_ACPI
272 static int iwl_uefi_sgom_parse(struct uefi_cnv_wlan_sgom_data *sgom_data,
273 struct iwl_fw_runtime *fwrt)
274 {
275 int i, j;
276
277 if (sgom_data->revision != 1)
278 return -EINVAL;
279
280 memcpy(fwrt->sgom_table.offset_map, sgom_data->offset_map,
281 sizeof(fwrt->sgom_table.offset_map));
282
283 for (i = 0; i < MCC_TO_SAR_OFFSET_TABLE_ROW_SIZE; i++) {
284 for (j = 0; j < MCC_TO_SAR_OFFSET_TABLE_COL_SIZE; j++) {
285 /* since each byte is composed of to values, */
286 /* one for each letter, */
287 /* extract and check each of them separately */
288 u8 value = fwrt->sgom_table.offset_map[i][j];
289 u8 low = value & 0xF;
290 u8 high = (value & 0xF0) >> 4;
291
292 if (high > fwrt->geo_num_profiles)
293 high = 0;
294 if (low > fwrt->geo_num_profiles)
295 low = 0;
296 fwrt->sgom_table.offset_map[i][j] = (high << 4) | low;
297 }
298 }
299
300 fwrt->sgom_enabled = true;
301 return 0;
302 }
303
304 void iwl_uefi_get_sgom_table(struct iwl_trans *trans,
305 struct iwl_fw_runtime *fwrt)
306 {
307 struct efivar_entry *sgom_efivar;
308 struct uefi_cnv_wlan_sgom_data *data;
309 unsigned long package_size;
310 int err, ret;
311
312 if (!fwrt->geo_enabled)
313 return;
314
315 sgom_efivar = kzalloc(sizeof(*sgom_efivar), GFP_KERNEL);
316 if (!sgom_efivar)
317 return;
318
319 memcpy(&sgom_efivar->var.VariableName, IWL_UEFI_SGOM_NAME,
320 sizeof(IWL_UEFI_SGOM_NAME));
321 sgom_efivar->var.VendorGuid = IWL_EFI_VAR_GUID;
322
323 /* TODO: we hardcode a maximum length here, because reading
324 * from the UEFI is not working. To implement this properly,
325 * we have to call efivar_entry_size().
326 */
327 package_size = IWL_HARDCODED_SGOM_SIZE;
328
329 data = kmalloc(package_size, GFP_KERNEL);
330 if (!data) {
331 data = ERR_PTR(-ENOMEM);
332 goto out;
333 }
334
335 err = efivar_entry_get(sgom_efivar, NULL, &package_size, data);
336 if (err) {
337 IWL_DEBUG_FW(trans,
338 "SGOM UEFI variable not found %d\n", err);
339 goto out_free;
340 }
341
342 IWL_DEBUG_FW(trans, "Read SGOM from UEFI with size %lu\n",
343 package_size);
344
345 ret = iwl_uefi_sgom_parse(data, fwrt);
346 if (ret < 0)
347 IWL_DEBUG_FW(trans, "Cannot read SGOM tables. rev is invalid\n");
348
349 out_free:
350 kfree(data);
351
352 out:
353 kfree(sgom_efivar);
354 }
355 IWL_EXPORT_SYMBOL(iwl_uefi_get_sgom_table);
356 #endif /* CONFIG_ACPI */
Cache object: 17229aa62aad39ced03681694214693e
|