Contributors: 19
Author Tokens Token Proportion Commits Commit Proportion
Miri Korenblit 663 36.63% 15 23.08%
Matt Chen 369 20.39% 2 3.08%
Anjaneyulu 243 13.43% 7 10.77%
Emmanuel Grumbach 143 7.90% 4 6.15%
Johannes Berg 76 4.20% 6 9.23%
Luciano Coelho 70 3.87% 13 20.00%
Ariel Malamud 56 3.09% 1 1.54%
Somashekhar(Som) 45 2.49% 2 3.08%
Alon Giladi 32 1.77% 2 3.08%
Ron Rindjunsky 24 1.33% 1 1.54%
Gregory Greenman 22 1.22% 1 1.54%
Ihab Zhaika 18 0.99% 1 1.54%
Golan Ben-Ami 17 0.94% 2 3.08%
Nidhish A N 9 0.50% 1 1.54%
Haim Dreyfuss 9 0.50% 2 3.08%
Gil Adam 9 0.50% 2 3.08%
Ayala Barazani 3 0.17% 1 1.54%
striebit 1 0.06% 1 1.54%
Wei Yongjun 1 0.06% 1 1.54%
Total 1810 65


// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
 * Copyright (C) 2023, 2025 Intel Corporation
 */
#include <linux/dmi.h>
#include "iwl-drv.h"
#include "iwl-debug.h"
#include "regulatory.h"
#include "fw/runtime.h"
#include "fw/uefi.h"

#define GET_BIOS_TABLE(__name, ...)					\
do {									\
	int ret = -ENOENT;						\
	if (fwrt->uefi_tables_lock_status > UEFI_WIFI_GUID_UNLOCKED)	\
		ret = iwl_uefi_get_ ## __name(__VA_ARGS__);		\
	if (ret < 0)							\
		ret = iwl_acpi_get_ ## __name(__VA_ARGS__);		\
	return ret;							\
} while (0)

#define IWL_BIOS_TABLE_LOADER(__name)					\
int iwl_bios_get_ ## __name(struct iwl_fw_runtime *fwrt)		\
{GET_BIOS_TABLE(__name, fwrt); }					\
IWL_EXPORT_SYMBOL(iwl_bios_get_ ## __name)

#define IWL_BIOS_TABLE_LOADER_DATA(__name, data_type)			\
int iwl_bios_get_ ## __name(struct iwl_fw_runtime *fwrt,		\
			    data_type * data)				\
{GET_BIOS_TABLE(__name, fwrt, data); }					\
IWL_EXPORT_SYMBOL(iwl_bios_get_ ## __name)

IWL_BIOS_TABLE_LOADER(wrds_table);
IWL_BIOS_TABLE_LOADER(ewrd_table);
IWL_BIOS_TABLE_LOADER(wgds_table);
IWL_BIOS_TABLE_LOADER(ppag_table);
IWL_BIOS_TABLE_LOADER(phy_filters);
IWL_BIOS_TABLE_LOADER_DATA(tas_table, struct iwl_tas_data);
IWL_BIOS_TABLE_LOADER_DATA(pwr_limit, u64);
IWL_BIOS_TABLE_LOADER_DATA(mcc, char);
IWL_BIOS_TABLE_LOADER_DATA(eckv, u32);
IWL_BIOS_TABLE_LOADER_DATA(wbem, u32);
IWL_BIOS_TABLE_LOADER_DATA(dsbr, u32);


static const struct dmi_system_id dmi_ppag_approved_list[] = {
	{ .ident = "HP",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "HP"),
		},
	},
	{ .ident = "SAMSUNG",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"),
		},
	},
	{ .ident = "MSFT",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
		},
	},
	{ .ident = "ASUSTEK",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
		},
	},
	{ .ident = "ASUS",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "ASUS"),
		},
	},
	{ .ident = "GOOGLE-HP",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Google"),
			DMI_MATCH(DMI_BOARD_VENDOR, "HP"),
		},
	},
	{ .ident = "GOOGLE-ASUS",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Google"),
			DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTek COMPUTER INC."),
		},
	},
	{ .ident = "GOOGLE-SAMSUNG",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Google"),
			DMI_MATCH(DMI_BOARD_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"),
		},
	},
	{ .ident = "DELL",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
		},
	},
	{ .ident = "DELL",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
		},
	},
	{ .ident = "RAZER",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Razer"),
		},
	},
	{ .ident = "Honor",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "HONOR"),
		},
	},
	{ .ident = "WIKO",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "WIKO"),
		},
	},
	{}
};

