xref: /freebsd/sys/contrib/dev/iwlwifi/fw/uefi.c (revision af23369a6deaaeb612ab266eb88b8bb8d560c322)
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 */
357