diff options
Diffstat (limited to 'sys/contrib/dev/iwlwifi/mld/mcc.c')
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/mcc.c | 285 | 
1 files changed, 285 insertions, 0 deletions
| diff --git a/sys/contrib/dev/iwlwifi/mld/mcc.c b/sys/contrib/dev/iwlwifi/mld/mcc.c new file mode 100644 index 000000000000..16bb1b4904f9 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/mcc.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2024-2025 Intel Corporation + */ + +#include <net/cfg80211.h> +#include <net/mac80211.h> + +#include <fw/dbg.h> +#include <iwl-nvm-parse.h> + +#include "mld.h" +#include "hcmd.h" +#include "mcc.h" + +/* It is the caller's responsibility to free the pointer returned here */ +static struct iwl_mcc_update_resp_v8 * +iwl_mld_copy_mcc_resp(const struct iwl_rx_packet *pkt) +{ +	const struct iwl_mcc_update_resp_v8 *mcc_resp_v8 = (const void *)pkt->data; +	int n_channels = __le32_to_cpu(mcc_resp_v8->n_channels); +	struct iwl_mcc_update_resp_v8 *resp_cp; +	int notif_len = struct_size(resp_cp, channels, n_channels); + +	if (iwl_rx_packet_payload_len(pkt) != notif_len) +		return ERR_PTR(-EINVAL); + +	resp_cp = kmemdup(mcc_resp_v8, notif_len, GFP_KERNEL); +	if (!resp_cp) +		return ERR_PTR(-ENOMEM); + +	return resp_cp; +} + +/* It is the caller's responsibility to free the pointer returned here */ +static struct iwl_mcc_update_resp_v8 * +iwl_mld_update_mcc(struct iwl_mld *mld, const char *alpha2, +		   enum iwl_mcc_source src_id) +{ +	struct iwl_mcc_update_cmd mcc_update_cmd = { +		.mcc = cpu_to_le16(alpha2[0] << 8 | alpha2[1]), +		.source_id = (u8)src_id, +	}; +	struct iwl_mcc_update_resp_v8 *resp_cp; +	struct iwl_rx_packet *pkt; +	struct iwl_host_cmd cmd = { +		.id = MCC_UPDATE_CMD, +		.flags = CMD_WANT_SKB, +		.data = { &mcc_update_cmd }, +		.len[0] = sizeof(mcc_update_cmd), +	}; +	int ret; +	u16 mcc; + +	IWL_DEBUG_LAR(mld, "send MCC update to FW with '%c%c' src = %d\n", +		      alpha2[0], alpha2[1], src_id); + +	ret = iwl_mld_send_cmd(mld, &cmd); +	if (ret) +		return ERR_PTR(ret); + +	pkt = cmd.resp_pkt; + +	resp_cp = iwl_mld_copy_mcc_resp(pkt); +	if (IS_ERR(resp_cp)) +		goto exit; + +	mcc = le16_to_cpu(resp_cp->mcc); + +	IWL_FW_CHECK(mld, !mcc, "mcc can't be 0: %d\n", mcc); + +	IWL_DEBUG_LAR(mld, +		      "MCC response status: 0x%x. new MCC: 0x%x ('%c%c')\n", +		      le32_to_cpu(resp_cp->status), mcc, mcc >> 8, mcc & 0xff); + +exit: +	iwl_free_resp(&cmd); +	return resp_cp; +} + +/* It is the caller's responsibility to free the pointer returned here */ +struct ieee80211_regdomain * +iwl_mld_get_regdomain(struct iwl_mld *mld, +		      const char *alpha2, +		      enum iwl_mcc_source src_id, +		      bool *changed) +{ +	struct ieee80211_regdomain *regd = NULL; +	struct iwl_mcc_update_resp_v8 *resp; +	u8 resp_ver = iwl_fw_lookup_notif_ver(mld->fw, IWL_ALWAYS_LONG_GROUP, +					      MCC_UPDATE_CMD, 0); + +	IWL_DEBUG_LAR(mld, "Getting regdomain data for %s from FW\n", alpha2); + +	lockdep_assert_wiphy(mld->wiphy); + +	resp = iwl_mld_update_mcc(mld, alpha2, src_id); +	if (IS_ERR(resp)) { +		IWL_DEBUG_LAR(mld, "Could not get update from FW %ld\n", +			      PTR_ERR(resp)); +		resp = NULL; +		goto out; +	} + +	if (changed) { +		u32 status = le32_to_cpu(resp->status); + +		*changed = (status == MCC_RESP_NEW_CHAN_PROFILE || +			    status == MCC_RESP_ILLEGAL); +	} +	IWL_DEBUG_LAR(mld, "MCC update response version: %d\n", resp_ver); + +	regd = iwl_parse_nvm_mcc_info(mld->trans, +				      __le32_to_cpu(resp->n_channels), +				      resp->channels, +				      __le16_to_cpu(resp->mcc), +				      __le16_to_cpu(resp->geo_info), +				      le32_to_cpu(resp->cap), resp_ver); + +	if (IS_ERR(regd)) { +		IWL_DEBUG_LAR(mld, "Could not get parse update from FW %ld\n", +			      PTR_ERR(regd)); +		goto out; +	} + +	IWL_DEBUG_LAR(mld, "setting alpha2 from FW to %s (0x%x, 0x%x) src=%d\n", +		      regd->alpha2, regd->alpha2[0], +		      regd->alpha2[1], resp->source_id); + +	mld->mcc_src = resp->source_id; + +	/* FM is the earliest supported and later always do puncturing */ +	if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) == IWL_CFG_RF_TYPE_FM) { +		if (!iwl_puncturing_is_allowed_in_bios(mld->bios_enable_puncturing, +						       le16_to_cpu(resp->mcc))) +			ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING); +		else +			__clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING, +				    mld->hw->flags); +	} + +out: +	kfree(resp); +	return regd; +} + +/* It is the caller's responsibility to free the pointer returned here */ +static struct ieee80211_regdomain * +iwl_mld_get_current_regdomain(struct iwl_mld *mld, +			      bool *changed) +{ +	return iwl_mld_get_regdomain(mld, "ZZ", +				     MCC_SOURCE_GET_CURRENT, changed); +} + +void iwl_mld_update_changed_regdomain(struct iwl_mld *mld) +{ +	struct ieee80211_regdomain *regd; +	bool changed; + +	regd = iwl_mld_get_current_regdomain(mld, &changed); + +	if (IS_ERR_OR_NULL(regd)) +		return; + +	if (changed) +		regulatory_set_wiphy_regd(mld->wiphy, regd); +	kfree(regd); +} + +static int iwl_mld_apply_last_mcc(struct iwl_mld *mld, +				  const char *alpha2) +{ +	struct ieee80211_regdomain *regd; +	u32 used_src; +	bool changed; +	int ret; + +	/* save the last source in case we overwrite it below */ +	used_src = mld->mcc_src; + +	/* Notify the firmware we support wifi location updates */ +	regd = iwl_mld_get_current_regdomain(mld, NULL); +	if (!IS_ERR_OR_NULL(regd)) +		kfree(regd); + +	/* Now set our last stored MCC and source */ +	regd = iwl_mld_get_regdomain(mld, alpha2, used_src, +				     &changed); +	if (IS_ERR_OR_NULL(regd)) +		return -EIO; + +	/* update cfg80211 if the regdomain was changed */ +	if (changed) +		ret = regulatory_set_wiphy_regd_sync(mld->wiphy, regd); +	else +		ret = 0; + +	kfree(regd); +	return ret; +} + +int iwl_mld_init_mcc(struct iwl_mld *mld) +{ +	const struct ieee80211_regdomain *r; +	struct ieee80211_regdomain *regd; +	char mcc[3]; +	int retval; + +	/* try to replay the last set MCC to FW */ +	r = wiphy_dereference(mld->wiphy, mld->wiphy->regd); + +	if (r) +		return iwl_mld_apply_last_mcc(mld, r->alpha2); + +	regd = iwl_mld_get_current_regdomain(mld, NULL); +	if (IS_ERR_OR_NULL(regd)) +		return -EIO; + +	if (!iwl_bios_get_mcc(&mld->fwrt, mcc)) { +		kfree(regd); +		regd = iwl_mld_get_regdomain(mld, mcc, MCC_SOURCE_BIOS, NULL); +		if (IS_ERR_OR_NULL(regd)) +			return -EIO; +	} + +	retval = regulatory_set_wiphy_regd_sync(mld->wiphy, regd); + +	kfree(regd); +	return retval; +} + +static void iwl_mld_find_assoc_vif_iterator(void *data, u8 *mac, +					    struct ieee80211_vif *vif) +{ +	bool *assoc = data; + +	if (vif->type == NL80211_IFTYPE_STATION && +	    vif->cfg.assoc) +		*assoc = true; +} + +static bool iwl_mld_is_a_vif_assoc(struct iwl_mld *mld) +{ +	bool assoc = false; + +	ieee80211_iterate_active_interfaces_atomic(mld->hw, +						   IEEE80211_IFACE_ITER_NORMAL, +						   iwl_mld_find_assoc_vif_iterator, +						   &assoc); +	return assoc; +} + +void iwl_mld_handle_update_mcc(struct iwl_mld *mld, struct iwl_rx_packet *pkt) +{ +	struct iwl_mcc_chub_notif *notif = (void *)pkt->data; +	enum iwl_mcc_source src; +	char mcc[3]; +	struct ieee80211_regdomain *regd; +	bool changed; + +	lockdep_assert_wiphy(mld->wiphy); + +	if (iwl_mld_is_a_vif_assoc(mld) && +	    notif->source_id == MCC_SOURCE_WIFI) { +		IWL_DEBUG_LAR(mld, "Ignore mcc update while associated\n"); +		return; +	} + +	mcc[0] = le16_to_cpu(notif->mcc) >> 8; +	mcc[1] = le16_to_cpu(notif->mcc) & 0xff; +	mcc[2] = '\0'; +	src = notif->source_id; + +	IWL_DEBUG_LAR(mld, +		      "RX: received chub update mcc cmd (mcc '%s' src %d)\n", +		      mcc, src); +	regd = iwl_mld_get_regdomain(mld, mcc, src, &changed); +	if (IS_ERR_OR_NULL(regd)) +		return; + +	if (changed) +		regulatory_set_wiphy_regd(mld->hw->wiphy, regd); +	kfree(regd); +} | 