static const struct dmi_system_id dmi_tas_approved_list[] = {
	{ .ident = "HP",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "HP"),
		},
	},
	{ .ident = "SAMSUNG",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"),
		},
	},
		{ .ident = "LENOVO",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
		},
	},
	{ .ident = "DELL",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
		},
	},
	{ .ident = "MSFT",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
		},
	},
	{ .ident = "Acer",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
		},
	},
	{ .ident = "ASUSTEK",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
		},
	},
	{ .ident = "ASUS",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "ASUS"),
		},
	},
	{ .ident = "GOOGLE-HP",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Google"),
			DMI_MATCH(DMI_BOARD_VENDOR, "HP"),
		},
	},
	{ .ident = "MSI",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
		},
	},
	{ .ident = "Honor",
	  .matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "HONOR"),
		},
	},
	/* keep last */
	{}
};

bool iwl_sar_geo_support(struct iwl_fw_runtime *fwrt)
{
	/*
	 * The PER_CHAIN_LIMIT_OFFSET_CMD command is not supported on
	 * earlier firmware versions.  Unfortunately, we don't have a
	 * TLV API flag to rely on, so rely on the major version which
	 * is in the first byte of ucode_ver.  This was implemented
	 * initially on version 38 and then backported to 17.  It was
	 * also backported to 29, but only for 7265D devices.  The
	 * intention was to have it in 36 as well, but not all 8000
	 * family got this feature enabled.  The 8000 family is the
	 * only one using version 36, so skip this version entirely.
	 */
	return IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) >= 38 ||
		(IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) == 17 &&
		 fwrt->trans->info.hw_rev != CSR_HW_REV_TYPE_3160) ||
		(IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) == 29 &&
		 ((fwrt->trans->info.hw_rev & CSR_HW_REV_TYPE_MSK) ==
		  CSR_HW_REV_TYPE_7265D));
}
IWL_EXPORT_SYMBOL(iwl_sar_geo_support);

int iwl_sar_geo_fill_table(struct iwl_fw_runtime *fwrt,
			   struct iwl_per_chain_offset *table,
			   u32 n_bands, u32 n_profiles)
{
	int i, j;

	if (!fwrt->geo_enabled)
		return -ENODATA;

	if (!iwl_sar_geo_support(fwrt))
		return -EOPNOTSUPP;

	for (i = 0; i < n_profiles; i++) {
		for (j = 0; j < n_bands; j++) {
			struct iwl_per_chain_offset *chain =
				&table[i * n_bands + j];

			chain->max_tx_power =
				cpu_to_le16(fwrt->geo_profiles[i].bands[j].max);
			chain->chain_a =
				fwrt->geo_profiles[i].bands[j].chains[0];
			chain->chain_b =
				fwrt->geo_profiles[i].bands[j].chains[1];
			IWL_DEBUG_RADIO(fwrt,
					"SAR geographic profile[%d] Band[%d]: chain A = %d chain B = %d max_tx_power = %d\n",
					i, j,
					fwrt->geo_profiles[i].bands[j].chains[0],
					fwrt->geo_profiles[i].bands[j].chains[1],
					fwrt->geo_profiles[i].bands[j].max);
		}
	}

	return 0;
}
IWL_EXPORT_SYMBOL(iwl_sar_geo_fill_table);

