wifi: iwlwifi: mvm: support set_antenna()
authorEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Thu, 21 Sep 2023 08:57:59 +0000 (11:57 +0300)
committerJohannes Berg <johannes.berg@intel.com>
Mon, 25 Sep 2023 07:14:24 +0000 (09:14 +0200)
set_antenna() is supported only when the device is not started in
mac80211 which translates to the firmware not being loaded in iwlwifi.

The tricky part is that iwlwifi populates the sband data during its boot
and doesn't touch this data afterwards, but if the antenna settings
forbid MIMO, we need to update the sband data.

Rework the nvm parsing code to allow to get an existing nvm_data and
modify the sband with additional constraints (tx / rx chains masks).

Suggested-by: Ben Greear <greearb@candelatech.com>
Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: Gregory Greenman <gregory.greenman@intel.com>
Link: https://lore.kernel.org/r/20230921110726.81d94d630c95.I9473da818cbeeb51b2f89dcc59b00019113e7f55@changeid
[add bugfix from Benjamin for iwl_mvm_get_valid_rx_ant()]
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
drivers/net/wireless/intel/iwlwifi/iwl-eeprom-parse.c
drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c
drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h
drivers/net/wireless/intel/iwlwifi/mvm/fw.c
drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c
drivers/net/wireless/intel/iwlwifi/mvm/mld-mac80211.c
drivers/net/wireless/intel/iwlwifi/mvm/mvm.h
drivers/net/wireless/intel/iwlwifi/mvm/nvm.c
drivers/net/wireless/intel/iwlwifi/mvm/ops.c

index d7a7835b935c7350ce159488471f9bbfceb6cfb3..5aab64c63a137dc52263873aaf7f13cfb8ee4b52 100644 (file)
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
 /*
- * Copyright (C) 2005-2014, 2018-2020 Intel Corporation
+ * Copyright (C) 2005-2014, 2018-2021, 2023 Intel Corporation
  * Copyright (C) 2015 Intel Mobile Communications GmbH
  */
 #include <linux/types.h>
@@ -721,6 +721,9 @@ void iwl_init_ht_hw_capab(struct iwl_trans *trans,
        ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_4;
 
        ht_info->mcs.rx_mask[0] = 0xFF;
+       ht_info->mcs.rx_mask[1] = 0x00;
+       ht_info->mcs.rx_mask[2] = 0x00;
+
        if (rx_chains >= 2)
                ht_info->mcs.rx_mask[1] = 0xFF;
        if (rx_chains >= 3)
index cff1f97536e31f093389af32e3bafa6ebd9c09da..512af3605a2ca180811dba1ff7500d826a52c622 100644 (file)
@@ -962,6 +962,9 @@ iwl_nvm_fixup_sband_iftd(struct iwl_trans *trans,
                        }
                }
        } else {
+               struct ieee80211_he_mcs_nss_supp *he_mcs_nss_supp =
+                       &iftype_data->he_cap.he_mcs_nss_supp;
+
                if (iftype_data->eht_cap.has_eht) {
                        struct ieee80211_eht_mcs_nss_supp *mcs_nss =
                                &iftype_data->eht_cap.eht_mcs_nss_supp;
@@ -980,6 +983,19 @@ iwl_nvm_fixup_sband_iftd(struct iwl_trans *trans,
                        iftype_data->he_cap.he_cap_elem.phy_cap_info[7] |=
                                IEEE80211_HE_PHY_CAP7_MAX_NC_1;
                }
+
+               he_mcs_nss_supp->rx_mcs_80 |=
+                       cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << 2);
+               he_mcs_nss_supp->tx_mcs_80 |=
+                       cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << 2);
+               he_mcs_nss_supp->rx_mcs_160 |=
+                       cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << 2);
+               he_mcs_nss_supp->tx_mcs_160 |=
+                       cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << 2);
+               he_mcs_nss_supp->rx_mcs_80p80 |=
+                       cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << 2);
+               he_mcs_nss_supp->tx_mcs_80p80 |=
+                       cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << 2);
        }
 
        if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210 && !is_ap)
