new file mode 100644
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (C) 2024 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_parse_mcc_update_resp_v8(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_parse_mcc_update_resp_v5_v6(const struct iwl_rx_packet *pkt)
+{
+ const struct iwl_mcc_update_resp_v4 *mcc_resp_v4 = (const void *)pkt->data;
+ struct iwl_mcc_update_resp_v8 *resp_cp;
+ int n_channels = __le32_to_cpu(mcc_resp_v4->n_channels);
+ int resp_len;
+
+ if (iwl_rx_packet_payload_len(pkt) !=
+ struct_size(mcc_resp_v4, channels, n_channels))
+ return ERR_PTR(-EINVAL);
+
+ resp_len = struct_size(resp_cp, channels, n_channels);
+ resp_cp = kzalloc(resp_len, GFP_KERNEL);
+ if (!resp_cp)
+ return ERR_PTR(-ENOMEM);
+
+ resp_cp->status = mcc_resp_v4->status;
+ resp_cp->mcc = mcc_resp_v4->mcc;
+ resp_cp->cap = cpu_to_le32(le16_to_cpu(mcc_resp_v4->cap));
+ resp_cp->source_id = mcc_resp_v4->source_id;
+ resp_cp->geo_info = mcc_resp_v4->geo_info;
+ resp_cp->n_channels = mcc_resp_v4->n_channels;
+ memcpy(resp_cp->channels, mcc_resp_v4->channels,
+ n_channels * sizeof(__le32));
+
+ 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)
+{
+ int resp_ver = iwl_fw_lookup_notif_ver(mld->fw, LONG_GROUP,
+ MCC_UPDATE_CMD, 0);
+ 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;
+
+ /* For Wifi-7 radios, we get version 8
+ * For Wifi-6E radios, we get version 6
+ * For Wifi-6 radios, we get version 5, but 5, 6, and 4 are compatible.
+ */
+ switch (resp_ver) {
+ case 5:
+ case 6:
+ resp_cp = iwl_mld_parse_mcc_update_resp_v5_v6(pkt);
+ break;
+ case 8:
+ resp_cp = iwl_mld_parse_mcc_update_resp_v8(pkt);
+ break;
+ default:
+ IWL_FW_CHECK_FAILED(mld, "Unknown MCC_UPDATE_CMD version %d\n", resp_ver);
+ resp_cp = ERR_PTR(-EINVAL);
+ }
+
+ 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->dev, mld->cfg,
+ __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;
+
+ 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);
+}
new file mode 100644
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2024 Intel Corporation
+ */
+#ifndef __iwl_mld_mcc_h__
+#define __iwl_mld_mcc_h__
+
+int iwl_mld_init_mcc(struct iwl_mld *mld);
+void iwl_mld_handle_update_mcc(struct iwl_mld *mld, struct iwl_rx_packet *pkt);
+void iwl_mld_update_changed_regdomain(struct iwl_mld *mld);
+struct ieee80211_regdomain *
+iwl_mld_get_regdomain(struct iwl_mld *mld,
+ const char *alpha2,
+ enum iwl_mcc_source src_id,
+ bool *changed);
+
+#endif /* __iwl_mld_mcc_h__ */
this file is handling mobile country/regdomain functionality Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com> --- drivers/net/wireless/intel/iwlwifi/mld/mcc.c | 329 +++++++++++++++++++ drivers/net/wireless/intel/iwlwifi/mld/mcc.h | 17 + 2 files changed, 346 insertions(+) create mode 100644 drivers/net/wireless/intel/iwlwifi/mld/mcc.c create mode 100644 drivers/net/wireless/intel/iwlwifi/mld/mcc.h