static int iwl_sar_fill_table(struct iwl_fw_runtime *fwrt,
			      __le16 *per_chain, u32 n_subbands,
			      int prof_a, int prof_b)
{
	int profs[BIOS_SAR_NUM_CHAINS] = { prof_a, prof_b };
	int i, j;

	if (WARN_ON_ONCE(n_subbands >
			 ARRAY_SIZE(fwrt->sar_profiles[0].chains[0].subbands)))
		return -EINVAL;

	for (i = 0; i < BIOS_SAR_NUM_CHAINS; i++) {
		struct iwl_sar_profile *prof;

		/* don't allow SAR to be disabled (profile 0 means disable) */
		if (profs[i] == 0)
			return -EPERM;

		/* we are off by one, so allow up to BIOS_SAR_MAX_PROFILE_NUM */
		if (profs[i] > BIOS_SAR_MAX_PROFILE_NUM)
			return -EINVAL;

		/* profiles go from 1 to 4, so decrement to access the array */
		prof = &fwrt->sar_profiles[profs[i] - 1];

		/* if the profile is disabled, do nothing */
		if (!prof->enabled) {
			IWL_DEBUG_RADIO(fwrt, "SAR profile %d is disabled.\n",
					profs[i]);
			/*
			 * if one of the profiles is disabled, we
			 * ignore all of them and return 1 to
			 * differentiate disabled from other failures.
			 */
			return 1;
		}

		IWL_DEBUG_INFO(fwrt,
			       "SAR EWRD: chain %d profile index %d\n",
			       i, profs[i]);
		IWL_DEBUG_RADIO(fwrt, "  Chain[%d]:\n", i);
		for (j = 0; j < n_subbands; j++) {
			per_chain[i * n_subbands + j] =
				cpu_to_le16(prof->chains[i].subbands[j]);
			IWL_DEBUG_RADIO(fwrt, "    Band[%d] = %d * .125dBm\n",
					j, prof->chains[i].subbands[j]);
		}
	}

	return 0;
}

int iwl_sar_fill_profile(struct iwl_fw_runtime *fwrt,
			 __le16 *per_chain, u32 n_tables, u32 n_subbands,
			 int prof_a, int prof_b)
{
	int i, ret = 0;

	for (i = 0; i < n_tables; i++) {
		ret = iwl_sar_fill_table(fwrt,
			&per_chain[i * n_subbands * BIOS_SAR_NUM_CHAINS],
			n_subbands, prof_a, prof_b);
		if (ret)
			break;
	}

	return ret;
}
IWL_EXPORT_SYMBOL(iwl_sar_fill_profile);

bool iwl_is_ppag_approved(struct iwl_fw_runtime *fwrt)
{
	if (!dmi_check_system(dmi_ppag_approved_list)) {
		IWL_DEBUG_RADIO(fwrt,
				"System vendor '%s' is not in the approved list, disabling PPAG.\n",
				dmi_get_system_info(DMI_SYS_VENDOR) ?: "<unknown>");
		fwrt->ppag_flags = 0;
		return false;
	}

	return true;
}
IWL_EXPORT_SYMBOL(iwl_is_ppag_approved);

/* Print the PPAG table as read from BIOS */
void iwl_bios_print_ppag(struct iwl_fw_runtime *fwrt, int n_subbands)
{
	int i, j;

	IWL_DEBUG_RADIO(fwrt, "PPAG table as read from BIOS:\n");
	IWL_DEBUG_RADIO(fwrt, "PPAG revision = %d\n", fwrt->ppag_bios_rev);
	IWL_DEBUG_RADIO(fwrt, "PPAG flags = 0x%x\n", fwrt->ppag_flags);

	if (WARN_ON_ONCE(n_subbands >
			 ARRAY_SIZE(fwrt->ppag_chains[0].subbands)))
		return;

	for (i = 0; i < ARRAY_SIZE(fwrt->ppag_chains); i++)
		for (j = 0; j < n_subbands; j++)
			IWL_DEBUG_RADIO(fwrt,
					"ppag_chains[%d].subbands[%d] = %d\n",
					i, j,
					fwrt->ppag_chains[i].subbands[j]);
}

bool iwl_is_tas_approved(void)
{
	return dmi_check_system(dmi_tas_approved_list);
}
IWL_EXPORT_SYMBOL(iwl_is_tas_approved);