@@ -1052,10 +1068,6 @@ static void iwl_init_he_hw_capab(struct iwl_trans *trans,
        struct ieee80211_sband_iftype_data *iftype_data;
        int i;
 
-       /* should only initialize once */
-       if (WARN_ON(sband->iftype_data))
-               return;
-
        BUILD_BUG_ON(sizeof(data->iftd.low) != sizeof(iwl_he_eht_capa));
        BUILD_BUG_ON(sizeof(data->iftd.high) != sizeof(iwl_he_eht_capa));
        BUILD_BUG_ON(sizeof(data->iftd.uhb) != sizeof(iwl_he_eht_capa));
@@ -1087,6 +1099,37 @@ static void iwl_init_he_hw_capab(struct iwl_trans *trans,
        iwl_init_he_6ghz_capa(trans, data, sband, tx_chains, rx_chains);
 }
 
+void iwl_reinit_cab(struct iwl_trans *trans, struct iwl_nvm_data *data,
+                   u8 tx_chains, u8 rx_chains, const struct iwl_fw *fw)
+{
+       struct ieee80211_supported_band *sband;
+
+       sband = &data->bands[NL80211_BAND_2GHZ];
+       iwl_init_ht_hw_capab(trans, data, &sband->ht_cap, NL80211_BAND_2GHZ,
+                            tx_chains, rx_chains);
+
+       if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax)
+               iwl_init_he_hw_capab(trans, data, sband, tx_chains, rx_chains,
+                                    fw);
+
+       sband = &data->bands[NL80211_BAND_5GHZ];
+       iwl_init_ht_hw_capab(trans, data, &sband->ht_cap, NL80211_BAND_5GHZ,
+                            tx_chains, rx_chains);
+       if (data->sku_cap_11ac_enable && !iwlwifi_mod_params.disable_11ac)
+               iwl_init_vht_hw_capab(trans, data, &sband->vht_cap,
+                                     tx_chains, rx_chains);
+
+       if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax)
+               iwl_init_he_hw_capab(trans, data, sband, tx_chains, rx_chains,
+                                    fw);
+
+       sband = &data->bands[NL80211_BAND_6GHZ];
+       if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax)
+               iwl_init_he_hw_capab(trans, data, sband, tx_chains, rx_chains,
+                                    fw);
+}
+IWL_EXPORT_SYMBOL(iwl_reinit_cab);
+
 static void iwl_init_sbands(struct iwl_trans *trans,
                            struct iwl_nvm_data *data,
                            const void *nvm_ch_flags, u8 tx_chains,
@@ -1365,7 +1408,7 @@ iwl_nvm_no_wide_in_5ghz(struct iwl_trans *trans, const struct iwl_cfg *cfg,
 struct iwl_nvm_data *
 iwl_parse_mei_nvm_data(struct iwl_trans *trans, const struct iwl_cfg *cfg,
                       const struct iwl_mei_nvm *mei_nvm,
-                      const struct iwl_fw *fw)
+                      const struct iwl_fw *fw, u8 tx_ant, u8 rx_ant)
 {
        struct iwl_nvm_data *data;
        u32 sbands_flags = 0;
@@ -1392,6 +1435,10 @@ iwl_parse_mei_nvm_data(struct iwl_trans *trans, const struct iwl_cfg *cfg,
                tx_chains &= data->valid_tx_ant;
        if (data->valid_rx_ant)
                rx_chains &= data->valid_rx_ant;
+       if (tx_ant)
+               tx_chains &= tx_ant;
+       if (rx_ant)
+               rx_chains &= rx_ant;
 
        data->sku_cap_mimo_disabled = false;
        data->sku_cap_band_24ghz_enable = true;
@@ -1957,7 +2004,8 @@ out:
 IWL_EXPORT_SYMBOL(iwl_read_external_nvm);
 
 struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans,
-                                const struct iwl_fw *fw)
+                                const struct iwl_fw *fw,
+                                u8 set_tx_ant, u8 set_rx_ant)
 {
        struct iwl_nvm_get_info cmd = {};
        struct iwl_nvm_data *nvm;
@@ -1971,6 +2019,9 @@ struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans,
        bool empty_otp;
        u32 mac_flags;
        u32 sbands_flags = 0;
+       u8 tx_ant;
+       u8 rx_ant;
+
        /*
         * All the values in iwl_nvm_get_info_rsp v4 are the same as
         * in v3, except for the channel profile part of the
@@ -2058,10 +2109,15 @@ struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans,
        channel_profile = v4 ? (void *)rsp->regulatory.channel_profile :
                          (void *)rsp_v3->regulatory.channel_profile;
 
-       iwl_init_sbands(trans, nvm,
-                       channel_profile,
-                       nvm->valid_tx_ant & fw->valid_tx_ant,
-                       nvm->valid_rx_ant & fw->valid_rx_ant,
+       tx_ant = nvm->valid_tx_ant & fw->valid_tx_ant;
+       rx_ant = nvm->valid_rx_ant & fw->valid_rx_ant;
+
+       if (set_tx_ant)
+               tx_ant &= set_tx_ant;
+       if (set_rx_ant)
+               rx_ant &= set_rx_ant;
+
+       iwl_init_sbands(trans, nvm, channel_profile, tx_ant, rx_ant,
                        sbands_flags, v4, fw);
 
        iwl_free_resp(&hcmd);
index c79f72d5448273870507d100888def3e3774952e..651ed25b683b106960307b6ca071e59ce92ab16d 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
 /*
- * Copyright (C) 2005-2015, 2018-2022 Intel Corporation
+ * Copyright (C) 2005-2015, 2018-2023 Intel Corporation
  * Copyright (C) 2016-2017 Intel Deutschland GmbH
  */
 #ifndef __iwl_nvm_parse_h__
@@ -21,7 +21,7 @@ enum iwl_nvm_sbands_flags {
        IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ    = BIT(1),
 };
 
-/**
+/*
  * iwl_parse_nvm_data - parse NVM data and return values
  *
  * This function parses all NVM values we need and then
@@ -73,21 +73,28 @@ int iwl_read_external_nvm(struct iwl_trans *trans,
 void iwl_nvm_fixups(u32 hw_id, unsigned int section, u8 *data,
                    unsigned int len);
 
-/**
+/*
  * iwl_get_nvm - retrieve NVM data from firmware
  *
  * Allocates a new iwl_nvm_data structure, fills it with
  * NVM data, and returns it to caller.
  */
 struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans,
-                                const struct iwl_fw *fw);
+                                const struct iwl_fw *fw,
+                                u8 set_tx_ant, u8 set_rx_ant);
 
