Path: blob/main/sys/contrib/dev/iwlwifi/mld/link.c
48285 views
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause1/*2* Copyright (C) 2024-2025 Intel Corporation3*/45#include "constants.h"6#include "link.h"7#include "iface.h"8#include "mlo.h"9#include "hcmd.h"10#include "phy.h"11#include "fw/api/rs.h"12#include "fw/api/txq.h"13#include "fw/api/mac.h"1415#include "fw/api/context.h"16#include "fw/dbg.h"1718static int iwl_mld_send_link_cmd(struct iwl_mld *mld,19struct iwl_link_config_cmd *cmd,20enum iwl_ctxt_action action)21{22int ret;2324lockdep_assert_wiphy(mld->wiphy);2526cmd->action = cpu_to_le32(action);27ret = iwl_mld_send_cmd_pdu(mld,28WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD),29cmd);30if (ret)31IWL_ERR(mld, "Failed to send LINK_CONFIG_CMD (action:%d): %d\n",32action, ret);33return ret;34}3536static int iwl_mld_add_link_to_fw(struct iwl_mld *mld,37struct ieee80211_bss_conf *link_conf)38{39struct ieee80211_vif *vif = link_conf->vif;40struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);41struct iwl_mld_link *link = iwl_mld_link_from_mac80211(link_conf);42struct iwl_link_config_cmd cmd = {};4344lockdep_assert_wiphy(mld->wiphy);4546if (WARN_ON(!link))47return -EINVAL;4849cmd.link_id = cpu_to_le32(link->fw_id);50cmd.mac_id = cpu_to_le32(mld_vif->fw_id);51cmd.spec_link_id = link_conf->link_id;52cmd.phy_id = cpu_to_le32(FW_CTXT_ID_INVALID);5354ether_addr_copy(cmd.local_link_addr, link_conf->addr);5556if (vif->type == NL80211_IFTYPE_ADHOC && link_conf->bssid)57ether_addr_copy(cmd.ibss_bssid_addr, link_conf->bssid);5859return iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_ADD);60}6162/* Get the basic rates of the used band and add the mandatory ones */63static void iwl_mld_fill_rates(struct iwl_mld *mld,64struct ieee80211_bss_conf *link,65struct ieee80211_chanctx_conf *chan_ctx,66__le32 *cck_rates, __le32 *ofdm_rates)67{68struct cfg80211_chan_def *chandef =69iwl_mld_get_chandef_from_chanctx(mld, chan_ctx);70struct ieee80211_supported_band *sband =71mld->hw->wiphy->bands[chandef->chan->band];72unsigned long basic = link->basic_rates;73int lowest_present_ofdm = 100;74int lowest_present_cck = 100;75u32 cck = 0;76u32 ofdm = 0;77int i;7879for_each_set_bit(i, &basic, BITS_PER_LONG) {80int hw = sband->bitrates[i].hw_value;8182if (hw >= IWL_FIRST_OFDM_RATE) {83ofdm |= BIT(hw - IWL_FIRST_OFDM_RATE);84if (lowest_present_ofdm > hw)85lowest_present_ofdm = hw;86} else {87BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0);8889cck |= BIT(hw);90if (lowest_present_cck > hw)91lowest_present_cck = hw;92}93}9495/* Now we've got the basic rates as bitmaps in the ofdm and cck96* variables. This isn't sufficient though, as there might not97* be all the right rates in the bitmap. E.g. if the only basic98* rates are 5.5 Mbps and 11 Mbps, we still need to add 1 Mbps99* and 6 Mbps because the 802.11-2007 standard says in 9.6:100*101* [...] a STA responding to a received frame shall transmit102* its Control Response frame [...] at the highest rate in the103* BSSBasicRateSet parameter that is less than or equal to the104* rate of the immediately previous frame in the frame exchange105* sequence ([...]) and that is of the same modulation class106* ([...]) as the received frame. If no rate contained in the107* BSSBasicRateSet parameter meets these conditions, then the108* control frame sent in response to a received frame shall be109* transmitted at the highest mandatory rate of the PHY that is110* less than or equal to the rate of the received frame, and111* that is of the same modulation class as the received frame.112*113* As a consequence, we need to add all mandatory rates that are114* lower than all of the basic rates to these bitmaps.115*/116117if (lowest_present_ofdm > IWL_RATE_24M_INDEX)118ofdm |= IWL_RATE_BIT_MSK(24) >> IWL_FIRST_OFDM_RATE;119if (lowest_present_ofdm > IWL_RATE_12M_INDEX)120ofdm |= IWL_RATE_BIT_MSK(12) >> IWL_FIRST_OFDM_RATE;121/* 6M already there or needed so always add */122ofdm |= IWL_RATE_BIT_MSK(6) >> IWL_FIRST_OFDM_RATE;123124/* CCK is a bit more complex with DSSS vs. HR/DSSS vs. ERP.125* Note, however:126* - if no CCK rates are basic, it must be ERP since there must127* be some basic rates at all, so they're OFDM => ERP PHY128* (or we're in 5 GHz, and the cck bitmap will never be used)129* - if 11M is a basic rate, it must be ERP as well, so add 5.5M130* - if 5.5M is basic, 1M and 2M are mandatory131* - if 2M is basic, 1M is mandatory132* - if 1M is basic, that's the only valid ACK rate.133* As a consequence, it's not as complicated as it sounds, just add134* any lower rates to the ACK rate bitmap.135*/136if (lowest_present_cck > IWL_RATE_11M_INDEX)137cck |= IWL_RATE_BIT_MSK(11) >> IWL_FIRST_CCK_RATE;138if (lowest_present_cck > IWL_RATE_5M_INDEX)139cck |= IWL_RATE_BIT_MSK(5) >> IWL_FIRST_CCK_RATE;140if (lowest_present_cck > IWL_RATE_2M_INDEX)141cck |= IWL_RATE_BIT_MSK(2) >> IWL_FIRST_CCK_RATE;142/* 1M already there or needed so always add */143cck |= IWL_RATE_BIT_MSK(1) >> IWL_FIRST_CCK_RATE;144145*cck_rates = cpu_to_le32((u32)cck);146*ofdm_rates = cpu_to_le32((u32)ofdm);147}148149static void iwl_mld_fill_protection_flags(struct iwl_mld *mld,150struct ieee80211_bss_conf *link,151__le32 *protection_flags)152{153u8 protection_mode = link->ht_operation_mode &154IEEE80211_HT_OP_MODE_PROTECTION;155u8 ht_flag = LINK_PROT_FLG_HT_PROT | LINK_PROT_FLG_FAT_PROT;156157IWL_DEBUG_RATE(mld, "HT protection mode: %d\n", protection_mode);158159if (link->use_cts_prot)160*protection_flags |= cpu_to_le32(LINK_PROT_FLG_TGG_PROTECT);161162/* See section 9.23.3.1 of IEEE 80211-2012.163* Nongreenfield HT STAs Present is not supported.164*/165switch (protection_mode) {166case IEEE80211_HT_OP_MODE_PROTECTION_NONE:167break;168case IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER:169case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED:170*protection_flags |= cpu_to_le32(ht_flag);171break;172case IEEE80211_HT_OP_MODE_PROTECTION_20MHZ:173/* Protect when channel wider than 20MHz */174if (link->chanreq.oper.width > NL80211_CHAN_WIDTH_20)175*protection_flags |= cpu_to_le32(ht_flag);176break;177}178}179180static u8 iwl_mld_mac80211_ac_to_fw_ac(enum ieee80211_ac_numbers ac)181{182static const u8 mac80211_ac_to_fw[] = {183AC_VO,184AC_VI,185AC_BE,186AC_BK187};188189return mac80211_ac_to_fw[ac];190}191192static void iwl_mld_fill_qos_params(struct ieee80211_bss_conf *link,193struct iwl_ac_qos *ac, __le32 *qos_flags)194{195struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);196197/* no need to check mld_link since it is done in the caller */198199for (int mac_ac = 0; mac_ac < IEEE80211_NUM_ACS; mac_ac++) {200u8 txf = iwl_mld_mac80211_ac_to_fw_tx_fifo(mac_ac);201u8 fw_ac = iwl_mld_mac80211_ac_to_fw_ac(mac_ac);202203ac[fw_ac].cw_min =204cpu_to_le16(mld_link->queue_params[mac_ac].cw_min);205ac[fw_ac].cw_max =206cpu_to_le16(mld_link->queue_params[mac_ac].cw_max);207ac[fw_ac].edca_txop =208cpu_to_le16(mld_link->queue_params[mac_ac].txop * 32);209ac[fw_ac].aifsn = mld_link->queue_params[mac_ac].aifs;210ac[fw_ac].fifos_mask = BIT(txf);211}212213if (link->qos)214*qos_flags |= cpu_to_le32(MAC_QOS_FLG_UPDATE_EDCA);215216if (link->chanreq.oper.width != NL80211_CHAN_WIDTH_20_NOHT)217*qos_flags |= cpu_to_le32(MAC_QOS_FLG_TGN);218}219220static bool iwl_mld_fill_mu_edca(struct iwl_mld *mld,221const struct iwl_mld_link *mld_link,222struct iwl_he_backoff_conf *trig_based_txf)223{224for (int mac_ac = 0; mac_ac < IEEE80211_NUM_ACS; mac_ac++) {225const struct ieee80211_he_mu_edca_param_ac_rec *mu_edca =226&mld_link->queue_params[mac_ac].mu_edca_param_rec;227u8 fw_ac = iwl_mld_mac80211_ac_to_fw_ac(mac_ac);228229if (!mld_link->queue_params[mac_ac].mu_edca)230return false;231232trig_based_txf[fw_ac].cwmin =233cpu_to_le16(mu_edca->ecw_min_max & 0xf);234trig_based_txf[fw_ac].cwmax =235cpu_to_le16((mu_edca->ecw_min_max & 0xf0) >> 4);236trig_based_txf[fw_ac].aifsn =237cpu_to_le16(mu_edca->aifsn & 0xf);238trig_based_txf[fw_ac].mu_time =239cpu_to_le16(mu_edca->mu_edca_timer);240}241return true;242}243244int245iwl_mld_change_link_in_fw(struct iwl_mld *mld, struct ieee80211_bss_conf *link,246u32 changes)247{248struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);249struct ieee80211_vif *vif = link->vif;250struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);251struct ieee80211_chanctx_conf *chan_ctx;252struct iwl_link_config_cmd cmd = {};253u32 flags = 0;254255lockdep_assert_wiphy(mld->wiphy);256257if (WARN_ON(!mld_link))258return -EINVAL;259260cmd.link_id = cpu_to_le32(mld_link->fw_id);261cmd.spec_link_id = link->link_id;262cmd.mac_id = cpu_to_le32(mld_vif->fw_id);263264chan_ctx = wiphy_dereference(mld->wiphy, mld_link->chan_ctx);265266cmd.phy_id = cpu_to_le32(chan_ctx ?267iwl_mld_phy_from_mac80211(chan_ctx)->fw_id :268FW_CTXT_ID_INVALID);269270ether_addr_copy(cmd.local_link_addr, link->addr);271272cmd.active = cpu_to_le32(mld_link->active);273274if ((changes & LINK_CONTEXT_MODIFY_ACTIVE) && !mld_link->active &&275mld_link->silent_deactivation) {276/* We are de-activating a link that is having CSA with277* immediate quiet in EMLSR. Tell the firmware not to send any278* frame.279*/280cmd.block_tx = 1;281mld_link->silent_deactivation = false;282}283284if (vif->type == NL80211_IFTYPE_ADHOC && link->bssid)285ether_addr_copy(cmd.ibss_bssid_addr, link->bssid);286287/* Channel context is needed to get the rates */288if (chan_ctx)289iwl_mld_fill_rates(mld, link, chan_ctx, &cmd.cck_rates,290&cmd.ofdm_rates);291292cmd.cck_short_preamble = cpu_to_le32(link->use_short_preamble);293cmd.short_slot = cpu_to_le32(link->use_short_slot);294295iwl_mld_fill_protection_flags(mld, link, &cmd.protection_flags);296297iwl_mld_fill_qos_params(link, cmd.ac, &cmd.qos_flags);298299cmd.bi = cpu_to_le32(link->beacon_int);300cmd.dtim_interval = cpu_to_le32(link->beacon_int * link->dtim_period);301302/* Configure HE parameters only if HE is supported, and only after303* the parameters are set in mac80211 (meaning after assoc)304*/305if (!link->he_support || iwlwifi_mod_params.disable_11ax ||306(vif->type == NL80211_IFTYPE_STATION && !vif->cfg.assoc)) {307changes &= ~LINK_CONTEXT_MODIFY_HE_PARAMS;308goto send_cmd;309}310311/* ap_sta may be NULL if we're disconnecting */312if (mld_vif->ap_sta) {313struct ieee80211_link_sta *link_sta =314link_sta_dereference_check(mld_vif->ap_sta,315link->link_id);316317if (!WARN_ON(!link_sta) && link_sta->he_cap.has_he &&318link_sta->he_cap.he_cap_elem.mac_cap_info[5] &319IEEE80211_HE_MAC_CAP5_OM_CTRL_UL_MU_DATA_DIS_RX)320cmd.ul_mu_data_disable = 1;321}322323cmd.htc_trig_based_pkt_ext = link->htc_trig_based_pkt_ext;324325if (link->uora_exists) {326cmd.rand_alloc_ecwmin = link->uora_ocw_range & 0x7;327cmd.rand_alloc_ecwmax = (link->uora_ocw_range >> 3) & 0x7;328}329330if (iwl_mld_fill_mu_edca(mld, mld_link, cmd.trig_based_txf))331flags |= LINK_FLG_MU_EDCA_CW;332333cmd.bss_color = link->he_bss_color.color;334335if (!link->he_bss_color.enabled)336flags |= LINK_FLG_BSS_COLOR_DIS;337338cmd.frame_time_rts_th = cpu_to_le16(link->frame_time_rts_th);339340/* Block 26-tone RU OFDMA transmissions */341if (mld_link->he_ru_2mhz_block)342flags |= LINK_FLG_RU_2MHZ_BLOCK;343344if (link->nontransmitted) {345ether_addr_copy(cmd.ref_bssid_addr, link->transmitter_bssid);346cmd.bssid_index = link->bssid_index;347}348349/* The only EHT parameter is puncturing, and starting from PHY cmd350* version 6 - it is sent there. For older versions of the PHY cmd,351* puncturing is not needed at all.352*/353if (WARN_ON(changes & LINK_CONTEXT_MODIFY_EHT_PARAMS))354changes &= ~LINK_CONTEXT_MODIFY_EHT_PARAMS;355356send_cmd:357cmd.modify_mask = cpu_to_le32(changes);358cmd.flags = cpu_to_le32(flags);359360return iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_MODIFY);361}362363int iwl_mld_activate_link(struct iwl_mld *mld,364struct ieee80211_bss_conf *link)365{366struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);367struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(link->vif);368int ret;369370lockdep_assert_wiphy(mld->wiphy);371372if (WARN_ON(!mld_link || mld_link->active))373return -EINVAL;374375mld_link->active = true;376377ret = iwl_mld_change_link_in_fw(mld, link,378LINK_CONTEXT_MODIFY_ACTIVE);379if (ret)380mld_link->active = false;381else382mld_vif->last_link_activation_time =383ktime_get_boottime_seconds();384385return ret;386}387388void iwl_mld_deactivate_link(struct iwl_mld *mld,389struct ieee80211_bss_conf *link)390{391struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);392struct iwl_probe_resp_data *probe_data;393394lockdep_assert_wiphy(mld->wiphy);395396if (WARN_ON(!mld_link || !mld_link->active))397return;398399iwl_mld_cancel_session_protection(mld, link->vif, link->link_id);400401/* If we deactivate the link, we will probably remove it, or switch402* channel. In both cases, the CSA or Notice of Absence information is403* now irrelevant. Remove the data here.404*/405probe_data = wiphy_dereference(mld->wiphy, mld_link->probe_resp_data);406RCU_INIT_POINTER(mld_link->probe_resp_data, NULL);407if (probe_data)408kfree_rcu(probe_data, rcu_head);409410mld_link->active = false;411412iwl_mld_change_link_in_fw(mld, link, LINK_CONTEXT_MODIFY_ACTIVE);413414/* Now that the link is not active in FW, we don't expect any new415* notifications for it. Cancel the ones that are already pending416*/417iwl_mld_cancel_notifications_of_object(mld, IWL_MLD_OBJECT_TYPE_LINK,418mld_link->fw_id);419}420421static void422iwl_mld_rm_link_from_fw(struct iwl_mld *mld, struct ieee80211_bss_conf *link)423{424struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);425struct iwl_link_config_cmd cmd = {};426427lockdep_assert_wiphy(mld->wiphy);428429if (WARN_ON(!mld_link))430return;431432cmd.link_id = cpu_to_le32(mld_link->fw_id);433cmd.spec_link_id = link->link_id;434cmd.phy_id = cpu_to_le32(FW_CTXT_ID_INVALID);435436iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_REMOVE);437}438439IWL_MLD_ALLOC_FN(link, bss_conf)440441/* Constructor function for struct iwl_mld_link */442static int443iwl_mld_init_link(struct iwl_mld *mld, struct ieee80211_bss_conf *link,444struct iwl_mld_link *mld_link)445{446mld_link->average_beacon_energy = 0;447448iwl_mld_init_internal_sta(&mld_link->bcast_sta);449iwl_mld_init_internal_sta(&mld_link->mcast_sta);450iwl_mld_init_internal_sta(&mld_link->mon_sta);451452return iwl_mld_allocate_link_fw_id(mld, &mld_link->fw_id, link);453}454455/* Initializes the link structure, maps fw id to the ieee80211_bss_conf, and456* adds a link to the fw457*/458int iwl_mld_add_link(struct iwl_mld *mld,459struct ieee80211_bss_conf *bss_conf)460{461struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(bss_conf->vif);462struct iwl_mld_link *link = iwl_mld_link_from_mac80211(bss_conf);463bool is_deflink = bss_conf == &bss_conf->vif->bss_conf;464int ret;465466if (!link) {467if (is_deflink)468link = &mld_vif->deflink;469else470link = kzalloc(sizeof(*link), GFP_KERNEL);471} else {472WARN_ON(!mld->fw_status.in_hw_restart);473}474475ret = iwl_mld_init_link(mld, bss_conf, link);476if (ret)477goto free;478479rcu_assign_pointer(mld_vif->link[bss_conf->link_id], link);480481ret = iwl_mld_add_link_to_fw(mld, bss_conf);482if (ret) {483RCU_INIT_POINTER(mld->fw_id_to_bss_conf[link->fw_id], NULL);484RCU_INIT_POINTER(mld_vif->link[bss_conf->link_id], NULL);485goto free;486}487488return ret;489490free:491if (!is_deflink)492kfree(link);493return ret;494}495496/* Remove link from fw, unmap the bss_conf, and destroy the link structure */497void iwl_mld_remove_link(struct iwl_mld *mld,498struct ieee80211_bss_conf *bss_conf)499{500struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(bss_conf->vif);501struct iwl_mld_link *link = iwl_mld_link_from_mac80211(bss_conf);502bool is_deflink = link == &mld_vif->deflink;503504if (WARN_ON(!link || link->active))505return;506507iwl_mld_rm_link_from_fw(mld, bss_conf);508/* Continue cleanup on failure */509510if (!is_deflink)511kfree_rcu(link, rcu_head);512513RCU_INIT_POINTER(mld_vif->link[bss_conf->link_id], NULL);514515if (WARN_ON(link->fw_id >= mld->fw->ucode_capa.num_links))516return;517518RCU_INIT_POINTER(mld->fw_id_to_bss_conf[link->fw_id], NULL);519}520521void iwl_mld_handle_missed_beacon_notif(struct iwl_mld *mld,522struct iwl_rx_packet *pkt)523{524const struct iwl_missed_beacons_notif *notif = (const void *)pkt->data;525union iwl_dbg_tlv_tp_data tp_data = { .fw_pkt = pkt };526u32 fw_link_id = le32_to_cpu(notif->link_id);527u32 missed_bcon = le32_to_cpu(notif->consec_missed_beacons);528u32 missed_bcon_since_rx =529le32_to_cpu(notif->consec_missed_beacons_since_last_rx);530u32 scnd_lnk_bcn_lost =531le32_to_cpu(notif->consec_missed_beacons_other_link);532struct ieee80211_bss_conf *link_conf =533iwl_mld_fw_id_to_link_conf(mld, fw_link_id);534u32 bss_param_ch_cnt_link_id;535struct ieee80211_vif *vif;536u8 link_id;537538if (WARN_ON(!link_conf))539return;540541vif = link_conf->vif;542link_id = link_conf->link_id;543bss_param_ch_cnt_link_id = link_conf->bss_param_ch_cnt_link_id;544545IWL_DEBUG_INFO(mld,546"missed bcn link_id=%u, %u consecutive=%u\n",547link_id, missed_bcon, missed_bcon_since_rx);548549if (WARN_ON(!vif))550return;551552mld->trans->dbg.dump_file_name_ext_valid = true;553snprintf(mld->trans->dbg.dump_file_name_ext, IWL_FW_INI_MAX_NAME,554"LinkId_%d_MacType_%d", fw_link_id,555iwl_mld_mac80211_iftype_to_fw(vif));556557iwl_dbg_tlv_time_point(&mld->fwrt,558IWL_FW_INI_TIME_POINT_MISSED_BEACONS, &tp_data);559560if (missed_bcon >= IWL_MLD_MISSED_BEACONS_THRESHOLD_LONG) {561if (missed_bcon_since_rx >=562IWL_MLD_MISSED_BEACONS_SINCE_RX_THOLD) {563ieee80211_connection_loss(vif);564return;565}566IWL_WARN(mld,567"missed beacons exceeds threshold, but receiving data. Stay connected, Expect bugs.\n");568return;569}570571if (missed_bcon_since_rx > IWL_MLD_MISSED_BEACONS_THRESHOLD) {572ieee80211_cqm_beacon_loss_notify(vif, GFP_ATOMIC);573574/* try to switch links, no-op if we don't have MLO */575iwl_mld_int_mlo_scan(mld, vif);576}577578/* no more logic if we're not in EMLSR */579if (hweight16(vif->active_links) <= 1)580return;581582/* We are processing a notification before link activation */583if (le32_to_cpu(notif->other_link_id) == FW_CTXT_ID_INVALID)584return;585586/* Exit EMLSR if we lost more than587* IWL_MLD_MISSED_BEACONS_EXIT_ESR_THRESH beacons on boths links588* OR more than IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH on current link.589* OR more than IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED590* on current link and the link's bss_param_ch_count has changed on591* the other link's beacon.592*/593if ((missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS &&594scnd_lnk_bcn_lost >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS) ||595missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH ||596(bss_param_ch_cnt_link_id != link_id &&597missed_bcon >=598IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED)) {599iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_MISSED_BEACON,600iwl_mld_get_primary_link(vif));601}602}603EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_handle_missed_beacon_notif);604605bool iwl_mld_cancel_missed_beacon_notif(struct iwl_mld *mld,606struct iwl_rx_packet *pkt,607u32 removed_link_id)608{609struct iwl_missed_beacons_notif *notif = (void *)pkt->data;610611if (le32_to_cpu(notif->other_link_id) == removed_link_id) {612/* Second link is being removed. Don't cancel the notification,613* but mark second link as invalid.614*/615notif->other_link_id = cpu_to_le32(FW_CTXT_ID_INVALID);616}617618/* If the primary link is removed, cancel the notification */619return le32_to_cpu(notif->link_id) == removed_link_id;620}621622int iwl_mld_link_set_associated(struct iwl_mld *mld, struct ieee80211_vif *vif,623struct ieee80211_bss_conf *link)624{625return iwl_mld_change_link_in_fw(mld, link, LINK_CONTEXT_MODIFY_ALL &626~(LINK_CONTEXT_MODIFY_ACTIVE |627LINK_CONTEXT_MODIFY_EHT_PARAMS));628}629630struct iwl_mld_rssi_to_grade {631s8 rssi[2];632u16 grade;633};634635#define RSSI_TO_GRADE_LINE(_lb, _hb_uhb, _grade) \636{ \637.rssi = {_lb, _hb_uhb}, \638.grade = _grade \639}640641/*642* This array must be sorted by increasing RSSI for proper functionality.643* The grades are actually estimated throughput, represented as fixed-point644* with a scale factor of 1/10.645*/646static const struct iwl_mld_rssi_to_grade rssi_to_grade_map[] = {647RSSI_TO_GRADE_LINE(-85, -89, 172),648RSSI_TO_GRADE_LINE(-83, -86, 344),649RSSI_TO_GRADE_LINE(-82, -85, 516),650RSSI_TO_GRADE_LINE(-80, -83, 688),651RSSI_TO_GRADE_LINE(-77, -79, 1032),652RSSI_TO_GRADE_LINE(-73, -76, 1376),653RSSI_TO_GRADE_LINE(-70, -74, 1548),654RSSI_TO_GRADE_LINE(-69, -72, 1720),655RSSI_TO_GRADE_LINE(-65, -68, 2064),656RSSI_TO_GRADE_LINE(-61, -66, 2294),657RSSI_TO_GRADE_LINE(-58, -61, 2580),658RSSI_TO_GRADE_LINE(-55, -58, 2868),659RSSI_TO_GRADE_LINE(-46, -55, 3098),660RSSI_TO_GRADE_LINE(-43, -54, 3442)661};662663#define MAX_GRADE (rssi_to_grade_map[ARRAY_SIZE(rssi_to_grade_map) - 1].grade)664665#define DEFAULT_CHAN_LOAD_2GHZ 30666#define DEFAULT_CHAN_LOAD_5GHZ 15667#define DEFAULT_CHAN_LOAD_6GHZ 0668669/* Factors calculation is done with fixed-point with a scaling factor of 1/256 */670#define SCALE_FACTOR 256671#define MAX_CHAN_LOAD 256672673static unsigned int674iwl_mld_get_n_subchannels(const struct ieee80211_bss_conf *link_conf)675{676enum nl80211_chan_width chan_width =677link_conf->chanreq.oper.width;678int mhz = nl80211_chan_width_to_mhz(chan_width);679unsigned int n_subchannels;680681if (WARN_ONCE(mhz < 20 || mhz > 320,682"Invalid channel width : (%d)\n", mhz))683return 1;684685/* total number of subchannels */686n_subchannels = mhz / 20;687688/* No puncturing if less than 80 MHz */689if (mhz >= 80)690n_subchannels -= hweight16(link_conf->chanreq.oper.punctured);691692return n_subchannels;693}694695static int696iwl_mld_get_chan_load_from_element(struct iwl_mld *mld,697struct ieee80211_bss_conf *link_conf)698{699struct ieee80211_vif *vif = link_conf->vif;700const struct cfg80211_bss_ies *ies;701const struct element *bss_load_elem = NULL;702const struct ieee80211_bss_load_elem *bss_load;703704guard(rcu)();705706if (ieee80211_vif_link_active(vif, link_conf->link_id))707ies = rcu_dereference(link_conf->bss->beacon_ies);708else709ies = rcu_dereference(link_conf->bss->ies);710711if (ies)712bss_load_elem = cfg80211_find_elem(WLAN_EID_QBSS_LOAD,713ies->data, ies->len);714715if (!bss_load_elem ||716bss_load_elem->datalen != sizeof(*bss_load))717return -EINVAL;718719bss_load = (const void *)bss_load_elem->data;720721return bss_load->channel_util;722}723724static unsigned int725iwl_mld_get_chan_load_by_us(struct iwl_mld *mld,726struct ieee80211_bss_conf *link_conf,727bool expect_active_link)728{729struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link_conf);730struct ieee80211_chanctx_conf *chan_ctx;731struct iwl_mld_phy *phy;732733if (!mld_link || !mld_link->active) {734WARN_ON(expect_active_link);735return 0;736}737738if (WARN_ONCE(!rcu_access_pointer(mld_link->chan_ctx),739"Active link (%u) without channel ctxt assigned!\n",740link_conf->link_id))741return 0;742743chan_ctx = wiphy_dereference(mld->wiphy, mld_link->chan_ctx);744phy = iwl_mld_phy_from_mac80211(chan_ctx);745746return phy->channel_load_by_us;747}748749/* Returns error if the channel utilization element is invalid/unavailable */750int iwl_mld_get_chan_load_by_others(struct iwl_mld *mld,751struct ieee80211_bss_conf *link_conf,752bool expect_active_link)753{754int chan_load;755unsigned int chan_load_by_us;756757/* get overall load */758chan_load = iwl_mld_get_chan_load_from_element(mld, link_conf);759if (chan_load < 0)760return chan_load;761762chan_load_by_us = iwl_mld_get_chan_load_by_us(mld, link_conf,763expect_active_link);764765/* channel load by us is given in percentage */766chan_load_by_us =767NORMALIZE_PERCENT_TO_255(chan_load_by_us);768769/* Use only values that firmware sends that can possibly be valid */770if (chan_load_by_us <= chan_load)771chan_load -= chan_load_by_us;772773return chan_load;774}775776static unsigned int777iwl_mld_get_default_chan_load(struct ieee80211_bss_conf *link_conf)778{779enum nl80211_band band = link_conf->chanreq.oper.chan->band;780781switch (band) {782case NL80211_BAND_2GHZ:783return DEFAULT_CHAN_LOAD_2GHZ;784case NL80211_BAND_5GHZ:785return DEFAULT_CHAN_LOAD_5GHZ;786case NL80211_BAND_6GHZ:787return DEFAULT_CHAN_LOAD_6GHZ;788default:789WARN_ON(1);790return 0;791}792}793794unsigned int iwl_mld_get_chan_load(struct iwl_mld *mld,795struct ieee80211_bss_conf *link_conf)796{797int chan_load;798799chan_load = iwl_mld_get_chan_load_by_others(mld, link_conf, false);800if (chan_load >= 0)801return chan_load;802803/* No information from the element, take the defaults */804chan_load = iwl_mld_get_default_chan_load(link_conf);805806/* The defaults are given in percentage */807return NORMALIZE_PERCENT_TO_255(chan_load);808}809810static unsigned int811iwl_mld_get_avail_chan_load(struct iwl_mld *mld,812struct ieee80211_bss_conf *link_conf)813{814return MAX_CHAN_LOAD - iwl_mld_get_chan_load(mld, link_conf);815}816817/* This function calculates the grade of a link. Returns 0 in error case */818unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld,819struct ieee80211_bss_conf *link_conf)820{821enum nl80211_band band;822int rssi_idx;823s32 link_rssi;824unsigned int grade = MAX_GRADE;825826if (WARN_ON_ONCE(!link_conf))827return 0;828829band = link_conf->chanreq.oper.chan->band;830if (WARN_ONCE(band != NL80211_BAND_2GHZ &&831band != NL80211_BAND_5GHZ &&832band != NL80211_BAND_6GHZ,833"Invalid band (%u)\n", band))834return 0;835836link_rssi = MBM_TO_DBM(link_conf->bss->signal);837/*838* For 6 GHz the RSSI of the beacons is lower than839* the RSSI of the data.840*/841if (band == NL80211_BAND_6GHZ && link_rssi)842link_rssi += 4;843844rssi_idx = band == NL80211_BAND_2GHZ ? 0 : 1;845846/* No valid RSSI - take the lowest grade */847if (!link_rssi)848link_rssi = rssi_to_grade_map[0].rssi[rssi_idx];849850IWL_DEBUG_EHT(mld,851"Calculating grade of link %d: band = %d, bandwidth = %d, punctured subchannels =0x%x RSSI = %d\n",852link_conf->link_id, band,853link_conf->chanreq.oper.width,854link_conf->chanreq.oper.punctured, link_rssi);855856/* Get grade based on RSSI */857for (int i = 0; i < ARRAY_SIZE(rssi_to_grade_map); i++) {858const struct iwl_mld_rssi_to_grade *line =859&rssi_to_grade_map[i];860861if (link_rssi > line->rssi[rssi_idx])862continue;863grade = line->grade;864break;865}866867/* Apply the channel load and puncturing factors */868grade = grade * iwl_mld_get_avail_chan_load(mld, link_conf) / SCALE_FACTOR;869grade = grade * iwl_mld_get_n_subchannels(link_conf);870871IWL_DEBUG_EHT(mld, "Link %d's grade: %d\n", link_conf->link_id, grade);872873return grade;874}875EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_get_link_grade);876877void iwl_mld_handle_beacon_filter_notif(struct iwl_mld *mld,878struct iwl_rx_packet *pkt)879{880const struct iwl_beacon_filter_notif *notif = (const void *)pkt->data;881u32 link_id = le32_to_cpu(notif->link_id);882struct ieee80211_bss_conf *link_conf =883iwl_mld_fw_id_to_link_conf(mld, link_id);884struct iwl_mld_link *mld_link;885886if (IWL_FW_CHECK(mld, !link_conf, "invalid link ID %d\n", link_id))887return;888889mld_link = iwl_mld_link_from_mac80211(link_conf);890if (WARN_ON_ONCE(!mld_link))891return;892893mld_link->average_beacon_energy = le32_to_cpu(notif->average_energy);894}895896897