struct iwl_tas_selection_data
iwl_parse_tas_selection(const u32 tas_selection_in, const u8 tbl_rev)
{
	struct iwl_tas_selection_data tas_selection_out = {};
	u8 override_iec = u32_get_bits(tas_selection_in,
				       IWL_WTAS_OVERRIDE_IEC_MSK);
	u8 canada_tas_uhb = u32_get_bits(tas_selection_in,
					 IWL_WTAS_CANADA_UHB_MSK);
	u8 enabled_iec = u32_get_bits(tas_selection_in,
				      IWL_WTAS_ENABLE_IEC_MSK);
	u8 usa_tas_uhb = u32_get_bits(tas_selection_in,
				      IWL_WTAS_USA_UHB_MSK);

	if (tbl_rev > 0) {
		tas_selection_out.usa_tas_uhb_allowed = usa_tas_uhb;
		tas_selection_out.override_tas_iec = override_iec;
		tas_selection_out.enable_tas_iec = enabled_iec;
	}

	if (tbl_rev > 1)
		tas_selection_out.canada_tas_uhb_allowed = canada_tas_uhb;

	return tas_selection_out;
}
IWL_EXPORT_SYMBOL(iwl_parse_tas_selection);

bool iwl_add_mcc_to_tas_block_list(u16 *list, u8 *size, u16 mcc)
{
	for (int i = 0; i < *size; i++) {
		if (list[i] == mcc)
			return true;
	}

	/* Verify that there is room for another country
	 * If *size == IWL_WTAS_BLACK_LIST_MAX, then the table is full.
	 */
	if (*size >= IWL_WTAS_BLACK_LIST_MAX)
		return false;

	list[*size++] = mcc;
	return true;
}
IWL_EXPORT_SYMBOL(iwl_add_mcc_to_tas_block_list);

int iwl_bios_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func,
		     u32 *value)
{
	GET_BIOS_TABLE(dsm, fwrt, func, value);
}
IWL_EXPORT_SYMBOL(iwl_bios_get_dsm);

bool iwl_puncturing_is_allowed_in_bios(u32 puncturing, u16 mcc)
{
	/* Some kind of regulatory mess means we need to currently disallow
	 * puncturing in the US and Canada unless enabled in BIOS.
	 */
	switch (mcc) {
	case IWL_MCC_US:
		return puncturing & IWL_UEFI_CNV_PUNCTURING_USA_EN_MSK;
	case IWL_MCC_CANADA:
		return puncturing & IWL_UEFI_CNV_PUNCTURING_CANADA_EN_MSK;
	default:
		return true;
	}
}
IWL_EXPORT_SYMBOL(iwl_puncturing_is_allowed_in_bios);

bool iwl_rfi_is_enabled_in_bios(struct iwl_fw_runtime *fwrt)
{
	/* default behaviour is disabled */
	u32 value = 0;
	int ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_RFI_CONFIG, &value);

	if (ret < 0) {
		IWL_DEBUG_RADIO(fwrt, "Failed to get DSM RFI, ret=%d\n", ret);
		return false;
	}

	value &= DSM_VALUE_RFI_DISABLE;
	/* RFI BIOS CONFIG value can be 0 or 3 only.
	 * i.e 0 means DDR and DLVR enabled. 3 means DDR and DLVR disabled.
	 * 1 and 2 are invalid BIOS configurations, So, it's not possible to
	 * disable ddr/dlvr separately.
	 */
	if (!value) {
		IWL_DEBUG_RADIO(fwrt, "DSM RFI is evaluated to enable\n");
		return true;
	} else if (value == DSM_VALUE_RFI_DISABLE) {
		IWL_DEBUG_RADIO(fwrt, "DSM RFI is evaluated to disable\n");
	} else {
		IWL_DEBUG_RADIO(fwrt,
				"DSM RFI got invalid value, value=%d\n", value);
	}

	return false;
}
IWL_EXPORT_SYMBOL(iwl_rfi_is_enabled_in_bios);