1d1e879ecSMiri Korenblit // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2d1e879ecSMiri Korenblit /* 3d1e879ecSMiri Korenblit * Copyright (C) 2024-2025 Intel Corporation 4d1e879ecSMiri Korenblit */ 5d1e879ecSMiri Korenblit 6d1e879ecSMiri Korenblit #include "constants.h" 7d1e879ecSMiri Korenblit #include "link.h" 8d1e879ecSMiri Korenblit #include "iface.h" 9d1e879ecSMiri Korenblit #include "mlo.h" 10d1e879ecSMiri Korenblit #include "hcmd.h" 11d1e879ecSMiri Korenblit #include "phy.h" 12d1e879ecSMiri Korenblit #include "fw/api/rs.h" 13d1e879ecSMiri Korenblit #include "fw/api/txq.h" 14d1e879ecSMiri Korenblit #include "fw/api/mac.h" 15d1e879ecSMiri Korenblit 16d1e879ecSMiri Korenblit #include "fw/api/context.h" 17d1e879ecSMiri Korenblit #include "fw/dbg.h" 18d1e879ecSMiri Korenblit 19d1e879ecSMiri Korenblit static int iwl_mld_send_link_cmd(struct iwl_mld *mld, 20d1e879ecSMiri Korenblit struct iwl_link_config_cmd *cmd, 21d1e879ecSMiri Korenblit enum iwl_ctxt_action action) 22d1e879ecSMiri Korenblit { 23d1e879ecSMiri Korenblit int ret; 24d1e879ecSMiri Korenblit 25d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 26d1e879ecSMiri Korenblit 27d1e879ecSMiri Korenblit cmd->action = cpu_to_le32(action); 28d1e879ecSMiri Korenblit ret = iwl_mld_send_cmd_pdu(mld, 29d1e879ecSMiri Korenblit WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD), 30d1e879ecSMiri Korenblit cmd); 31d1e879ecSMiri Korenblit if (ret) 32d1e879ecSMiri Korenblit IWL_ERR(mld, "Failed to send LINK_CONFIG_CMD (action:%d): %d\n", 33d1e879ecSMiri Korenblit action, ret); 34d1e879ecSMiri Korenblit return ret; 35d1e879ecSMiri Korenblit } 36d1e879ecSMiri Korenblit 37d1e879ecSMiri Korenblit static int iwl_mld_add_link_to_fw(struct iwl_mld *mld, 38d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf) 39d1e879ecSMiri Korenblit { 40d1e879ecSMiri Korenblit struct ieee80211_vif *vif = link_conf->vif; 41d1e879ecSMiri Korenblit struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); 42d1e879ecSMiri Korenblit struct iwl_mld_link *link = iwl_mld_link_from_mac80211(link_conf); 43d1e879ecSMiri Korenblit struct iwl_link_config_cmd cmd = {}; 44d1e879ecSMiri Korenblit 45d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 46d1e879ecSMiri Korenblit 47d1e879ecSMiri Korenblit if (WARN_ON(!link)) 48d1e879ecSMiri Korenblit return -EINVAL; 49d1e879ecSMiri Korenblit 50d1e879ecSMiri Korenblit cmd.link_id = cpu_to_le32(link->fw_id); 51d1e879ecSMiri Korenblit cmd.mac_id = cpu_to_le32(mld_vif->fw_id); 52d1e879ecSMiri Korenblit cmd.spec_link_id = link_conf->link_id; 53d1e879ecSMiri Korenblit cmd.phy_id = cpu_to_le32(FW_CTXT_ID_INVALID); 54d1e879ecSMiri Korenblit 55d1e879ecSMiri Korenblit ether_addr_copy(cmd.local_link_addr, link_conf->addr); 56d1e879ecSMiri Korenblit 57d1e879ecSMiri Korenblit if (vif->type == NL80211_IFTYPE_ADHOC && link_conf->bssid) 58d1e879ecSMiri Korenblit ether_addr_copy(cmd.ibss_bssid_addr, link_conf->bssid); 59d1e879ecSMiri Korenblit 60d1e879ecSMiri Korenblit return iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_ADD); 61d1e879ecSMiri Korenblit } 62d1e879ecSMiri Korenblit 63d1e879ecSMiri Korenblit /* Get the basic rates of the used band and add the mandatory ones */ 64d1e879ecSMiri Korenblit static void iwl_mld_fill_rates(struct iwl_mld *mld, 65d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link, 66d1e879ecSMiri Korenblit struct ieee80211_chanctx_conf *chan_ctx, 67d1e879ecSMiri Korenblit __le32 *cck_rates, __le32 *ofdm_rates) 68d1e879ecSMiri Korenblit { 69d1e879ecSMiri Korenblit struct cfg80211_chan_def *chandef = 70fd04fbeeSJohannes Berg iwl_mld_get_chandef_from_chanctx(mld, chan_ctx); 71d1e879ecSMiri Korenblit struct ieee80211_supported_band *sband = 72d1e879ecSMiri Korenblit mld->hw->wiphy->bands[chandef->chan->band]; 73d1e879ecSMiri Korenblit unsigned long basic = link->basic_rates; 74d1e879ecSMiri Korenblit int lowest_present_ofdm = 100; 75d1e879ecSMiri Korenblit int lowest_present_cck = 100; 76d1e879ecSMiri Korenblit u32 cck = 0; 77d1e879ecSMiri Korenblit u32 ofdm = 0; 78d1e879ecSMiri Korenblit int i; 79d1e879ecSMiri Korenblit 80d1e879ecSMiri Korenblit for_each_set_bit(i, &basic, BITS_PER_LONG) { 81d1e879ecSMiri Korenblit int hw = sband->bitrates[i].hw_value; 82d1e879ecSMiri Korenblit 83d1e879ecSMiri Korenblit if (hw >= IWL_FIRST_OFDM_RATE) { 84d1e879ecSMiri Korenblit ofdm |= BIT(hw - IWL_FIRST_OFDM_RATE); 85d1e879ecSMiri Korenblit if (lowest_present_ofdm > hw) 86d1e879ecSMiri Korenblit lowest_present_ofdm = hw; 87d1e879ecSMiri Korenblit } else { 88d1e879ecSMiri Korenblit BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0); 89d1e879ecSMiri Korenblit 90d1e879ecSMiri Korenblit cck |= BIT(hw); 91d1e879ecSMiri Korenblit if (lowest_present_cck > hw) 92d1e879ecSMiri Korenblit lowest_present_cck = hw; 93d1e879ecSMiri Korenblit } 94d1e879ecSMiri Korenblit } 95d1e879ecSMiri Korenblit 96d1e879ecSMiri Korenblit /* Now we've got the basic rates as bitmaps in the ofdm and cck 97d1e879ecSMiri Korenblit * variables. This isn't sufficient though, as there might not 98d1e879ecSMiri Korenblit * be all the right rates in the bitmap. E.g. if the only basic 99d1e879ecSMiri Korenblit * rates are 5.5 Mbps and 11 Mbps, we still need to add 1 Mbps 100d1e879ecSMiri Korenblit * and 6 Mbps because the 802.11-2007 standard says in 9.6: 101d1e879ecSMiri Korenblit * 102d1e879ecSMiri Korenblit * [...] a STA responding to a received frame shall transmit 103d1e879ecSMiri Korenblit * its Control Response frame [...] at the highest rate in the 104d1e879ecSMiri Korenblit * BSSBasicRateSet parameter that is less than or equal to the 105d1e879ecSMiri Korenblit * rate of the immediately previous frame in the frame exchange 106d1e879ecSMiri Korenblit * sequence ([...]) and that is of the same modulation class 107d1e879ecSMiri Korenblit * ([...]) as the received frame. If no rate contained in the 108d1e879ecSMiri Korenblit * BSSBasicRateSet parameter meets these conditions, then the 109d1e879ecSMiri Korenblit * control frame sent in response to a received frame shall be 110d1e879ecSMiri Korenblit * transmitted at the highest mandatory rate of the PHY that is 111d1e879ecSMiri Korenblit * less than or equal to the rate of the received frame, and 112d1e879ecSMiri Korenblit * that is of the same modulation class as the received frame. 113d1e879ecSMiri Korenblit * 114d1e879ecSMiri Korenblit * As a consequence, we need to add all mandatory rates that are 115d1e879ecSMiri Korenblit * lower than all of the basic rates to these bitmaps. 116d1e879ecSMiri Korenblit */ 117d1e879ecSMiri Korenblit 118d1e879ecSMiri Korenblit if (lowest_present_ofdm > IWL_RATE_24M_INDEX) 119d1e879ecSMiri Korenblit ofdm |= IWL_RATE_BIT_MSK(24) >> IWL_FIRST_OFDM_RATE; 120d1e879ecSMiri Korenblit if (lowest_present_ofdm > IWL_RATE_12M_INDEX) 121d1e879ecSMiri Korenblit ofdm |= IWL_RATE_BIT_MSK(12) >> IWL_FIRST_OFDM_RATE; 122d1e879ecSMiri Korenblit /* 6M already there or needed so always add */ 123d1e879ecSMiri Korenblit ofdm |= IWL_RATE_BIT_MSK(6) >> IWL_FIRST_OFDM_RATE; 124d1e879ecSMiri Korenblit 125d1e879ecSMiri Korenblit /* CCK is a bit more complex with DSSS vs. HR/DSSS vs. ERP. 126d1e879ecSMiri Korenblit * Note, however: 127d1e879ecSMiri Korenblit * - if no CCK rates are basic, it must be ERP since there must 128d1e879ecSMiri Korenblit * be some basic rates at all, so they're OFDM => ERP PHY 129d1e879ecSMiri Korenblit * (or we're in 5 GHz, and the cck bitmap will never be used) 130d1e879ecSMiri Korenblit * - if 11M is a basic rate, it must be ERP as well, so add 5.5M 131d1e879ecSMiri Korenblit * - if 5.5M is basic, 1M and 2M are mandatory 132d1e879ecSMiri Korenblit * - if 2M is basic, 1M is mandatory 133d1e879ecSMiri Korenblit * - if 1M is basic, that's the only valid ACK rate. 134d1e879ecSMiri Korenblit * As a consequence, it's not as complicated as it sounds, just add 135d1e879ecSMiri Korenblit * any lower rates to the ACK rate bitmap. 136d1e879ecSMiri Korenblit */ 137d1e879ecSMiri Korenblit if (lowest_present_cck > IWL_RATE_11M_INDEX) 138d1e879ecSMiri Korenblit cck |= IWL_RATE_BIT_MSK(11) >> IWL_FIRST_CCK_RATE; 139d1e879ecSMiri Korenblit if (lowest_present_cck > IWL_RATE_5M_INDEX) 140d1e879ecSMiri Korenblit cck |= IWL_RATE_BIT_MSK(5) >> IWL_FIRST_CCK_RATE; 141d1e879ecSMiri Korenblit if (lowest_present_cck > IWL_RATE_2M_INDEX) 142d1e879ecSMiri Korenblit cck |= IWL_RATE_BIT_MSK(2) >> IWL_FIRST_CCK_RATE; 143d1e879ecSMiri Korenblit /* 1M already there or needed so always add */ 144d1e879ecSMiri Korenblit cck |= IWL_RATE_BIT_MSK(1) >> IWL_FIRST_CCK_RATE; 145d1e879ecSMiri Korenblit 146d1e879ecSMiri Korenblit *cck_rates = cpu_to_le32((u32)cck); 147d1e879ecSMiri Korenblit *ofdm_rates = cpu_to_le32((u32)ofdm); 148d1e879ecSMiri Korenblit } 149d1e879ecSMiri Korenblit 150d1e879ecSMiri Korenblit static void iwl_mld_fill_protection_flags(struct iwl_mld *mld, 151d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link, 152d1e879ecSMiri Korenblit __le32 *protection_flags) 153d1e879ecSMiri Korenblit { 154d1e879ecSMiri Korenblit u8 protection_mode = link->ht_operation_mode & 155d1e879ecSMiri Korenblit IEEE80211_HT_OP_MODE_PROTECTION; 156d1e879ecSMiri Korenblit u8 ht_flag = LINK_PROT_FLG_HT_PROT | LINK_PROT_FLG_FAT_PROT; 157d1e879ecSMiri Korenblit 158d1e879ecSMiri Korenblit IWL_DEBUG_RATE(mld, "HT protection mode: %d\n", protection_mode); 159d1e879ecSMiri Korenblit 160d1e879ecSMiri Korenblit if (link->use_cts_prot) 161d1e879ecSMiri Korenblit *protection_flags |= cpu_to_le32(LINK_PROT_FLG_TGG_PROTECT); 162d1e879ecSMiri Korenblit 163d1e879ecSMiri Korenblit /* See section 9.23.3.1 of IEEE 80211-2012. 164d1e879ecSMiri Korenblit * Nongreenfield HT STAs Present is not supported. 165d1e879ecSMiri Korenblit */ 166d1e879ecSMiri Korenblit switch (protection_mode) { 167d1e879ecSMiri Korenblit case IEEE80211_HT_OP_MODE_PROTECTION_NONE: 168d1e879ecSMiri Korenblit break; 169d1e879ecSMiri Korenblit case IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER: 170d1e879ecSMiri Korenblit case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED: 171d1e879ecSMiri Korenblit *protection_flags |= cpu_to_le32(ht_flag); 172d1e879ecSMiri Korenblit break; 173d1e879ecSMiri Korenblit case IEEE80211_HT_OP_MODE_PROTECTION_20MHZ: 174d1e879ecSMiri Korenblit /* Protect when channel wider than 20MHz */ 175d1e879ecSMiri Korenblit if (link->chanreq.oper.width > NL80211_CHAN_WIDTH_20) 176d1e879ecSMiri Korenblit *protection_flags |= cpu_to_le32(ht_flag); 177d1e879ecSMiri Korenblit break; 178d1e879ecSMiri Korenblit } 179d1e879ecSMiri Korenblit } 180d1e879ecSMiri Korenblit 181d1e879ecSMiri Korenblit static u8 iwl_mld_mac80211_ac_to_fw_ac(enum ieee80211_ac_numbers ac) 182d1e879ecSMiri Korenblit { 183d1e879ecSMiri Korenblit static const u8 mac80211_ac_to_fw[] = { 184d1e879ecSMiri Korenblit AC_VO, 185d1e879ecSMiri Korenblit AC_VI, 186d1e879ecSMiri Korenblit AC_BE, 187d1e879ecSMiri Korenblit AC_BK 188d1e879ecSMiri Korenblit }; 189d1e879ecSMiri Korenblit 190d1e879ecSMiri Korenblit return mac80211_ac_to_fw[ac]; 191d1e879ecSMiri Korenblit } 192d1e879ecSMiri Korenblit 193d1e879ecSMiri Korenblit static void iwl_mld_fill_qos_params(struct ieee80211_bss_conf *link, 194d1e879ecSMiri Korenblit struct iwl_ac_qos *ac, __le32 *qos_flags) 195d1e879ecSMiri Korenblit { 196d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); 197d1e879ecSMiri Korenblit 198d1e879ecSMiri Korenblit /* no need to check mld_link since it is done in the caller */ 199d1e879ecSMiri Korenblit 200d1e879ecSMiri Korenblit for (int mac_ac = 0; mac_ac < IEEE80211_NUM_ACS; mac_ac++) { 201d1e879ecSMiri Korenblit u8 txf = iwl_mld_mac80211_ac_to_fw_tx_fifo(mac_ac); 202d1e879ecSMiri Korenblit u8 fw_ac = iwl_mld_mac80211_ac_to_fw_ac(mac_ac); 203d1e879ecSMiri Korenblit 204d1e879ecSMiri Korenblit ac[fw_ac].cw_min = 205d1e879ecSMiri Korenblit cpu_to_le16(mld_link->queue_params[mac_ac].cw_min); 206d1e879ecSMiri Korenblit ac[fw_ac].cw_max = 207d1e879ecSMiri Korenblit cpu_to_le16(mld_link->queue_params[mac_ac].cw_max); 208d1e879ecSMiri Korenblit ac[fw_ac].edca_txop = 209d1e879ecSMiri Korenblit cpu_to_le16(mld_link->queue_params[mac_ac].txop * 32); 210d1e879ecSMiri Korenblit ac[fw_ac].aifsn = mld_link->queue_params[mac_ac].aifs; 211d1e879ecSMiri Korenblit ac[fw_ac].fifos_mask = BIT(txf); 212d1e879ecSMiri Korenblit } 213d1e879ecSMiri Korenblit 214d1e879ecSMiri Korenblit if (link->qos) 215d1e879ecSMiri Korenblit *qos_flags |= cpu_to_le32(MAC_QOS_FLG_UPDATE_EDCA); 216d1e879ecSMiri Korenblit 217d1e879ecSMiri Korenblit if (link->chanreq.oper.width != NL80211_CHAN_WIDTH_20_NOHT) 218d1e879ecSMiri Korenblit *qos_flags |= cpu_to_le32(MAC_QOS_FLG_TGN); 219d1e879ecSMiri Korenblit } 220d1e879ecSMiri Korenblit 221d1e879ecSMiri Korenblit static bool iwl_mld_fill_mu_edca(struct iwl_mld *mld, 222d1e879ecSMiri Korenblit const struct iwl_mld_link *mld_link, 223d1e879ecSMiri Korenblit struct iwl_he_backoff_conf *trig_based_txf) 224d1e879ecSMiri Korenblit { 225d1e879ecSMiri Korenblit for (int mac_ac = 0; mac_ac < IEEE80211_NUM_ACS; mac_ac++) { 226d1e879ecSMiri Korenblit const struct ieee80211_he_mu_edca_param_ac_rec *mu_edca = 227d1e879ecSMiri Korenblit &mld_link->queue_params[mac_ac].mu_edca_param_rec; 228d1e879ecSMiri Korenblit u8 fw_ac = iwl_mld_mac80211_ac_to_fw_ac(mac_ac); 229d1e879ecSMiri Korenblit 230d1e879ecSMiri Korenblit if (!mld_link->queue_params[mac_ac].mu_edca) 231d1e879ecSMiri Korenblit return false; 232d1e879ecSMiri Korenblit 233d1e879ecSMiri Korenblit trig_based_txf[fw_ac].cwmin = 234d1e879ecSMiri Korenblit cpu_to_le16(mu_edca->ecw_min_max & 0xf); 235d1e879ecSMiri Korenblit trig_based_txf[fw_ac].cwmax = 236d1e879ecSMiri Korenblit cpu_to_le16((mu_edca->ecw_min_max & 0xf0) >> 4); 237d1e879ecSMiri Korenblit trig_based_txf[fw_ac].aifsn = 238d1e879ecSMiri Korenblit cpu_to_le16(mu_edca->aifsn & 0xf); 239d1e879ecSMiri Korenblit trig_based_txf[fw_ac].mu_time = 240d1e879ecSMiri Korenblit cpu_to_le16(mu_edca->mu_edca_timer); 241d1e879ecSMiri Korenblit } 242d1e879ecSMiri Korenblit return true; 243d1e879ecSMiri Korenblit } 244d1e879ecSMiri Korenblit 245d1e879ecSMiri Korenblit static u8 iwl_mld_sta_rx_bw_to_fw(enum ieee80211_sta_rx_bandwidth bw) 246d1e879ecSMiri Korenblit { 247d1e879ecSMiri Korenblit switch (bw) { 248d1e879ecSMiri Korenblit default: /* potential future values not supported by this hw/driver */ 249d1e879ecSMiri Korenblit case IEEE80211_STA_RX_BW_20: 250d1e879ecSMiri Korenblit return IWL_LINK_MODIFY_BW_20; 251d1e879ecSMiri Korenblit case IEEE80211_STA_RX_BW_40: 252d1e879ecSMiri Korenblit return IWL_LINK_MODIFY_BW_40; 253d1e879ecSMiri Korenblit case IEEE80211_STA_RX_BW_80: 254d1e879ecSMiri Korenblit return IWL_LINK_MODIFY_BW_80; 255d1e879ecSMiri Korenblit case IEEE80211_STA_RX_BW_160: 256d1e879ecSMiri Korenblit return IWL_LINK_MODIFY_BW_160; 257d1e879ecSMiri Korenblit case IEEE80211_STA_RX_BW_320: 258d1e879ecSMiri Korenblit return IWL_LINK_MODIFY_BW_320; 259d1e879ecSMiri Korenblit } 260d1e879ecSMiri Korenblit } 261d1e879ecSMiri Korenblit 262d1e879ecSMiri Korenblit static int _iwl_mld_change_link_in_fw(struct iwl_mld *mld, 263d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link, 264d1e879ecSMiri Korenblit enum ieee80211_sta_rx_bandwidth bw, 265d1e879ecSMiri Korenblit u32 changes) 266d1e879ecSMiri Korenblit { 267d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); 268d1e879ecSMiri Korenblit struct ieee80211_vif *vif = link->vif; 269d1e879ecSMiri Korenblit struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); 270d1e879ecSMiri Korenblit struct ieee80211_chanctx_conf *chan_ctx; 271d1e879ecSMiri Korenblit struct iwl_link_config_cmd cmd = {}; 272d1e879ecSMiri Korenblit u32 flags = 0; 273d1e879ecSMiri Korenblit 274d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 275d1e879ecSMiri Korenblit 276d1e879ecSMiri Korenblit if (WARN_ON(!mld_link)) 277d1e879ecSMiri Korenblit return -EINVAL; 278d1e879ecSMiri Korenblit 279d1e879ecSMiri Korenblit cmd.link_id = cpu_to_le32(mld_link->fw_id); 280d1e879ecSMiri Korenblit cmd.spec_link_id = link->link_id; 281d1e879ecSMiri Korenblit cmd.mac_id = cpu_to_le32(mld_vif->fw_id); 282d1e879ecSMiri Korenblit 283d1e879ecSMiri Korenblit chan_ctx = wiphy_dereference(mld->wiphy, mld_link->chan_ctx); 284d1e879ecSMiri Korenblit 285d1e879ecSMiri Korenblit cmd.phy_id = cpu_to_le32(chan_ctx ? 286d1e879ecSMiri Korenblit iwl_mld_phy_from_mac80211(chan_ctx)->fw_id : 287d1e879ecSMiri Korenblit FW_CTXT_ID_INVALID); 288d1e879ecSMiri Korenblit 289d1e879ecSMiri Korenblit ether_addr_copy(cmd.local_link_addr, link->addr); 290d1e879ecSMiri Korenblit 291d1e879ecSMiri Korenblit cmd.active = cpu_to_le32(mld_link->active); 292d1e879ecSMiri Korenblit 293d1e879ecSMiri Korenblit if ((changes & LINK_CONTEXT_MODIFY_ACTIVE) && !mld_link->active && 294d1e879ecSMiri Korenblit mld_link->silent_deactivation) { 295d1e879ecSMiri Korenblit /* We are de-activating a link that is having CSA with 296d1e879ecSMiri Korenblit * immediate quiet in EMLSR. Tell the firmware not to send any 297d1e879ecSMiri Korenblit * frame. 298d1e879ecSMiri Korenblit */ 299d1e879ecSMiri Korenblit cmd.block_tx = 1; 300d1e879ecSMiri Korenblit mld_link->silent_deactivation = false; 301d1e879ecSMiri Korenblit } 302d1e879ecSMiri Korenblit 303d1e879ecSMiri Korenblit if (vif->type == NL80211_IFTYPE_ADHOC && link->bssid) 304d1e879ecSMiri Korenblit ether_addr_copy(cmd.ibss_bssid_addr, link->bssid); 305d1e879ecSMiri Korenblit 306d1e879ecSMiri Korenblit /* Channel context is needed to get the rates */ 307d1e879ecSMiri Korenblit if (chan_ctx) 308d1e879ecSMiri Korenblit iwl_mld_fill_rates(mld, link, chan_ctx, &cmd.cck_rates, 309d1e879ecSMiri Korenblit &cmd.ofdm_rates); 310d1e879ecSMiri Korenblit 311d1e879ecSMiri Korenblit cmd.cck_short_preamble = cpu_to_le32(link->use_short_preamble); 312d1e879ecSMiri Korenblit cmd.short_slot = cpu_to_le32(link->use_short_slot); 313d1e879ecSMiri Korenblit 314d1e879ecSMiri Korenblit iwl_mld_fill_protection_flags(mld, link, &cmd.protection_flags); 315d1e879ecSMiri Korenblit 316d1e879ecSMiri Korenblit iwl_mld_fill_qos_params(link, cmd.ac, &cmd.qos_flags); 317d1e879ecSMiri Korenblit 318d1e879ecSMiri Korenblit cmd.bi = cpu_to_le32(link->beacon_int); 319d1e879ecSMiri Korenblit cmd.dtim_interval = cpu_to_le32(link->beacon_int * link->dtim_period); 320d1e879ecSMiri Korenblit 321d1e879ecSMiri Korenblit if (changes & LINK_CONTEXT_MODIFY_BANDWIDTH) 322d1e879ecSMiri Korenblit cmd.modify_bandwidth = iwl_mld_sta_rx_bw_to_fw(bw); 323d1e879ecSMiri Korenblit 324d1e879ecSMiri Korenblit /* Configure HE parameters only if HE is supported, and only after 325d1e879ecSMiri Korenblit * the parameters are set in mac80211 (meaning after assoc) 326d1e879ecSMiri Korenblit */ 327d1e879ecSMiri Korenblit if (!link->he_support || iwlwifi_mod_params.disable_11ax || 328d1e879ecSMiri Korenblit (vif->type == NL80211_IFTYPE_STATION && !vif->cfg.assoc)) { 329d1e879ecSMiri Korenblit changes &= ~LINK_CONTEXT_MODIFY_HE_PARAMS; 330d1e879ecSMiri Korenblit goto send_cmd; 331d1e879ecSMiri Korenblit } 332d1e879ecSMiri Korenblit 333d1e879ecSMiri Korenblit /* ap_sta may be NULL if we're disconnecting */ 334d1e879ecSMiri Korenblit if (mld_vif->ap_sta) { 335d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta = 336d1e879ecSMiri Korenblit link_sta_dereference_check(mld_vif->ap_sta, 337d1e879ecSMiri Korenblit link->link_id); 338d1e879ecSMiri Korenblit 339d1e879ecSMiri Korenblit if (!WARN_ON(!link_sta) && link_sta->he_cap.has_he && 340d1e879ecSMiri Korenblit link_sta->he_cap.he_cap_elem.mac_cap_info[5] & 341d1e879ecSMiri Korenblit IEEE80211_HE_MAC_CAP5_OM_CTRL_UL_MU_DATA_DIS_RX) 342d1e879ecSMiri Korenblit cmd.ul_mu_data_disable = 1; 343d1e879ecSMiri Korenblit } 344d1e879ecSMiri Korenblit 345d1e879ecSMiri Korenblit cmd.htc_trig_based_pkt_ext = link->htc_trig_based_pkt_ext; 346d1e879ecSMiri Korenblit 347d1e879ecSMiri Korenblit if (link->uora_exists) { 348d1e879ecSMiri Korenblit cmd.rand_alloc_ecwmin = link->uora_ocw_range & 0x7; 349d1e879ecSMiri Korenblit cmd.rand_alloc_ecwmax = (link->uora_ocw_range >> 3) & 0x7; 350d1e879ecSMiri Korenblit } 351d1e879ecSMiri Korenblit 352d1e879ecSMiri Korenblit if (iwl_mld_fill_mu_edca(mld, mld_link, cmd.trig_based_txf)) 353d1e879ecSMiri Korenblit flags |= LINK_FLG_MU_EDCA_CW; 354d1e879ecSMiri Korenblit 355d1e879ecSMiri Korenblit cmd.bss_color = link->he_bss_color.color; 356d1e879ecSMiri Korenblit 357d1e879ecSMiri Korenblit if (!link->he_bss_color.enabled) 358d1e879ecSMiri Korenblit flags |= LINK_FLG_BSS_COLOR_DIS; 359d1e879ecSMiri Korenblit 360d1e879ecSMiri Korenblit cmd.frame_time_rts_th = cpu_to_le16(link->frame_time_rts_th); 361d1e879ecSMiri Korenblit 362d1e879ecSMiri Korenblit /* Block 26-tone RU OFDMA transmissions */ 363d1e879ecSMiri Korenblit if (mld_link->he_ru_2mhz_block) 364d1e879ecSMiri Korenblit flags |= LINK_FLG_RU_2MHZ_BLOCK; 365d1e879ecSMiri Korenblit 366d1e879ecSMiri Korenblit if (link->nontransmitted) { 367d1e879ecSMiri Korenblit ether_addr_copy(cmd.ref_bssid_addr, link->transmitter_bssid); 368d1e879ecSMiri Korenblit cmd.bssid_index = link->bssid_index; 369d1e879ecSMiri Korenblit } 370d1e879ecSMiri Korenblit 371d1e879ecSMiri Korenblit /* The only EHT parameter is puncturing, and starting from PHY cmd 372d1e879ecSMiri Korenblit * version 6 - it is sent there. For older versions of the PHY cmd, 373d1e879ecSMiri Korenblit * puncturing is not needed at all. 374d1e879ecSMiri Korenblit */ 375d1e879ecSMiri Korenblit if (WARN_ON(changes & LINK_CONTEXT_MODIFY_EHT_PARAMS)) 376d1e879ecSMiri Korenblit changes &= ~LINK_CONTEXT_MODIFY_EHT_PARAMS; 377d1e879ecSMiri Korenblit 378d1e879ecSMiri Korenblit send_cmd: 379d1e879ecSMiri Korenblit cmd.modify_mask = cpu_to_le32(changes); 380d1e879ecSMiri Korenblit cmd.flags = cpu_to_le32(flags); 381d1e879ecSMiri Korenblit 382d1e879ecSMiri Korenblit return iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_MODIFY); 383d1e879ecSMiri Korenblit } 384d1e879ecSMiri Korenblit 385d1e879ecSMiri Korenblit int iwl_mld_change_link_in_fw(struct iwl_mld *mld, 386d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link, 387d1e879ecSMiri Korenblit u32 changes) 388d1e879ecSMiri Korenblit { 389d1e879ecSMiri Korenblit if (WARN_ON(changes & LINK_CONTEXT_MODIFY_BANDWIDTH)) 390d1e879ecSMiri Korenblit changes &= ~LINK_CONTEXT_MODIFY_BANDWIDTH; 391d1e879ecSMiri Korenblit 392d1e879ecSMiri Korenblit return _iwl_mld_change_link_in_fw(mld, link, 0, changes); 393d1e879ecSMiri Korenblit } 394d1e879ecSMiri Korenblit 395d1e879ecSMiri Korenblit int iwl_mld_change_link_omi_bw(struct iwl_mld *mld, 396d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link, 397d1e879ecSMiri Korenblit enum ieee80211_sta_rx_bandwidth bw) 398d1e879ecSMiri Korenblit { 399d1e879ecSMiri Korenblit return _iwl_mld_change_link_in_fw(mld, link, bw, 400d1e879ecSMiri Korenblit LINK_CONTEXT_MODIFY_BANDWIDTH); 401d1e879ecSMiri Korenblit } 402d1e879ecSMiri Korenblit 403d1e879ecSMiri Korenblit int iwl_mld_activate_link(struct iwl_mld *mld, 404d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link) 405d1e879ecSMiri Korenblit { 406d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); 407d1e879ecSMiri Korenblit int ret; 408d1e879ecSMiri Korenblit 409d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 410d1e879ecSMiri Korenblit 411d1e879ecSMiri Korenblit if (WARN_ON(!mld_link || mld_link->active)) 412d1e879ecSMiri Korenblit return -EINVAL; 413d1e879ecSMiri Korenblit 414d1e879ecSMiri Korenblit mld_link->rx_omi.exit_ts = jiffies; 415d1e879ecSMiri Korenblit mld_link->active = true; 416d1e879ecSMiri Korenblit 417d1e879ecSMiri Korenblit ret = iwl_mld_change_link_in_fw(mld, link, 418d1e879ecSMiri Korenblit LINK_CONTEXT_MODIFY_ACTIVE); 419d1e879ecSMiri Korenblit if (ret) 420d1e879ecSMiri Korenblit mld_link->active = false; 421d1e879ecSMiri Korenblit 422d1e879ecSMiri Korenblit return ret; 423d1e879ecSMiri Korenblit } 424d1e879ecSMiri Korenblit 425d1e879ecSMiri Korenblit void iwl_mld_deactivate_link(struct iwl_mld *mld, 426d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link) 427d1e879ecSMiri Korenblit { 428d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); 429d1e879ecSMiri Korenblit struct iwl_probe_resp_data *probe_data; 430d1e879ecSMiri Korenblit 431d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 432d1e879ecSMiri Korenblit 433d1e879ecSMiri Korenblit if (WARN_ON(!mld_link || !mld_link->active)) 434d1e879ecSMiri Korenblit return; 435d1e879ecSMiri Korenblit 436d1e879ecSMiri Korenblit iwl_mld_cancel_session_protection(mld, link->vif, link->link_id); 437d1e879ecSMiri Korenblit 438d1e879ecSMiri Korenblit /* If we deactivate the link, we will probably remove it, or switch 439d1e879ecSMiri Korenblit * channel. In both cases, the CSA or Notice of Absence information is 440d1e879ecSMiri Korenblit * now irrelevant. Remove the data here. 441d1e879ecSMiri Korenblit */ 442d1e879ecSMiri Korenblit probe_data = wiphy_dereference(mld->wiphy, mld_link->probe_resp_data); 443d1e879ecSMiri Korenblit RCU_INIT_POINTER(mld_link->probe_resp_data, NULL); 444d1e879ecSMiri Korenblit if (probe_data) 445d1e879ecSMiri Korenblit kfree_rcu(probe_data, rcu_head); 446d1e879ecSMiri Korenblit 447d1e879ecSMiri Korenblit mld_link->active = false; 448d1e879ecSMiri Korenblit 449d1e879ecSMiri Korenblit iwl_mld_change_link_in_fw(mld, link, LINK_CONTEXT_MODIFY_ACTIVE); 450d1e879ecSMiri Korenblit 451d1e879ecSMiri Korenblit /* Now that the link is not active in FW, we don't expect any new 452d1e879ecSMiri Korenblit * notifications for it. Cancel the ones that are already pending 453d1e879ecSMiri Korenblit */ 454d1e879ecSMiri Korenblit iwl_mld_cancel_notifications_of_object(mld, IWL_MLD_OBJECT_TYPE_LINK, 455d1e879ecSMiri Korenblit mld_link->fw_id); 456d1e879ecSMiri Korenblit } 457d1e879ecSMiri Korenblit 458*5789f791SMiri Korenblit static void 459d1e879ecSMiri Korenblit iwl_mld_rm_link_from_fw(struct iwl_mld *mld, struct ieee80211_bss_conf *link) 460d1e879ecSMiri Korenblit { 461d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); 462d1e879ecSMiri Korenblit struct iwl_link_config_cmd cmd = {}; 463d1e879ecSMiri Korenblit 464d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 465d1e879ecSMiri Korenblit 466d1e879ecSMiri Korenblit if (WARN_ON(!mld_link)) 467*5789f791SMiri Korenblit return; 468d1e879ecSMiri Korenblit 469d1e879ecSMiri Korenblit cmd.link_id = cpu_to_le32(mld_link->fw_id); 470d1e879ecSMiri Korenblit cmd.spec_link_id = link->link_id; 471d1e879ecSMiri Korenblit cmd.phy_id = cpu_to_le32(FW_CTXT_ID_INVALID); 472d1e879ecSMiri Korenblit 473*5789f791SMiri Korenblit iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_REMOVE); 474d1e879ecSMiri Korenblit } 475d1e879ecSMiri Korenblit 476d1e879ecSMiri Korenblit static void iwl_mld_omi_bw_update(struct iwl_mld *mld, 477d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf, 478d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link, 479d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta, 480d1e879ecSMiri Korenblit enum ieee80211_sta_rx_bandwidth bw, 481d1e879ecSMiri Korenblit bool ap_update) 482d1e879ecSMiri Korenblit { 483d1e879ecSMiri Korenblit enum ieee80211_sta_rx_bandwidth apply_bw; 484d1e879ecSMiri Korenblit 485d1e879ecSMiri Korenblit mld_link->rx_omi.desired_bw = bw; 486d1e879ecSMiri Korenblit 487d1e879ecSMiri Korenblit /* Can't update OMI while already in progress, desired_bw was 488d1e879ecSMiri Korenblit * set so on FW notification the worker will see the change 489d1e879ecSMiri Korenblit * and apply new the new desired bw. 490d1e879ecSMiri Korenblit */ 491d1e879ecSMiri Korenblit if (mld_link->rx_omi.bw_in_progress) 492d1e879ecSMiri Korenblit return; 493d1e879ecSMiri Korenblit 494d1e879ecSMiri Korenblit if (bw == IEEE80211_STA_RX_BW_MAX) 495d1e879ecSMiri Korenblit apply_bw = ieee80211_chan_width_to_rx_bw(link_conf->chanreq.oper.width); 496d1e879ecSMiri Korenblit else 497d1e879ecSMiri Korenblit apply_bw = bw; 498d1e879ecSMiri Korenblit 499d1e879ecSMiri Korenblit if (!ap_update) { 500d1e879ecSMiri Korenblit /* The update isn't due to AP tracking after leaving OMI, 501d1e879ecSMiri Korenblit * where the AP could increase BW and then we must tell 502d1e879ecSMiri Korenblit * it that we can do the increased BW as well, if we did 503d1e879ecSMiri Korenblit * update the chandef. 504d1e879ecSMiri Korenblit * In this case, if we want MAX, then we will need to send 505d1e879ecSMiri Korenblit * a new OMI to the AP if it increases its own bandwidth as 506d1e879ecSMiri Korenblit * we can (due to internal and FW limitations, and being 507d1e879ecSMiri Korenblit * worried the AP might break) only send to what we're doing 508d1e879ecSMiri Korenblit * at the moment. In this case, set last_max_bw; otherwise 509d1e879ecSMiri Korenblit * if we really want to decrease our bandwidth set it to 0 510d1e879ecSMiri Korenblit * to indicate no updates are needed if the AP changes. 511d1e879ecSMiri Korenblit */ 512d1e879ecSMiri Korenblit if (bw != IEEE80211_STA_RX_BW_MAX) 513d1e879ecSMiri Korenblit mld_link->rx_omi.last_max_bw = apply_bw; 514d1e879ecSMiri Korenblit else 515d1e879ecSMiri Korenblit mld_link->rx_omi.last_max_bw = 0; 516d1e879ecSMiri Korenblit } else { 517d1e879ecSMiri Korenblit /* Otherwise, if we're already trying to do maximum and 518d1e879ecSMiri Korenblit * the AP is changing, set last_max_bw to the new max the 519d1e879ecSMiri Korenblit * AP is using, we'll only get to this code path if the 520d1e879ecSMiri Korenblit * new bandwidth of the AP is bigger than what we sent it 521d1e879ecSMiri Korenblit * previously. This avoids repeatedly sending updates if 522d1e879ecSMiri Korenblit * it changes bandwidth, only doing it once on an increase. 523d1e879ecSMiri Korenblit */ 524d1e879ecSMiri Korenblit mld_link->rx_omi.last_max_bw = apply_bw; 525d1e879ecSMiri Korenblit } 526d1e879ecSMiri Korenblit 527d1e879ecSMiri Korenblit if (ieee80211_prepare_rx_omi_bw(link_sta, bw)) { 528d1e879ecSMiri Korenblit mld_link->rx_omi.bw_in_progress = apply_bw; 529d1e879ecSMiri Korenblit iwl_mld_change_link_omi_bw(mld, link_conf, apply_bw); 530d1e879ecSMiri Korenblit } 531d1e879ecSMiri Korenblit } 532d1e879ecSMiri Korenblit 533d1e879ecSMiri Korenblit static void iwl_mld_omi_bw_finished_work(struct wiphy *wiphy, 534d1e879ecSMiri Korenblit struct wiphy_work *work) 535d1e879ecSMiri Korenblit { 536d1e879ecSMiri Korenblit struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); 537d1e879ecSMiri Korenblit struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); 538d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link = 539d1e879ecSMiri Korenblit container_of(work, typeof(*mld_link), rx_omi.finished_work.work); 540d1e879ecSMiri Korenblit enum ieee80211_sta_rx_bandwidth desired_bw, switched_to_bw; 541d1e879ecSMiri Korenblit struct ieee80211_vif *vif = mld_link->vif; 542d1e879ecSMiri Korenblit struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); 543d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf; 544d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta; 545d1e879ecSMiri Korenblit 546d1e879ecSMiri Korenblit if (!mld_vif->ap_sta) 547d1e879ecSMiri Korenblit return; 548d1e879ecSMiri Korenblit 549d1e879ecSMiri Korenblit link_sta = wiphy_dereference(mld->wiphy, 550d1e879ecSMiri Korenblit mld_vif->ap_sta->link[mld_link->link_id]); 551d1e879ecSMiri Korenblit if (WARN_ON_ONCE(!link_sta)) 552d1e879ecSMiri Korenblit return; 553d1e879ecSMiri Korenblit 554d1e879ecSMiri Korenblit link_conf = link_conf_dereference_protected(vif, link_sta->link_id); 555d1e879ecSMiri Korenblit if (WARN_ON_ONCE(!link_conf)) 556d1e879ecSMiri Korenblit return; 557d1e879ecSMiri Korenblit 558d1e879ecSMiri Korenblit if (WARN_ON(!mld_link->rx_omi.bw_in_progress)) 559d1e879ecSMiri Korenblit return; 560d1e879ecSMiri Korenblit 561d1e879ecSMiri Korenblit desired_bw = mld_link->rx_omi.desired_bw; 562d1e879ecSMiri Korenblit switched_to_bw = mld_link->rx_omi.bw_in_progress; 563d1e879ecSMiri Korenblit 564d1e879ecSMiri Korenblit ieee80211_finalize_rx_omi_bw(link_sta); 565d1e879ecSMiri Korenblit mld_link->rx_omi.bw_in_progress = 0; 566d1e879ecSMiri Korenblit 567d1e879ecSMiri Korenblit if (desired_bw != switched_to_bw) 568d1e879ecSMiri Korenblit iwl_mld_omi_bw_update(mld, link_conf, mld_link, link_sta, 569d1e879ecSMiri Korenblit desired_bw, false); 570d1e879ecSMiri Korenblit } 571d1e879ecSMiri Korenblit 572d1e879ecSMiri Korenblit static struct ieee80211_vif * 573d1e879ecSMiri Korenblit iwl_mld_get_omi_bw_reduction_pointers(struct iwl_mld *mld, 574d1e879ecSMiri Korenblit struct ieee80211_link_sta **link_sta, 575d1e879ecSMiri Korenblit struct iwl_mld_link **mld_link) 576d1e879ecSMiri Korenblit { 577d1e879ecSMiri Korenblit struct iwl_mld_vif *mld_vif; 578d1e879ecSMiri Korenblit struct ieee80211_vif *vif; 579d1e879ecSMiri Korenblit int n_link_stas = 0; 580d1e879ecSMiri Korenblit 581d1e879ecSMiri Korenblit *link_sta = NULL; 582d1e879ecSMiri Korenblit 583d1e879ecSMiri Korenblit if (mld->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_SC) 584d1e879ecSMiri Korenblit return NULL; 585d1e879ecSMiri Korenblit 586d1e879ecSMiri Korenblit vif = iwl_mld_get_bss_vif(mld); 587d1e879ecSMiri Korenblit if (!vif) 588d1e879ecSMiri Korenblit return NULL; 589d1e879ecSMiri Korenblit 590d1e879ecSMiri Korenblit for (int i = 0; i < ARRAY_SIZE(mld->fw_id_to_link_sta); i++) { 591d1e879ecSMiri Korenblit struct ieee80211_link_sta *tmp; 592d1e879ecSMiri Korenblit 593d1e879ecSMiri Korenblit tmp = wiphy_dereference(mld->wiphy, mld->fw_id_to_link_sta[i]); 594d1e879ecSMiri Korenblit if (IS_ERR_OR_NULL(tmp)) 595d1e879ecSMiri Korenblit continue; 596d1e879ecSMiri Korenblit 597d1e879ecSMiri Korenblit n_link_stas++; 598d1e879ecSMiri Korenblit *link_sta = tmp; 599d1e879ecSMiri Korenblit } 600d1e879ecSMiri Korenblit 601d1e879ecSMiri Korenblit /* can't do anything if we have TDLS peers or EMLSR */ 602d1e879ecSMiri Korenblit if (n_link_stas != 1) 603d1e879ecSMiri Korenblit return NULL; 604d1e879ecSMiri Korenblit 605d1e879ecSMiri Korenblit mld_vif = iwl_mld_vif_from_mac80211(vif); 606d1e879ecSMiri Korenblit *mld_link = iwl_mld_link_dereference_check(mld_vif, 607d1e879ecSMiri Korenblit (*link_sta)->link_id); 608d1e879ecSMiri Korenblit if (WARN_ON(!*mld_link)) 609d1e879ecSMiri Korenblit return NULL; 610d1e879ecSMiri Korenblit 611d1e879ecSMiri Korenblit return vif; 612d1e879ecSMiri Korenblit } 613d1e879ecSMiri Korenblit 614d1e879ecSMiri Korenblit void iwl_mld_omi_ap_changed_bw(struct iwl_mld *mld, 615d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf, 616d1e879ecSMiri Korenblit enum ieee80211_sta_rx_bandwidth bw) 617d1e879ecSMiri Korenblit { 618d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta; 619d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link; 620d1e879ecSMiri Korenblit struct ieee80211_vif *vif; 621d1e879ecSMiri Korenblit 622d1e879ecSMiri Korenblit vif = iwl_mld_get_omi_bw_reduction_pointers(mld, &link_sta, &mld_link); 623d1e879ecSMiri Korenblit if (!vif) 624d1e879ecSMiri Korenblit return; 625d1e879ecSMiri Korenblit 626d1e879ecSMiri Korenblit if (WARN_ON(link_conf->vif != vif)) 627d1e879ecSMiri Korenblit return; 628d1e879ecSMiri Korenblit 629d1e879ecSMiri Korenblit /* This is 0 if we requested an OMI BW reduction and don't want to 630d1e879ecSMiri Korenblit * be sending an OMI when the AP's bandwidth changes. 631d1e879ecSMiri Korenblit */ 632d1e879ecSMiri Korenblit if (!mld_link->rx_omi.last_max_bw) 633d1e879ecSMiri Korenblit return; 634d1e879ecSMiri Korenblit 635d1e879ecSMiri Korenblit /* We only need to tell the AP if it increases BW over what we last 636d1e879ecSMiri Korenblit * told it we were using, if it reduces then our last OMI to it will 637d1e879ecSMiri Korenblit * not get used anyway (e.g. we said we want 160 but it's doing 80.) 638d1e879ecSMiri Korenblit */ 639d1e879ecSMiri Korenblit if (bw < mld_link->rx_omi.last_max_bw) 640d1e879ecSMiri Korenblit return; 641d1e879ecSMiri Korenblit 642d1e879ecSMiri Korenblit iwl_mld_omi_bw_update(mld, link_conf, mld_link, link_sta, bw, true); 643d1e879ecSMiri Korenblit } 644d1e879ecSMiri Korenblit 645d1e879ecSMiri Korenblit void iwl_mld_handle_omi_status_notif(struct iwl_mld *mld, 646d1e879ecSMiri Korenblit struct iwl_rx_packet *pkt) 647d1e879ecSMiri Korenblit { 648d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta; 649d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link; 650d1e879ecSMiri Korenblit struct ieee80211_vif *vif; 651d1e879ecSMiri Korenblit 652d1e879ecSMiri Korenblit vif = iwl_mld_get_omi_bw_reduction_pointers(mld, &link_sta, &mld_link); 653d1e879ecSMiri Korenblit if (IWL_FW_CHECK(mld, !vif, "unexpected OMI notification\n")) 654d1e879ecSMiri Korenblit return; 655d1e879ecSMiri Korenblit 656d1e879ecSMiri Korenblit if (IWL_FW_CHECK(mld, !mld_link->rx_omi.bw_in_progress, 657d1e879ecSMiri Korenblit "OMI notification when not requested\n")) 658d1e879ecSMiri Korenblit return; 659d1e879ecSMiri Korenblit 660d1e879ecSMiri Korenblit wiphy_delayed_work_queue(mld->hw->wiphy, 661d1e879ecSMiri Korenblit &mld_link->rx_omi.finished_work, 662d1e879ecSMiri Korenblit msecs_to_jiffies(IWL_MLD_OMI_AP_SETTLE_DELAY)); 663d1e879ecSMiri Korenblit } 664d1e879ecSMiri Korenblit 665d1e879ecSMiri Korenblit void iwl_mld_leave_omi_bw_reduction(struct iwl_mld *mld) 666d1e879ecSMiri Korenblit { 667d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf; 668d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta; 669d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link; 670d1e879ecSMiri Korenblit struct ieee80211_vif *vif; 671d1e879ecSMiri Korenblit 672d1e879ecSMiri Korenblit vif = iwl_mld_get_omi_bw_reduction_pointers(mld, &link_sta, &mld_link); 673d1e879ecSMiri Korenblit if (!vif) 674d1e879ecSMiri Korenblit return; 675d1e879ecSMiri Korenblit 676d1e879ecSMiri Korenblit link_conf = link_conf_dereference_protected(vif, link_sta->link_id); 677d1e879ecSMiri Korenblit if (WARN_ON_ONCE(!link_conf)) 678d1e879ecSMiri Korenblit return; 679d1e879ecSMiri Korenblit 680d1e879ecSMiri Korenblit if (!link_conf->he_support) 681d1e879ecSMiri Korenblit return; 682d1e879ecSMiri Korenblit 683d1e879ecSMiri Korenblit mld_link->rx_omi.exit_ts = jiffies; 684d1e879ecSMiri Korenblit 685d1e879ecSMiri Korenblit iwl_mld_omi_bw_update(mld, link_conf, mld_link, link_sta, 686d1e879ecSMiri Korenblit IEEE80211_STA_RX_BW_MAX, false); 687d1e879ecSMiri Korenblit } 688d1e879ecSMiri Korenblit 689d1e879ecSMiri Korenblit void iwl_mld_check_omi_bw_reduction(struct iwl_mld *mld) 690d1e879ecSMiri Korenblit { 691d1e879ecSMiri Korenblit enum ieee80211_sta_rx_bandwidth bw = IEEE80211_STA_RX_BW_MAX; 692d1e879ecSMiri Korenblit struct ieee80211_chanctx_conf *chanctx; 693d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf; 694d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta; 695d1e879ecSMiri Korenblit struct cfg80211_chan_def chandef; 696d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link; 697d1e879ecSMiri Korenblit struct iwl_mld_vif *mld_vif; 698d1e879ecSMiri Korenblit struct ieee80211_vif *vif; 699d1e879ecSMiri Korenblit struct iwl_mld_phy *phy; 700d1e879ecSMiri Korenblit u16 punctured; 701d1e879ecSMiri Korenblit int exit_thr; 702d1e879ecSMiri Korenblit 703d1e879ecSMiri Korenblit /* not allowed in CAM mode */ 704d1e879ecSMiri Korenblit if (iwlmld_mod_params.power_scheme == IWL_POWER_SCHEME_CAM) 705d1e879ecSMiri Korenblit return; 706d1e879ecSMiri Korenblit 707d1e879ecSMiri Korenblit /* must have one BSS connection (no P2P), no TDLS, nor EMLSR */ 708d1e879ecSMiri Korenblit vif = iwl_mld_get_omi_bw_reduction_pointers(mld, &link_sta, &mld_link); 709d1e879ecSMiri Korenblit if (!vif) 710d1e879ecSMiri Korenblit return; 711d1e879ecSMiri Korenblit 712d1e879ecSMiri Korenblit link_conf = link_conf_dereference_protected(vif, link_sta->link_id); 713d1e879ecSMiri Korenblit if (WARN_ON_ONCE(!link_conf)) 714d1e879ecSMiri Korenblit return; 715d1e879ecSMiri Korenblit 716d1e879ecSMiri Korenblit if (!link_conf->he_support) 717d1e879ecSMiri Korenblit return; 718d1e879ecSMiri Korenblit 719d1e879ecSMiri Korenblit chanctx = wiphy_dereference(mld->wiphy, mld_link->chan_ctx); 720d1e879ecSMiri Korenblit if (WARN_ON(!chanctx)) 721d1e879ecSMiri Korenblit return; 722d1e879ecSMiri Korenblit 723d1e879ecSMiri Korenblit mld_vif = iwl_mld_vif_from_mac80211(vif); 724d1e879ecSMiri Korenblit if (!mld_vif->authorized) 725d1e879ecSMiri Korenblit goto apply; 726d1e879ecSMiri Korenblit 727d1e879ecSMiri Korenblit /* must not be in low-latency mode */ 728d1e879ecSMiri Korenblit if (iwl_mld_vif_low_latency(mld_vif)) 729d1e879ecSMiri Korenblit goto apply; 730d1e879ecSMiri Korenblit 731d1e879ecSMiri Korenblit chandef = link_conf->chanreq.oper; 732d1e879ecSMiri Korenblit 733d1e879ecSMiri Korenblit switch (chandef.width) { 734d1e879ecSMiri Korenblit case NL80211_CHAN_WIDTH_320: 735d1e879ecSMiri Korenblit exit_thr = IWL_MLD_OMI_EXIT_CHAN_LOAD_320; 736d1e879ecSMiri Korenblit break; 737d1e879ecSMiri Korenblit case NL80211_CHAN_WIDTH_160: 738d1e879ecSMiri Korenblit exit_thr = IWL_MLD_OMI_EXIT_CHAN_LOAD_160; 739d1e879ecSMiri Korenblit break; 740d1e879ecSMiri Korenblit default: 741d1e879ecSMiri Korenblit /* since we reduce to 80 MHz, must have more to start with */ 742d1e879ecSMiri Korenblit goto apply; 743d1e879ecSMiri Korenblit } 744d1e879ecSMiri Korenblit 745d1e879ecSMiri Korenblit /* not to be done if primary 80 MHz is punctured */ 746d1e879ecSMiri Korenblit if (cfg80211_chandef_primary(&chandef, NL80211_CHAN_WIDTH_80, 747d1e879ecSMiri Korenblit &punctured) < 0 || 748d1e879ecSMiri Korenblit punctured != 0) 749d1e879ecSMiri Korenblit goto apply; 750d1e879ecSMiri Korenblit 751d1e879ecSMiri Korenblit phy = iwl_mld_phy_from_mac80211(chanctx); 752d1e879ecSMiri Korenblit 753d1e879ecSMiri Korenblit if (phy->channel_load_by_us > exit_thr) { 754d1e879ecSMiri Korenblit /* send OMI for max bandwidth */ 755d1e879ecSMiri Korenblit goto apply; 756d1e879ecSMiri Korenblit } 757d1e879ecSMiri Korenblit 758d1e879ecSMiri Korenblit if (phy->channel_load_by_us > IWL_MLD_OMI_ENTER_CHAN_LOAD) { 759d1e879ecSMiri Korenblit /* no changes between enter/exit thresholds */ 760d1e879ecSMiri Korenblit return; 761d1e879ecSMiri Korenblit } 762d1e879ecSMiri Korenblit 763b807dec3SJohannes Berg if (time_is_after_jiffies(mld_link->rx_omi.exit_ts + 764d1e879ecSMiri Korenblit msecs_to_jiffies(IWL_MLD_OMI_EXIT_PROTECTION))) 765d1e879ecSMiri Korenblit return; 766d1e879ecSMiri Korenblit 767d1e879ecSMiri Korenblit /* reduce bandwidth to 80 MHz to save power */ 768d1e879ecSMiri Korenblit bw = IEEE80211_STA_RX_BW_80; 769d1e879ecSMiri Korenblit apply: 770d1e879ecSMiri Korenblit iwl_mld_omi_bw_update(mld, link_conf, mld_link, link_sta, bw, false); 771d1e879ecSMiri Korenblit } 772d1e879ecSMiri Korenblit 773d1e879ecSMiri Korenblit IWL_MLD_ALLOC_FN(link, bss_conf) 774d1e879ecSMiri Korenblit 775d1e879ecSMiri Korenblit /* Constructor function for struct iwl_mld_link */ 776d1e879ecSMiri Korenblit static int 777d1e879ecSMiri Korenblit iwl_mld_init_link(struct iwl_mld *mld, struct ieee80211_bss_conf *link, 778d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link) 779d1e879ecSMiri Korenblit { 780d1e879ecSMiri Korenblit mld_link->vif = link->vif; 781d1e879ecSMiri Korenblit mld_link->link_id = link->link_id; 782d1e879ecSMiri Korenblit 783d1e879ecSMiri Korenblit iwl_mld_init_internal_sta(&mld_link->bcast_sta); 784d1e879ecSMiri Korenblit iwl_mld_init_internal_sta(&mld_link->mcast_sta); 785d1e879ecSMiri Korenblit iwl_mld_init_internal_sta(&mld_link->aux_sta); 786d1e879ecSMiri Korenblit 787d1e879ecSMiri Korenblit wiphy_delayed_work_init(&mld_link->rx_omi.finished_work, 788d1e879ecSMiri Korenblit iwl_mld_omi_bw_finished_work); 789d1e879ecSMiri Korenblit 790d1e879ecSMiri Korenblit return iwl_mld_allocate_link_fw_id(mld, &mld_link->fw_id, link); 791d1e879ecSMiri Korenblit } 792d1e879ecSMiri Korenblit 793d1e879ecSMiri Korenblit /* Initializes the link structure, maps fw id to the ieee80211_bss_conf, and 794d1e879ecSMiri Korenblit * adds a link to the fw 795d1e879ecSMiri Korenblit */ 796d1e879ecSMiri Korenblit int iwl_mld_add_link(struct iwl_mld *mld, 797d1e879ecSMiri Korenblit struct ieee80211_bss_conf *bss_conf) 798d1e879ecSMiri Korenblit { 799d1e879ecSMiri Korenblit struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(bss_conf->vif); 800d1e879ecSMiri Korenblit struct iwl_mld_link *link = iwl_mld_link_from_mac80211(bss_conf); 801d1e879ecSMiri Korenblit bool is_deflink = bss_conf == &bss_conf->vif->bss_conf; 802d1e879ecSMiri Korenblit int ret; 803d1e879ecSMiri Korenblit 804d1e879ecSMiri Korenblit if (!link) { 805d1e879ecSMiri Korenblit if (is_deflink) 806d1e879ecSMiri Korenblit link = &mld_vif->deflink; 807d1e879ecSMiri Korenblit else 808d1e879ecSMiri Korenblit link = kzalloc(sizeof(*link), GFP_KERNEL); 809d1e879ecSMiri Korenblit } else { 810d1e879ecSMiri Korenblit WARN_ON(!mld->fw_status.in_hw_restart); 811d1e879ecSMiri Korenblit } 812d1e879ecSMiri Korenblit 813d1e879ecSMiri Korenblit ret = iwl_mld_init_link(mld, bss_conf, link); 814d1e879ecSMiri Korenblit if (ret) 815d1e879ecSMiri Korenblit goto free; 816d1e879ecSMiri Korenblit 817d1e879ecSMiri Korenblit rcu_assign_pointer(mld_vif->link[bss_conf->link_id], link); 818d1e879ecSMiri Korenblit 819d1e879ecSMiri Korenblit ret = iwl_mld_add_link_to_fw(mld, bss_conf); 820d1e879ecSMiri Korenblit if (ret) { 821d1e879ecSMiri Korenblit RCU_INIT_POINTER(mld->fw_id_to_bss_conf[link->fw_id], NULL); 822d1e879ecSMiri Korenblit RCU_INIT_POINTER(mld_vif->link[bss_conf->link_id], NULL); 823d1e879ecSMiri Korenblit goto free; 824d1e879ecSMiri Korenblit } 825d1e879ecSMiri Korenblit 826d1e879ecSMiri Korenblit return ret; 827d1e879ecSMiri Korenblit 828d1e879ecSMiri Korenblit free: 829d1e879ecSMiri Korenblit if (!is_deflink) 830d1e879ecSMiri Korenblit kfree(link); 831d1e879ecSMiri Korenblit return ret; 832d1e879ecSMiri Korenblit } 833d1e879ecSMiri Korenblit 834d1e879ecSMiri Korenblit /* Remove link from fw, unmap the bss_conf, and destroy the link structure */ 835*5789f791SMiri Korenblit void iwl_mld_remove_link(struct iwl_mld *mld, 836d1e879ecSMiri Korenblit struct ieee80211_bss_conf *bss_conf) 837d1e879ecSMiri Korenblit { 838d1e879ecSMiri Korenblit struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(bss_conf->vif); 839d1e879ecSMiri Korenblit struct iwl_mld_link *link = iwl_mld_link_from_mac80211(bss_conf); 840d1e879ecSMiri Korenblit bool is_deflink = link == &mld_vif->deflink; 841d1e879ecSMiri Korenblit 842d1e879ecSMiri Korenblit if (WARN_ON(!link || link->active)) 843*5789f791SMiri Korenblit return; 844d1e879ecSMiri Korenblit 845*5789f791SMiri Korenblit iwl_mld_rm_link_from_fw(mld, bss_conf); 846d1e879ecSMiri Korenblit /* Continue cleanup on failure */ 847d1e879ecSMiri Korenblit 848d1e879ecSMiri Korenblit if (!is_deflink) 849d1e879ecSMiri Korenblit kfree_rcu(link, rcu_head); 850d1e879ecSMiri Korenblit 851d1e879ecSMiri Korenblit RCU_INIT_POINTER(mld_vif->link[bss_conf->link_id], NULL); 852d1e879ecSMiri Korenblit 853d1e879ecSMiri Korenblit wiphy_delayed_work_cancel(mld->wiphy, &link->rx_omi.finished_work); 854d1e879ecSMiri Korenblit 855d1e879ecSMiri Korenblit if (WARN_ON(link->fw_id >= mld->fw->ucode_capa.num_links)) 856*5789f791SMiri Korenblit return; 857d1e879ecSMiri Korenblit 858d1e879ecSMiri Korenblit RCU_INIT_POINTER(mld->fw_id_to_bss_conf[link->fw_id], NULL); 859d1e879ecSMiri Korenblit } 860d1e879ecSMiri Korenblit 861d1e879ecSMiri Korenblit void iwl_mld_handle_missed_beacon_notif(struct iwl_mld *mld, 862d1e879ecSMiri Korenblit struct iwl_rx_packet *pkt) 863d1e879ecSMiri Korenblit { 864d1e879ecSMiri Korenblit const struct iwl_missed_beacons_notif *notif = (const void *)pkt->data; 865d1e879ecSMiri Korenblit union iwl_dbg_tlv_tp_data tp_data = { .fw_pkt = pkt }; 866d1e879ecSMiri Korenblit u32 link_id = le32_to_cpu(notif->link_id); 867d1e879ecSMiri Korenblit u32 missed_bcon = le32_to_cpu(notif->consec_missed_beacons); 868d1e879ecSMiri Korenblit u32 missed_bcon_since_rx = 869d1e879ecSMiri Korenblit le32_to_cpu(notif->consec_missed_beacons_since_last_rx); 870d1e879ecSMiri Korenblit u32 scnd_lnk_bcn_lost = 871d1e879ecSMiri Korenblit le32_to_cpu(notif->consec_missed_beacons_other_link); 872d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf = 873d1e879ecSMiri Korenblit iwl_mld_fw_id_to_link_conf(mld, link_id); 874d1e879ecSMiri Korenblit u32 bss_param_ch_cnt_link_id; 875d1e879ecSMiri Korenblit struct ieee80211_vif *vif; 876d1e879ecSMiri Korenblit 877d1e879ecSMiri Korenblit if (WARN_ON(!link_conf)) 878d1e879ecSMiri Korenblit return; 879d1e879ecSMiri Korenblit 880d1e879ecSMiri Korenblit vif = link_conf->vif; 881d1e879ecSMiri Korenblit bss_param_ch_cnt_link_id = link_conf->bss_param_ch_cnt_link_id; 882d1e879ecSMiri Korenblit 883d1e879ecSMiri Korenblit IWL_DEBUG_INFO(mld, 884d1e879ecSMiri Korenblit "missed bcn link_id=%u, %u consecutive=%u\n", 885d1e879ecSMiri Korenblit link_id, missed_bcon, missed_bcon_since_rx); 886d1e879ecSMiri Korenblit 887d1e879ecSMiri Korenblit if (WARN_ON(!vif)) 888d1e879ecSMiri Korenblit return; 889d1e879ecSMiri Korenblit 890d1e879ecSMiri Korenblit mld->trans->dbg.dump_file_name_ext_valid = true; 891d1e879ecSMiri Korenblit snprintf(mld->trans->dbg.dump_file_name_ext, IWL_FW_INI_MAX_NAME, 892d1e879ecSMiri Korenblit "LinkId_%d_MacType_%d", link_id, 893d1e879ecSMiri Korenblit iwl_mld_mac80211_iftype_to_fw(vif)); 894d1e879ecSMiri Korenblit 895d1e879ecSMiri Korenblit iwl_dbg_tlv_time_point(&mld->fwrt, 896d1e879ecSMiri Korenblit IWL_FW_INI_TIME_POINT_MISSED_BEACONS, &tp_data); 897d1e879ecSMiri Korenblit 898d1e879ecSMiri Korenblit if (missed_bcon >= IWL_MLD_MISSED_BEACONS_THRESHOLD_LONG) { 899d1e879ecSMiri Korenblit if (missed_bcon_since_rx >= 900d1e879ecSMiri Korenblit IWL_MLD_MISSED_BEACONS_SINCE_RX_THOLD) { 901d1e879ecSMiri Korenblit ieee80211_connection_loss(vif); 902d1e879ecSMiri Korenblit return; 903d1e879ecSMiri Korenblit } 904d1e879ecSMiri Korenblit IWL_WARN(mld, 905d1e879ecSMiri Korenblit "missed beacons exceeds threshold, but receiving data. Stay connected, Expect bugs.\n"); 906d1e879ecSMiri Korenblit return; 907d1e879ecSMiri Korenblit } 908d1e879ecSMiri Korenblit 909d1e879ecSMiri Korenblit if (missed_bcon_since_rx > IWL_MLD_MISSED_BEACONS_THRESHOLD) { 910d1e879ecSMiri Korenblit ieee80211_cqm_beacon_loss_notify(vif, GFP_ATOMIC); 911d1e879ecSMiri Korenblit 912d1e879ecSMiri Korenblit /* try to switch links, no-op if we don't have MLO */ 913f31d666fSMiri Korenblit iwl_mld_int_mlo_scan(mld, vif); 914d1e879ecSMiri Korenblit } 915d1e879ecSMiri Korenblit 916d1e879ecSMiri Korenblit /* no more logic if we're not in EMLSR */ 917d1e879ecSMiri Korenblit if (hweight16(vif->active_links) <= 1) 918d1e879ecSMiri Korenblit return; 919d1e879ecSMiri Korenblit 920d1e879ecSMiri Korenblit /* We are processing a notification before link activation */ 921d1e879ecSMiri Korenblit if (le32_to_cpu(notif->other_link_id) == FW_CTXT_ID_INVALID) 922d1e879ecSMiri Korenblit return; 923d1e879ecSMiri Korenblit 924d1e879ecSMiri Korenblit /* Exit EMLSR if we lost more than 925d1e879ecSMiri Korenblit * IWL_MLD_MISSED_BEACONS_EXIT_ESR_THRESH beacons on boths links 926d1e879ecSMiri Korenblit * OR more than IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH on current link. 927d1e879ecSMiri Korenblit * OR more than IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED 928d1e879ecSMiri Korenblit * on current link and the link's bss_param_ch_count has changed on 929d1e879ecSMiri Korenblit * the other link's beacon. 930d1e879ecSMiri Korenblit */ 931d1e879ecSMiri Korenblit if ((missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS && 932d1e879ecSMiri Korenblit scnd_lnk_bcn_lost >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS) || 933d1e879ecSMiri Korenblit missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH || 934d1e879ecSMiri Korenblit (bss_param_ch_cnt_link_id != link_id && 935d1e879ecSMiri Korenblit missed_bcon >= 936d1e879ecSMiri Korenblit IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED)) { 937d1e879ecSMiri Korenblit iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_MISSED_BEACON, 938d1e879ecSMiri Korenblit iwl_mld_get_primary_link(vif)); 939d1e879ecSMiri Korenblit } 940d1e879ecSMiri Korenblit } 941d1e879ecSMiri Korenblit EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_handle_missed_beacon_notif); 942d1e879ecSMiri Korenblit 943d1e879ecSMiri Korenblit bool iwl_mld_cancel_missed_beacon_notif(struct iwl_mld *mld, 944d1e879ecSMiri Korenblit struct iwl_rx_packet *pkt, 945d1e879ecSMiri Korenblit u32 removed_link_id) 946d1e879ecSMiri Korenblit { 947d1e879ecSMiri Korenblit struct iwl_missed_beacons_notif *notif = (void *)pkt->data; 948d1e879ecSMiri Korenblit 949d1e879ecSMiri Korenblit if (le32_to_cpu(notif->other_link_id) == removed_link_id) { 950d1e879ecSMiri Korenblit /* Second link is being removed. Don't cancel the notification, 951d1e879ecSMiri Korenblit * but mark second link as invalid. 952d1e879ecSMiri Korenblit */ 953d1e879ecSMiri Korenblit notif->other_link_id = cpu_to_le32(FW_CTXT_ID_INVALID); 954d1e879ecSMiri Korenblit } 955d1e879ecSMiri Korenblit 956d1e879ecSMiri Korenblit /* If the primary link is removed, cancel the notification */ 957d1e879ecSMiri Korenblit return le32_to_cpu(notif->link_id) == removed_link_id; 958d1e879ecSMiri Korenblit } 959d1e879ecSMiri Korenblit 960d1e879ecSMiri Korenblit int iwl_mld_link_set_associated(struct iwl_mld *mld, struct ieee80211_vif *vif, 961d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link) 962d1e879ecSMiri Korenblit { 963d1e879ecSMiri Korenblit return iwl_mld_change_link_in_fw(mld, link, LINK_CONTEXT_MODIFY_ALL & 964d1e879ecSMiri Korenblit ~(LINK_CONTEXT_MODIFY_ACTIVE | 965d1e879ecSMiri Korenblit LINK_CONTEXT_MODIFY_EHT_PARAMS)); 966d1e879ecSMiri Korenblit } 967d1e879ecSMiri Korenblit 968d1e879ecSMiri Korenblit struct iwl_mld_rssi_to_grade { 969d1e879ecSMiri Korenblit s8 rssi[2]; 970d1e879ecSMiri Korenblit u16 grade; 971d1e879ecSMiri Korenblit }; 972d1e879ecSMiri Korenblit 973d1e879ecSMiri Korenblit #define RSSI_TO_GRADE_LINE(_lb, _hb_uhb, _grade) \ 974d1e879ecSMiri Korenblit { \ 975d1e879ecSMiri Korenblit .rssi = {_lb, _hb_uhb}, \ 976d1e879ecSMiri Korenblit .grade = _grade \ 977d1e879ecSMiri Korenblit } 978d1e879ecSMiri Korenblit 979d1e879ecSMiri Korenblit /* 980d1e879ecSMiri Korenblit * This array must be sorted by increasing RSSI for proper functionality. 981d1e879ecSMiri Korenblit * The grades are actually estimated throughput, represented as fixed-point 982d1e879ecSMiri Korenblit * with a scale factor of 1/10. 983d1e879ecSMiri Korenblit */ 984d1e879ecSMiri Korenblit static const struct iwl_mld_rssi_to_grade rssi_to_grade_map[] = { 985d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-85, -89, 172), 986d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-83, -86, 344), 987d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-82, -85, 516), 988d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-80, -83, 688), 989d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-77, -79, 1032), 990d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-73, -76, 1376), 991d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-70, -74, 1548), 992d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-69, -72, 1720), 993d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-65, -68, 2064), 994d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-61, -66, 2294), 995d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-58, -61, 2580), 996d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-55, -58, 2868), 997d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-46, -55, 3098), 998d1e879ecSMiri Korenblit RSSI_TO_GRADE_LINE(-43, -54, 3442) 999d1e879ecSMiri Korenblit }; 1000d1e879ecSMiri Korenblit 1001d1e879ecSMiri Korenblit #define MAX_GRADE (rssi_to_grade_map[ARRAY_SIZE(rssi_to_grade_map) - 1].grade) 1002d1e879ecSMiri Korenblit 1003d1e879ecSMiri Korenblit #define DEFAULT_CHAN_LOAD_2GHZ 30 1004d1e879ecSMiri Korenblit #define DEFAULT_CHAN_LOAD_5GHZ 15 1005d1e879ecSMiri Korenblit #define DEFAULT_CHAN_LOAD_6GHZ 0 1006d1e879ecSMiri Korenblit 1007d1e879ecSMiri Korenblit /* Factors calculation is done with fixed-point with a scaling factor of 1/256 */ 1008d1e879ecSMiri Korenblit #define SCALE_FACTOR 256 1009d1e879ecSMiri Korenblit #define MAX_CHAN_LOAD 256 1010d1e879ecSMiri Korenblit 1011d1e879ecSMiri Korenblit static unsigned int 1012d1e879ecSMiri Korenblit iwl_mld_get_n_subchannels(const struct ieee80211_bss_conf *link_conf) 1013d1e879ecSMiri Korenblit { 1014d1e879ecSMiri Korenblit enum nl80211_chan_width chan_width = 1015d1e879ecSMiri Korenblit link_conf->chanreq.oper.width; 1016d1e879ecSMiri Korenblit int mhz = nl80211_chan_width_to_mhz(chan_width); 1017d1e879ecSMiri Korenblit unsigned int n_subchannels; 1018d1e879ecSMiri Korenblit 1019d1e879ecSMiri Korenblit if (WARN_ONCE(mhz < 20 || mhz > 320, 1020d1e879ecSMiri Korenblit "Invalid channel width : (%d)\n", mhz)) 1021d1e879ecSMiri Korenblit return 1; 1022d1e879ecSMiri Korenblit 1023d1e879ecSMiri Korenblit /* total number of subchannels */ 1024d1e879ecSMiri Korenblit n_subchannels = mhz / 20; 1025d1e879ecSMiri Korenblit 1026d1e879ecSMiri Korenblit /* No puncturing if less than 80 MHz */ 1027d1e879ecSMiri Korenblit if (mhz >= 80) 1028d1e879ecSMiri Korenblit n_subchannels -= hweight16(link_conf->chanreq.oper.punctured); 1029d1e879ecSMiri Korenblit 1030d1e879ecSMiri Korenblit return n_subchannels; 1031d1e879ecSMiri Korenblit } 1032d1e879ecSMiri Korenblit 1033d1e879ecSMiri Korenblit static int 1034d1e879ecSMiri Korenblit iwl_mld_get_chan_load_from_element(struct iwl_mld *mld, 1035d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf) 1036d1e879ecSMiri Korenblit { 1037d1e879ecSMiri Korenblit struct ieee80211_vif *vif = link_conf->vif; 1038d1e879ecSMiri Korenblit const struct cfg80211_bss_ies *ies; 1039d1e879ecSMiri Korenblit const struct element *bss_load_elem = NULL; 1040d1e879ecSMiri Korenblit const struct ieee80211_bss_load_elem *bss_load; 1041d1e879ecSMiri Korenblit 1042d1e879ecSMiri Korenblit guard(rcu)(); 1043d1e879ecSMiri Korenblit 1044d1e879ecSMiri Korenblit if (ieee80211_vif_link_active(vif, link_conf->link_id)) 1045d1e879ecSMiri Korenblit ies = rcu_dereference(link_conf->bss->beacon_ies); 1046d1e879ecSMiri Korenblit else 1047d1e879ecSMiri Korenblit ies = rcu_dereference(link_conf->bss->ies); 1048d1e879ecSMiri Korenblit 1049d1e879ecSMiri Korenblit if (ies) 1050d1e879ecSMiri Korenblit bss_load_elem = cfg80211_find_elem(WLAN_EID_QBSS_LOAD, 1051d1e879ecSMiri Korenblit ies->data, ies->len); 1052d1e879ecSMiri Korenblit 1053d1e879ecSMiri Korenblit if (!bss_load_elem || 1054d1e879ecSMiri Korenblit bss_load_elem->datalen != sizeof(*bss_load)) 1055d1e879ecSMiri Korenblit return -EINVAL; 1056d1e879ecSMiri Korenblit 1057d1e879ecSMiri Korenblit bss_load = (const void *)bss_load_elem->data; 1058d1e879ecSMiri Korenblit 1059d1e879ecSMiri Korenblit return bss_load->channel_util; 1060d1e879ecSMiri Korenblit } 1061d1e879ecSMiri Korenblit 1062d1e879ecSMiri Korenblit static unsigned int 1063d1e879ecSMiri Korenblit iwl_mld_get_chan_load_by_us(struct iwl_mld *mld, 1064d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf, 1065d1e879ecSMiri Korenblit bool expect_active_link) 1066d1e879ecSMiri Korenblit { 1067d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link_conf); 1068d1e879ecSMiri Korenblit struct ieee80211_chanctx_conf *chan_ctx; 1069d1e879ecSMiri Korenblit struct iwl_mld_phy *phy; 1070d1e879ecSMiri Korenblit 1071d1e879ecSMiri Korenblit if (!mld_link || !mld_link->active) { 1072d1e879ecSMiri Korenblit WARN_ON(expect_active_link); 1073d1e879ecSMiri Korenblit return 0; 1074d1e879ecSMiri Korenblit } 1075d1e879ecSMiri Korenblit 1076d1e879ecSMiri Korenblit if (WARN_ONCE(!rcu_access_pointer(mld_link->chan_ctx), 1077d1e879ecSMiri Korenblit "Active link (%u) without channel ctxt assigned!\n", 1078d1e879ecSMiri Korenblit link_conf->link_id)) 1079d1e879ecSMiri Korenblit return 0; 1080d1e879ecSMiri Korenblit 1081d1e879ecSMiri Korenblit chan_ctx = wiphy_dereference(mld->wiphy, mld_link->chan_ctx); 1082d1e879ecSMiri Korenblit phy = iwl_mld_phy_from_mac80211(chan_ctx); 1083d1e879ecSMiri Korenblit 1084d1e879ecSMiri Korenblit return phy->channel_load_by_us; 1085d1e879ecSMiri Korenblit } 1086d1e879ecSMiri Korenblit 1087d1e879ecSMiri Korenblit /* Returns error if the channel utilization element is invalid/unavailable */ 1088d1e879ecSMiri Korenblit int iwl_mld_get_chan_load_by_others(struct iwl_mld *mld, 1089d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf, 1090d1e879ecSMiri Korenblit bool expect_active_link) 1091d1e879ecSMiri Korenblit { 1092d1e879ecSMiri Korenblit int chan_load; 1093d1e879ecSMiri Korenblit unsigned int chan_load_by_us; 1094d1e879ecSMiri Korenblit 1095d1e879ecSMiri Korenblit /* get overall load */ 1096d1e879ecSMiri Korenblit chan_load = iwl_mld_get_chan_load_from_element(mld, link_conf); 1097d1e879ecSMiri Korenblit if (chan_load < 0) 1098d1e879ecSMiri Korenblit return chan_load; 1099d1e879ecSMiri Korenblit 1100d1e879ecSMiri Korenblit chan_load_by_us = iwl_mld_get_chan_load_by_us(mld, link_conf, 1101d1e879ecSMiri Korenblit expect_active_link); 1102d1e879ecSMiri Korenblit 1103d1e879ecSMiri Korenblit /* channel load by us is given in percentage */ 1104d1e879ecSMiri Korenblit chan_load_by_us = 1105d1e879ecSMiri Korenblit NORMALIZE_PERCENT_TO_255(chan_load_by_us); 1106d1e879ecSMiri Korenblit 1107d1e879ecSMiri Korenblit /* Use only values that firmware sends that can possibly be valid */ 1108d1e879ecSMiri Korenblit if (chan_load_by_us <= chan_load) 1109d1e879ecSMiri Korenblit chan_load -= chan_load_by_us; 1110d1e879ecSMiri Korenblit 1111d1e879ecSMiri Korenblit return chan_load; 1112d1e879ecSMiri Korenblit } 1113d1e879ecSMiri Korenblit 1114d1e879ecSMiri Korenblit static unsigned int 1115d1e879ecSMiri Korenblit iwl_mld_get_default_chan_load(struct ieee80211_bss_conf *link_conf) 1116d1e879ecSMiri Korenblit { 1117d1e879ecSMiri Korenblit enum nl80211_band band = link_conf->chanreq.oper.chan->band; 1118d1e879ecSMiri Korenblit 1119d1e879ecSMiri Korenblit switch (band) { 1120d1e879ecSMiri Korenblit case NL80211_BAND_2GHZ: 1121d1e879ecSMiri Korenblit return DEFAULT_CHAN_LOAD_2GHZ; 1122d1e879ecSMiri Korenblit case NL80211_BAND_5GHZ: 1123d1e879ecSMiri Korenblit return DEFAULT_CHAN_LOAD_5GHZ; 1124d1e879ecSMiri Korenblit case NL80211_BAND_6GHZ: 1125d1e879ecSMiri Korenblit return DEFAULT_CHAN_LOAD_6GHZ; 1126d1e879ecSMiri Korenblit default: 1127d1e879ecSMiri Korenblit WARN_ON(1); 1128d1e879ecSMiri Korenblit return 0; 1129d1e879ecSMiri Korenblit } 1130d1e879ecSMiri Korenblit } 1131d1e879ecSMiri Korenblit 1132d1e879ecSMiri Korenblit unsigned int iwl_mld_get_chan_load(struct iwl_mld *mld, 1133d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf) 1134d1e879ecSMiri Korenblit { 1135d1e879ecSMiri Korenblit int chan_load; 1136d1e879ecSMiri Korenblit 1137d1e879ecSMiri Korenblit chan_load = iwl_mld_get_chan_load_by_others(mld, link_conf, false); 1138d1e879ecSMiri Korenblit if (chan_load >= 0) 1139d1e879ecSMiri Korenblit return chan_load; 1140d1e879ecSMiri Korenblit 1141d1e879ecSMiri Korenblit /* No information from the element, take the defaults */ 1142d1e879ecSMiri Korenblit chan_load = iwl_mld_get_default_chan_load(link_conf); 1143d1e879ecSMiri Korenblit 1144d1e879ecSMiri Korenblit /* The defaults are given in percentage */ 1145d1e879ecSMiri Korenblit return NORMALIZE_PERCENT_TO_255(chan_load); 1146d1e879ecSMiri Korenblit } 1147d1e879ecSMiri Korenblit 1148d1e879ecSMiri Korenblit static unsigned int 1149d1e879ecSMiri Korenblit iwl_mld_get_avail_chan_load(struct iwl_mld *mld, 1150d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf) 1151d1e879ecSMiri Korenblit { 1152d1e879ecSMiri Korenblit return MAX_CHAN_LOAD - iwl_mld_get_chan_load(mld, link_conf); 1153d1e879ecSMiri Korenblit } 1154d1e879ecSMiri Korenblit 1155d1e879ecSMiri Korenblit /* This function calculates the grade of a link. Returns 0 in error case */ 1156d1e879ecSMiri Korenblit unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, 1157d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link_conf) 1158d1e879ecSMiri Korenblit { 1159d1e879ecSMiri Korenblit enum nl80211_band band; 1160d1e879ecSMiri Korenblit int rssi_idx; 1161d1e879ecSMiri Korenblit s32 link_rssi; 1162d1e879ecSMiri Korenblit unsigned int grade = MAX_GRADE; 1163d1e879ecSMiri Korenblit 1164d1e879ecSMiri Korenblit if (WARN_ON_ONCE(!link_conf)) 1165d1e879ecSMiri Korenblit return 0; 1166d1e879ecSMiri Korenblit 1167d1e879ecSMiri Korenblit band = link_conf->chanreq.oper.chan->band; 1168d1e879ecSMiri Korenblit if (WARN_ONCE(band != NL80211_BAND_2GHZ && 1169d1e879ecSMiri Korenblit band != NL80211_BAND_5GHZ && 1170d1e879ecSMiri Korenblit band != NL80211_BAND_6GHZ, 1171d1e879ecSMiri Korenblit "Invalid band (%u)\n", band)) 1172d1e879ecSMiri Korenblit return 0; 1173d1e879ecSMiri Korenblit 1174d1e879ecSMiri Korenblit link_rssi = MBM_TO_DBM(link_conf->bss->signal); 1175d1e879ecSMiri Korenblit /* 1176d1e879ecSMiri Korenblit * For 6 GHz the RSSI of the beacons is lower than 1177d1e879ecSMiri Korenblit * the RSSI of the data. 1178d1e879ecSMiri Korenblit */ 1179d1e879ecSMiri Korenblit if (band == NL80211_BAND_6GHZ && link_rssi) 1180d1e879ecSMiri Korenblit link_rssi += 4; 1181d1e879ecSMiri Korenblit 1182d1e879ecSMiri Korenblit rssi_idx = band == NL80211_BAND_2GHZ ? 0 : 1; 1183d1e879ecSMiri Korenblit 1184d1e879ecSMiri Korenblit /* No valid RSSI - take the lowest grade */ 1185d1e879ecSMiri Korenblit if (!link_rssi) 1186d1e879ecSMiri Korenblit link_rssi = rssi_to_grade_map[0].rssi[rssi_idx]; 1187d1e879ecSMiri Korenblit 1188d1e879ecSMiri Korenblit IWL_DEBUG_EHT(mld, 1189d1e879ecSMiri Korenblit "Calculating grade of link %d: band = %d, bandwidth = %d, punctured subchannels =0x%x RSSI = %d\n", 1190d1e879ecSMiri Korenblit link_conf->link_id, band, 1191d1e879ecSMiri Korenblit link_conf->chanreq.oper.width, 1192d1e879ecSMiri Korenblit link_conf->chanreq.oper.punctured, link_rssi); 1193d1e879ecSMiri Korenblit 1194d1e879ecSMiri Korenblit /* Get grade based on RSSI */ 1195d1e879ecSMiri Korenblit for (int i = 0; i < ARRAY_SIZE(rssi_to_grade_map); i++) { 1196d1e879ecSMiri Korenblit const struct iwl_mld_rssi_to_grade *line = 1197d1e879ecSMiri Korenblit &rssi_to_grade_map[i]; 1198d1e879ecSMiri Korenblit 1199d1e879ecSMiri Korenblit if (link_rssi > line->rssi[rssi_idx]) 1200d1e879ecSMiri Korenblit continue; 1201d1e879ecSMiri Korenblit grade = line->grade; 1202d1e879ecSMiri Korenblit break; 1203d1e879ecSMiri Korenblit } 1204d1e879ecSMiri Korenblit 1205d1e879ecSMiri Korenblit /* Apply the channel load and puncturing factors */ 1206d1e879ecSMiri Korenblit grade = grade * iwl_mld_get_avail_chan_load(mld, link_conf) / SCALE_FACTOR; 1207d1e879ecSMiri Korenblit grade = grade * iwl_mld_get_n_subchannels(link_conf); 1208d1e879ecSMiri Korenblit 1209d1e879ecSMiri Korenblit IWL_DEBUG_EHT(mld, "Link %d's grade: %d\n", link_conf->link_id, grade); 1210d1e879ecSMiri Korenblit 1211d1e879ecSMiri Korenblit return grade; 1212d1e879ecSMiri Korenblit } 1213d1e879ecSMiri Korenblit EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_get_link_grade); 1214