-/**
+/*
  * iwl_parse_mei_nvm_data - parse the mei_nvm_data and get an iwl_nvm_data
  */
 struct iwl_nvm_data *
 iwl_parse_mei_nvm_data(struct iwl_trans *trans, const struct iwl_cfg *cfg,
                       const struct iwl_mei_nvm *mei_nvm,
-                      const struct iwl_fw *fw);
+                      const struct iwl_fw *fw, u8 set_tx_ant, u8 set_rx_ant);
+
+/*
+ * iwl_reinit_cab - to be called when the tx_chains or rx_chains are modified
+ */
+void iwl_reinit_cab(struct iwl_trans *trans, struct iwl_nvm_data *data,
+                   u8 tx_chains, u8 rx_chains, const struct iwl_fw *fw);
 
 #endif /* __iwl_nvm_parse_h__ */
index 567b02754a438ad9cd9140eb4b327f420f75c83d..6e5c0f81e041ba360819555d4a722d3a5d9bc800 100644 (file)
@@ -681,7 +681,8 @@ static int iwl_run_unified_mvm_ucode(struct iwl_mvm *mvm)
 
        /* Read the NVM only at driver load time, no need to do this twice */
        if (!IWL_MVM_PARSE_NVM && !mvm->nvm_data) {
-               mvm->nvm_data = iwl_get_nvm(mvm->trans, mvm->fw);
+               mvm->nvm_data = iwl_get_nvm(mvm->trans, mvm->fw,
+                                           mvm->set_tx_ant, mvm->set_rx_ant);
                if (IS_ERR(mvm->nvm_data)) {
                        ret = PTR_ERR(mvm->nvm_data);
                        mvm->nvm_data = NULL;
index ba3109d0eb2b929d3a929e585f4d18b36ba53504..6fc5b3f227463f77c0cf8b49b70dc6bbd402d926 100644 (file)
@@ -279,6 +279,30 @@ int iwl_mvm_op_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant)
        return 0;
 }
 
