xref: /linux/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c (revision fab183d632628381b466a41479489541ac0e29a0)
1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 /*
3  * Copyright (C) 2023, 2025-2026 Intel Corporation
4  */
5 #include <linux/dmi.h>
6 #include "iwl-drv.h"
7 #include "iwl-debug.h"
8 #include "regulatory.h"
9 #include "fw/runtime.h"
10 #include "fw/uefi.h"
11 
12 #define GET_BIOS_TABLE(__name, ...)					\
13 do {									\
14 	int ret = -ENOENT;						\
15 	if (fwrt->uefi_tables_lock_status > UEFI_WIFI_GUID_UNLOCKED)	\
16 		ret = iwl_uefi_get_ ## __name(__VA_ARGS__);		\
17 	if (ret < 0)							\
18 		ret = iwl_acpi_get_ ## __name(__VA_ARGS__);		\
19 	return ret;							\
20 } while (0)
21 
22 #define IWL_BIOS_TABLE_LOADER(__name)					\
23 int iwl_bios_get_ ## __name(struct iwl_fw_runtime *fwrt)		\
24 {GET_BIOS_TABLE(__name, fwrt); }					\
25 IWL_EXPORT_SYMBOL(iwl_bios_get_ ## __name)
26 
27 #define IWL_BIOS_TABLE_LOADER_DATA(__name, data_type)			\
28 int iwl_bios_get_ ## __name(struct iwl_fw_runtime *fwrt,		\
29 			    data_type * data)				\
30 {GET_BIOS_TABLE(__name, fwrt, data); }					\
31 IWL_EXPORT_SYMBOL(iwl_bios_get_ ## __name)
32 
33 IWL_BIOS_TABLE_LOADER(wrds_table);
34 IWL_BIOS_TABLE_LOADER(ewrd_table);
35 IWL_BIOS_TABLE_LOADER(wgds_table);
36 IWL_BIOS_TABLE_LOADER(ppag_table);
37 IWL_BIOS_TABLE_LOADER(phy_filters);
38 IWL_BIOS_TABLE_LOADER_DATA(tas_table, struct iwl_tas_data);
39 IWL_BIOS_TABLE_LOADER_DATA(pwr_limit, u64);
40 IWL_BIOS_TABLE_LOADER_DATA(mcc, char);
41 IWL_BIOS_TABLE_LOADER_DATA(eckv, u32);
42 IWL_BIOS_TABLE_LOADER_DATA(wbem, u32);
43 IWL_BIOS_TABLE_LOADER_DATA(dsbr, u32);
44 
45 
46 static const struct dmi_system_id dmi_ppag_approved_list[] = {
47 	{ .ident = "HP",
48 	  .matches = {
49 			DMI_MATCH(DMI_SYS_VENDOR, "HP"),
50 		},
51 	},
52 	{ .ident = "SAMSUNG",
53 	  .matches = {
54 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"),
55 		},
56 	},
57 	{ .ident = "MSFT",
58 	  .matches = {
59 			DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
60 		},
61 	},
62 	{ .ident = "ASUSTEK",
63 	  .matches = {
64 			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
65 		},
66 	},
67 	{ .ident = "ASUS",
68 	  .matches = {
69 			DMI_MATCH(DMI_SYS_VENDOR, "ASUS"),
70 		},
71 	},
72 	{ .ident = "GOOGLE-HP",
73 	  .matches = {
74 			DMI_MATCH(DMI_SYS_VENDOR, "Google"),
75 			DMI_MATCH(DMI_BOARD_VENDOR, "HP"),
76 		},
77 	},
78 	{ .ident = "GOOGLE-ASUS",
79 	  .matches = {
80 			DMI_MATCH(DMI_SYS_VENDOR, "Google"),
81 			DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTek COMPUTER INC."),
82 		},
83 	},
84 	{ .ident = "GOOGLE-SAMSUNG",
85 	  .matches = {
86 			DMI_MATCH(DMI_SYS_VENDOR, "Google"),
87 			DMI_MATCH(DMI_BOARD_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"),
88 		},
89 	},
90 	{ .ident = "DELL",
91 	  .matches = {
92 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
93 		},
94 	},
95 	{ .ident = "DELL",
96 	  .matches = {
97 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
98 		},
99 	},
100 	{ .ident = "RAZER",
101 	  .matches = {
102 			DMI_MATCH(DMI_SYS_VENDOR, "Razer"),
103 		},
104 	},
105 	{ .ident = "Honor",
106 	  .matches = {
107 			DMI_MATCH(DMI_SYS_VENDOR, "HONOR"),
108 		},
109 	},
110 	{ .ident = "WIKO",
111 	  .matches = {
112 			DMI_MATCH(DMI_SYS_VENDOR, "WIKO"),
113 		},
114 	},
115 	{ .ident = "XIAOMI",
116 	  .matches = {
117 			DMI_MATCH(DMI_SYS_VENDOR, "XIAOMI"),
118 		},
119 	},
120 	{}
121 };
122 
123 static const struct dmi_system_id dmi_tas_approved_list[] = {
124 	{ .ident = "HP",
125 	  .matches = {
126 			DMI_MATCH(DMI_SYS_VENDOR, "HP"),
127 		},
128 	},
129 	{ .ident = "SAMSUNG",
130 	  .matches = {
131 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"),
132 		},
133 	},
134 		{ .ident = "LENOVO",
135 	  .matches = {
136 			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
137 		},
138 	},
139 	{ .ident = "DELL",
140 	  .matches = {
141 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
142 		},
143 	},
144 	{ .ident = "MSFT",
145 	  .matches = {
146 			DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
147 		},
148 	},
149 	{ .ident = "Acer",
150 	  .matches = {
151 			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
152 		},
153 	},
154 	{ .ident = "ASUSTEK",
155 	  .matches = {
156 			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
157 		},
158 	},
159 	{ .ident = "ASUS",
160 	  .matches = {
161 			DMI_MATCH(DMI_SYS_VENDOR, "ASUS"),
162 		},
163 	},
164 	{ .ident = "GOOGLE-HP",
165 	  .matches = {
166 			DMI_MATCH(DMI_SYS_VENDOR, "Google"),
167 			DMI_MATCH(DMI_BOARD_VENDOR, "HP"),
168 		},
169 	},
170 	{ .ident = "MSI",
171 	  .matches = {
172 			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
173 		},
174 	},
175 	{ .ident = "Honor",
176 	  .matches = {
177 			DMI_MATCH(DMI_SYS_VENDOR, "HONOR"),
178 		},
179 	},
180 	/* keep last */
181 	{}
182 };
183 
iwl_sar_geo_support(struct iwl_fw_runtime * fwrt)184 bool iwl_sar_geo_support(struct iwl_fw_runtime *fwrt)
185 {
186 	/*
187 	 * The PER_CHAIN_LIMIT_OFFSET_CMD command is not supported on
188 	 * earlier firmware versions.  Unfortunately, we don't have a
189 	 * TLV API flag to rely on, so rely on the major version which
190 	 * is in the first byte of ucode_ver.  This was implemented
191 	 * initially on version 38 and then backported to 17.  It was
192 	 * also backported to 29, but only for 7265D devices.  The
193 	 * intention was to have it in 36 as well, but not all 8000
194 	 * family got this feature enabled.  The 8000 family is the
195 	 * only one using version 36, so skip this version entirely.
196 	 */
197 	return IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) >= 38 ||
198 		(IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) == 17 &&
199 		 fwrt->trans->info.hw_rev != CSR_HW_REV_TYPE_3160) ||
200 		(IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) == 29 &&
201 		 ((fwrt->trans->info.hw_rev & CSR_HW_REV_TYPE_MSK) ==
202 		  CSR_HW_REV_TYPE_7265D));
203 }
204 IWL_EXPORT_SYMBOL(iwl_sar_geo_support);
205 
iwl_sar_geo_fill_table(struct iwl_fw_runtime * fwrt,struct iwl_per_chain_offset * table,u32 n_bands,u32 n_profiles)206 int iwl_sar_geo_fill_table(struct iwl_fw_runtime *fwrt,
207 			   struct iwl_per_chain_offset *table,
208 			   u32 n_bands, u32 n_profiles)
209 {
210 	int i, j;
211 
212 	if (!fwrt->geo_enabled)
213 		return -ENODATA;
214 
215 	if (!iwl_sar_geo_support(fwrt))
216 		return -EOPNOTSUPP;
217 
218 	for (i = 0; i < n_profiles; i++) {
219 		for (j = 0; j < n_bands; j++) {
220 			struct iwl_per_chain_offset *chain =
221 				&table[i * n_bands + j];
222 
223 			chain->max_tx_power =
224 				cpu_to_le16(fwrt->geo_profiles[i].bands[j].max);
225 			chain->chain_a =
226 				fwrt->geo_profiles[i].bands[j].chains[0];
227 			chain->chain_b =
228 				fwrt->geo_profiles[i].bands[j].chains[1];
229 			IWL_DEBUG_RADIO(fwrt,
230 					"SAR geographic profile[%d] Band[%d]: chain A = %d chain B = %d max_tx_power = %d\n",
231 					i, j,
232 					fwrt->geo_profiles[i].bands[j].chains[0],
233 					fwrt->geo_profiles[i].bands[j].chains[1],
234 					fwrt->geo_profiles[i].bands[j].max);
235 		}
236 	}
237 
238 	return 0;
239 }
240 IWL_EXPORT_SYMBOL(iwl_sar_geo_fill_table);
241 
iwl_sar_fill_table(struct iwl_fw_runtime * fwrt,__le16 * per_chain,u32 n_subbands,int prof_a,int prof_b)242 static int iwl_sar_fill_table(struct iwl_fw_runtime *fwrt,
243 			      __le16 *per_chain, u32 n_subbands,
244 			      int prof_a, int prof_b)
245 {
246 	int profs[BIOS_SAR_NUM_CHAINS] = { prof_a, prof_b };
247 	int i, j;
248 
249 	if (WARN_ON_ONCE(n_subbands >
250 			 ARRAY_SIZE(fwrt->sar_profiles[0].chains[0].subbands)))
251 		return -EINVAL;
252 
253 	for (i = 0; i < BIOS_SAR_NUM_CHAINS; i++) {
254 		struct iwl_sar_profile *prof;
255 
256 		/* don't allow SAR to be disabled (profile 0 means disable) */
257 		if (profs[i] == 0)
258 			return -EPERM;
259 
260 		/* we are off by one, so allow up to BIOS_SAR_MAX_PROFILE_NUM */
261 		if (profs[i] > BIOS_SAR_MAX_PROFILE_NUM)
262 			return -EINVAL;
263 
264 		/* profiles go from 1 to 4, so decrement to access the array */
265 		prof = &fwrt->sar_profiles[profs[i] - 1];
266 
267 		/* if the profile is disabled, do nothing */
268 		if (!prof->enabled) {
269 			IWL_DEBUG_RADIO(fwrt, "SAR profile %d is disabled.\n",
270 					profs[i]);
271 			/*
272 			 * if one of the profiles is disabled, we
273 			 * ignore all of them and return 1 to
274 			 * differentiate disabled from other failures.
275 			 */
276 			return 1;
277 		}
278 
279 		IWL_DEBUG_INFO(fwrt,
280 			       "SAR EWRD: chain %d profile index %d\n",
281 			       i, profs[i]);
282 		IWL_DEBUG_RADIO(fwrt, "  Chain[%d]:\n", i);
283 		for (j = 0; j < n_subbands; j++) {
284 			per_chain[i * n_subbands + j] =
285 				cpu_to_le16(prof->chains[i].subbands[j]);
286 			IWL_DEBUG_RADIO(fwrt, "    Band[%d] = %d * .125dBm\n",
287 					j, prof->chains[i].subbands[j]);
288 		}
289 	}
290 
291 	return 0;
292 }
293 
iwl_sar_fill_profile(struct iwl_fw_runtime * fwrt,__le16 * per_chain,u32 n_tables,u32 n_subbands,int prof_a,int prof_b)294 int iwl_sar_fill_profile(struct iwl_fw_runtime *fwrt,
295 			 __le16 *per_chain, u32 n_tables, u32 n_subbands,
296 			 int prof_a, int prof_b)
297 {
298 	int i, ret = 0;
299 
300 	for (i = 0; i < n_tables; i++) {
301 		ret = iwl_sar_fill_table(fwrt,
302 			&per_chain[i * n_subbands * BIOS_SAR_NUM_CHAINS],
303 			n_subbands, prof_a, prof_b);
304 		if (ret)
305 			break;
306 	}
307 
308 	return ret;
309 }
310 IWL_EXPORT_SYMBOL(iwl_sar_fill_profile);
311 
iwl_is_ppag_approved(struct iwl_fw_runtime * fwrt)312 bool iwl_is_ppag_approved(struct iwl_fw_runtime *fwrt)
313 {
314 	if (!dmi_check_system(dmi_ppag_approved_list)) {
315 		IWL_DEBUG_RADIO(fwrt,
316 				"System vendor '%s' is not in the approved list, disabling PPAG.\n",
317 				dmi_get_system_info(DMI_SYS_VENDOR) ?: "<unknown>");
318 		fwrt->ppag_flags = 0;
319 		return false;
320 	}
321 
322 	return true;
323 }
324 IWL_EXPORT_SYMBOL(iwl_is_ppag_approved);
325 
326 /* Print the PPAG table as read from BIOS */
iwl_bios_print_ppag(struct iwl_fw_runtime * fwrt,int n_subbands)327 void iwl_bios_print_ppag(struct iwl_fw_runtime *fwrt, int n_subbands)
328 {
329 	int i, j;
330 
331 	IWL_DEBUG_RADIO(fwrt, "PPAG table as read from BIOS:\n");
332 	IWL_DEBUG_RADIO(fwrt, "PPAG revision = %d\n", fwrt->ppag_bios_rev);
333 	IWL_DEBUG_RADIO(fwrt, "PPAG flags = 0x%x\n", fwrt->ppag_flags);
334 
335 	if (WARN_ON_ONCE(n_subbands >
336 			 ARRAY_SIZE(fwrt->ppag_chains[0].subbands)))
337 		return;
338 
339 	for (i = 0; i < ARRAY_SIZE(fwrt->ppag_chains); i++)
340 		for (j = 0; j < n_subbands; j++)
341 			IWL_DEBUG_RADIO(fwrt,
342 					"ppag_chains[%d].subbands[%d] = %d\n",
343 					i, j,
344 					fwrt->ppag_chains[i].subbands[j]);
345 }
346 
iwl_is_tas_approved(void)347 bool iwl_is_tas_approved(void)
348 {
349 	return dmi_check_system(dmi_tas_approved_list);
350 }
351 IWL_EXPORT_SYMBOL(iwl_is_tas_approved);
352 
353 struct iwl_tas_selection_data
iwl_parse_tas_selection(const u32 tas_selection_in,const u8 tbl_rev)354 iwl_parse_tas_selection(const u32 tas_selection_in, const u8 tbl_rev)
355 {
356 	struct iwl_tas_selection_data tas_selection_out = {};
357 	u8 override_iec = u32_get_bits(tas_selection_in,
358 				       IWL_WTAS_OVERRIDE_IEC_MSK);
359 	u8 canada_tas_uhb = u32_get_bits(tas_selection_in,
360 					 IWL_WTAS_CANADA_UHB_MSK);
361 	u8 enabled_iec = u32_get_bits(tas_selection_in,
362 				      IWL_WTAS_ENABLE_IEC_MSK);
363 	u8 usa_tas_uhb = u32_get_bits(tas_selection_in,
364 				      IWL_WTAS_USA_UHB_MSK);
365 
366 	if (tbl_rev > 0) {
367 		tas_selection_out.usa_tas_uhb_allowed = usa_tas_uhb;
368 		tas_selection_out.override_tas_iec = override_iec;
369 		tas_selection_out.enable_tas_iec = enabled_iec;
370 	}
371 
372 	if (tbl_rev > 1)
373 		tas_selection_out.canada_tas_uhb_allowed = canada_tas_uhb;
374 
375 	return tas_selection_out;
376 }
377 IWL_EXPORT_SYMBOL(iwl_parse_tas_selection);
378 
iwl_add_mcc_to_tas_block_list(u16 * list,u8 * size,u16 mcc)379 bool iwl_add_mcc_to_tas_block_list(u16 *list, u8 *size, u16 mcc)
380 {
381 	for (int i = 0; i < *size; i++) {
382 		if (list[i] == mcc)
383 			return true;
384 	}
385 
386 	/* Verify that there is room for another country
387 	 * If *size == IWL_WTAS_BLACK_LIST_MAX, then the table is full.
388 	 */
389 	if (*size >= IWL_WTAS_BLACK_LIST_MAX)
390 		return false;
391 
392 	list[(*size)++] = mcc;
393 	return true;
394 }
395 IWL_EXPORT_SYMBOL(iwl_add_mcc_to_tas_block_list);
396 
iwl_bios_get_dsm(struct iwl_fw_runtime * fwrt,enum iwl_dsm_funcs func,u32 * value)397 int iwl_bios_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func,
398 		     u32 *value)
399 {
400 	GET_BIOS_TABLE(dsm, fwrt, func, value);
401 }
402 IWL_EXPORT_SYMBOL(iwl_bios_get_dsm);
403 
iwl_puncturing_is_allowed_in_bios(u32 puncturing,u16 mcc)404 bool iwl_puncturing_is_allowed_in_bios(u32 puncturing, u16 mcc)
405 {
406 	/* Some kind of regulatory mess means we need to currently disallow
407 	 * puncturing in the US and Canada unless enabled in BIOS.
408 	 */
409 	switch (mcc) {
410 	case IWL_MCC_US:
411 		return puncturing & IWL_UEFI_CNV_PUNCTURING_USA_EN_MSK;
412 	case IWL_MCC_CANADA:
413 		return puncturing & IWL_UEFI_CNV_PUNCTURING_CANADA_EN_MSK;
414 	default:
415 		return true;
416 	}
417 }
418 IWL_EXPORT_SYMBOL(iwl_puncturing_is_allowed_in_bios);
419 
iwl_rfi_is_enabled_in_bios(struct iwl_fw_runtime * fwrt)420 bool iwl_rfi_is_enabled_in_bios(struct iwl_fw_runtime *fwrt)
421 {
422 	/* default behaviour is disabled */
423 	u32 value = 0;
424 	int ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_RFI_CONFIG, &value);
425 
426 	if (ret < 0) {
427 		IWL_DEBUG_RADIO(fwrt, "Failed to get DSM RFI, ret=%d\n", ret);
428 		return false;
429 	}
430 
431 	value &= DSM_VALUE_RFI_DISABLE;
432 	/* RFI BIOS CONFIG value can be 0 or 3 only.
433 	 * i.e 0 means DDR and DLVR enabled. 3 means DDR and DLVR disabled.
434 	 * 1 and 2 are invalid BIOS configurations, So, it's not possible to
435 	 * disable ddr/dlvr separately.
436 	 */
437 	if (!value) {
438 		IWL_DEBUG_RADIO(fwrt, "DSM RFI is evaluated to enable\n");
439 		return true;
440 	} else if (value == DSM_VALUE_RFI_DISABLE) {
441 		IWL_DEBUG_RADIO(fwrt, "DSM RFI is evaluated to disable\n");
442 	} else {
443 		IWL_DEBUG_RADIO(fwrt,
444 				"DSM RFI got invalid value, value=%d\n", value);
445 	}
446 
447 	return false;
448 }
449 IWL_EXPORT_SYMBOL(iwl_rfi_is_enabled_in_bios);
450