+int iwl_mvm_op_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant)
+{
+       struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+
+       /* This has been tested on those devices only */
+       if (mvm->trans->trans_cfg->device_family != IWL_DEVICE_FAMILY_9000 &&
+           mvm->trans->trans_cfg->device_family != IWL_DEVICE_FAMILY_22000)
+               return -ENOTSUPP;
+
+       if (!mvm->nvm_data)
+               return -EBUSY;
+
+       /* mac80211 ensures the device is not started,
+        * so the firmware cannot be running
+        */
+
+       mvm->set_tx_ant = tx_ant;
+       mvm->set_rx_ant = rx_ant;
+
+       iwl_reinit_cab(mvm->trans, mvm->nvm_data, tx_ant, rx_ant, mvm->fw);
+
+       return 0;
+}
+
 int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
 {
        struct ieee80211_hw *hw = mvm->hw;
@@ -6202,6 +6226,7 @@ const struct ieee80211_ops iwl_mvm_hw_ops = {
        .wake_tx_queue = iwl_mvm_mac_wake_tx_queue,
        .ampdu_action = iwl_mvm_mac_ampdu_action,
        .get_antenna = iwl_mvm_op_get_antenna,
+       .set_antenna = iwl_mvm_op_set_antenna,
        .start = iwl_mvm_mac_start,
        .reconfig_complete = iwl_mvm_mac_reconfig_complete,
        .stop = iwl_mvm_mac_stop,
index 9615bfff7f7de3ba2eedaa4a94917dd042e1337b..5449deb3c2d6933096b5ce254ab3dc576aefb804 100644 (file)
@@ -1121,6 +1121,7 @@ const struct ieee80211_ops iwl_mvm_mld_hw_ops = {
        .wake_tx_queue = iwl_mvm_mac_wake_tx_queue,
        .ampdu_action = iwl_mvm_mac_ampdu_action,
        .get_antenna = iwl_mvm_op_get_antenna,
+       .set_antenna = iwl_mvm_op_set_antenna,
        .start = iwl_mvm_mac_start,
        .reconfig_complete = iwl_mvm_mac_reconfig_complete,
        .stop = iwl_mvm_mac_stop,
index f0f9a166544349528cecc8c656bb703cde602b93..66d9de0f15117fe61bf3cf1e7a2123242c3ff431 100644 (file)
@@ -980,6 +980,9 @@ struct iwl_mvm {
        u8 scan_last_antenna_idx; /* to toggle TX between antennas */
        u8 mgmt_last_antenna_idx;
 
+       u8 set_tx_ant;
+       u8 set_rx_ant;
+
        /* last smart fifo state that was successfully sent to firmware */
        enum iwl_sf_state sf_state;
 
@@ -1715,16 +1718,29 @@ int iwl_mvm_load_nvm_to_nic(struct iwl_mvm *mvm);
 
 static inline u8 iwl_mvm_get_valid_tx_ant(struct iwl_mvm *mvm)
 {
-       return mvm->nvm_data && mvm->nvm_data->valid_tx_ant ?
-              mvm->fw->valid_tx_ant & mvm->nvm_data->valid_tx_ant :
-              mvm->fw->valid_tx_ant;
+       u8 tx_ant = mvm->fw->valid_tx_ant;
+
+       if (mvm->nvm_data && mvm->nvm_data->valid_tx_ant)
+               tx_ant &= mvm->nvm_data->valid_tx_ant;
+
+       if (mvm->set_tx_ant)
+               tx_ant &= mvm->set_tx_ant;
+
+       return tx_ant;
 }
 
 static inline u8 iwl_mvm_get_valid_rx_ant(struct iwl_mvm *mvm)
 {
-       return mvm->nvm_data && mvm->nvm_data->valid_rx_ant ?
-              mvm->fw->valid_rx_ant & mvm->nvm_data->valid_rx_ant :
-              mvm->fw->valid_rx_ant;
+       u8 rx_ant = mvm->fw->valid_tx_ant;
+
+       if (mvm->nvm_data && mvm->nvm_data->valid_rx_ant)
+               rx_ant &= mvm->nvm_data->valid_tx_ant;
+
+       if (mvm->set_rx_ant)
+               rx_ant &= mvm->set_rx_ant;
+
+       return rx_ant;
+
 }
 
 static inline void iwl_mvm_toggle_tx_ant(struct iwl_mvm *mvm, u8 *ant)
@@ -2625,6 +2641,7 @@ int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw,
                             struct ieee80211_vif *vif,
                             struct ieee80211_ampdu_params *params);
 int iwl_mvm_op_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant);
+int iwl_mvm_op_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant);
 int iwl_mvm_mac_start(struct ieee80211_hw *hw);
 void iwl_mvm_mac_reconfig_complete(struct ieee80211_hw *hw,
                                   enum ieee80211_reconfig_type reconfig_type);
index f67ab8ee18c243a7bb92f063a227301c0900de41..17a1e5717ddef632efb4769ec48e4b1b4514de20 100644 (file)
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
 /*
- * Copyright (C) 2012-2014, 2018-2019, 2021 Intel Corporation
+ * Copyright (C) 2012-2014, 2018-2019, 2021-2023 Intel Corporation
  * Copyright (C) 2013-2015 Intel Mobile Communications GmbH
  * Copyright (C) 2016-2017 Intel Deutschland GmbH
  */
@@ -220,6 +220,8 @@ iwl_parse_nvm_sections(struct iwl_mvm *mvm)
        struct iwl_nvm_section *sections = mvm->nvm_sections;
        const __be16 *hw;
        const __le16 *sw, *calib, *regulatory, *mac_override, *phy_sku;
+       u8 tx_ant = mvm->fw->valid_tx_ant;
+       u8 rx_ant = mvm->fw->valid_rx_ant;
        int regulatory_type;
 
        /* Checking for required sections */
@@ -270,9 +272,15 @@ iwl_parse_nvm_sections(struct iwl_mvm *mvm)
                (const __le16 *)sections[NVM_SECTION_TYPE_REGULATORY_SDP].data :
                (const __le16 *)sections[NVM_SECTION_TYPE_REGULATORY].data;
 
+       if (mvm->set_tx_ant)
+               tx_ant &= mvm->set_tx_ant;
+
+       if (mvm->set_rx_ant)
+               rx_ant &= mvm->set_rx_ant;
+
        return iwl_parse_nvm_data(mvm->trans, mvm->cfg, mvm->fw, hw, sw, calib,
                                  regulatory, mac_override, phy_sku,
-                                 mvm->fw->valid_tx_ant, mvm->fw->valid_rx_ant);
+                                 tx_ant, rx_ant);
 }
 
 /* Loads the NVM data stored in mvm->nvm_sections into the NIC */
index 1c21a313f8f1ed960db2cb81e55bd46adac3749d..465090f67aaf2908cf10135ac1e7dbbd2c661b72 100644 (file)
@@ -751,7 +751,10 @@ static int iwl_mvm_start_get_nvm(struct iwl_mvm *mvm)
                         */
                        mvm->nvm_data =
                                iwl_parse_mei_nvm_data(trans, trans->cfg,
-                                                      mvm->mei_nvm_data, mvm->fw);
+                                                      mvm->mei_nvm_data,
+                                                      mvm->fw,
+                                                      mvm->set_tx_ant,
+                                                      mvm->set_rx_ant);
                        return 0;
                }