Path: blob/main/sys/contrib/dev/iwlwifi/mvm/link.c
48287 views
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause1/*2* Copyright (C) 2022 - 2024 Intel Corporation3*/4#include "mvm.h"5#include "time-event.h"67#define HANDLE_ESR_REASONS(HOW) \8HOW(BLOCKED_PREVENTION) \9HOW(BLOCKED_WOWLAN) \10HOW(BLOCKED_TPT) \11HOW(BLOCKED_FW) \12HOW(BLOCKED_NON_BSS) \13HOW(BLOCKED_ROC) \14HOW(BLOCKED_TMP_NON_BSS) \15HOW(EXIT_MISSED_BEACON) \16HOW(EXIT_LOW_RSSI) \17HOW(EXIT_COEX) \18HOW(EXIT_BANDWIDTH) \19HOW(EXIT_CSA) \20HOW(EXIT_LINK_USAGE)2122static const char *const iwl_mvm_esr_states_names[] = {23#define NAME_ENTRY(x) [ilog2(IWL_MVM_ESR_##x)] = #x,24HANDLE_ESR_REASONS(NAME_ENTRY)25};2627const char *iwl_get_esr_state_string(enum iwl_mvm_esr_state state)28{29int offs = ilog2(state);3031if (offs >= ARRAY_SIZE(iwl_mvm_esr_states_names) ||32!iwl_mvm_esr_states_names[offs])33return "UNKNOWN";3435return iwl_mvm_esr_states_names[offs];36}3738static void iwl_mvm_print_esr_state(struct iwl_mvm *mvm, u32 mask)39{40#define NAME_FMT(x) "%s"41#define NAME_PR(x) (mask & IWL_MVM_ESR_##x) ? "[" #x "]" : "",42IWL_DEBUG_INFO(mvm,43"EMLSR state = " HANDLE_ESR_REASONS(NAME_FMT)44" (0x%x)\n",45HANDLE_ESR_REASONS(NAME_PR)46mask);47#undef NAME_FMT48#undef NAME_PR49}5051static int iwl_mvm_link_cmd_send(struct iwl_mvm *mvm,52struct iwl_link_config_cmd *cmd,53enum iwl_ctxt_action action)54{55int ret;5657cmd->action = cpu_to_le32(action);58ret = iwl_mvm_send_cmd_pdu(mvm,59WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD), 0,60sizeof(*cmd), cmd);61if (ret)62IWL_ERR(mvm, "Failed to send LINK_CONFIG_CMD (action:%d): %d\n",63action, ret);64return ret;65}6667void iwl_mvm_set_link_fw_id(struct iwl_mvm *mvm, struct ieee80211_vif *vif,68struct ieee80211_bss_conf *link_conf)69{70struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);71struct iwl_mvm_vif_link_info *link_info =72mvmvif->link[link_conf->link_id];7374if (link_info->fw_link_id == IWL_MVM_FW_LINK_ID_INVALID)75link_info->fw_link_id = mvmvif->id;76}7778int iwl_mvm_add_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,79struct ieee80211_bss_conf *link_conf)80{81struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);82unsigned int link_id = link_conf->link_id;83struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id];84struct iwl_link_config_cmd cmd = {};85unsigned int cmd_id = WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD);86u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 1);8788if (WARN_ON_ONCE(!link_info))89return -EINVAL;9091iwl_mvm_set_link_fw_id(mvm, vif, link_conf);9293/* Update SF - Disable if needed. if this fails, SF might still be on94* while many macs are bound, which is forbidden - so fail the binding.95*/96if (iwl_mvm_sf_update(mvm, vif, false))97return -EINVAL;9899cmd.link_id = cpu_to_le32(link_info->fw_link_id);100cmd.mac_id = cpu_to_le32(mvmvif->id);101cmd.spec_link_id = link_conf->link_id;102WARN_ON_ONCE(link_info->phy_ctxt);103cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID);104105memcpy(cmd.local_link_addr, link_conf->addr, ETH_ALEN);106107if (vif->type == NL80211_IFTYPE_ADHOC && link_conf->bssid)108memcpy(cmd.ibss_bssid_addr, link_conf->bssid, ETH_ALEN);109110if (cmd_ver < 2)111cmd.listen_lmac = cpu_to_le32(link_info->listen_lmac);112113return iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_ADD);114}115116struct iwl_mvm_esr_iter_data {117struct ieee80211_vif *vif;118unsigned int link_id;119bool lift_block;120};121122static void iwl_mvm_esr_vif_iterator(void *_data, u8 *mac,123struct ieee80211_vif *vif)124{125struct iwl_mvm_esr_iter_data *data = _data;126struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);127int link_id;128129if (ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_STATION)130return;131132for_each_mvm_vif_valid_link(mvmvif, link_id) {133struct iwl_mvm_vif_link_info *link_info =134mvmvif->link[link_id];135if (vif == data->vif && link_id == data->link_id)136continue;137if (link_info->active)138data->lift_block = false;139}140}141142int iwl_mvm_esr_non_bss_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,143unsigned int link_id, bool active)144{145/* An active link of a non-station vif blocks EMLSR. Upon activation146* block EMLSR on the bss vif. Upon deactivation, check if this link147* was the last non-station link active, and if so unblock the bss vif148*/149struct ieee80211_vif *bss_vif = iwl_mvm_get_bss_vif(mvm);150struct iwl_mvm_esr_iter_data data = {151.vif = vif,152.link_id = link_id,153.lift_block = true,154};155156if (IS_ERR_OR_NULL(bss_vif))157return 0;158159if (active)160return iwl_mvm_block_esr_sync(mvm, bss_vif,161IWL_MVM_ESR_BLOCKED_NON_BSS);162163ieee80211_iterate_active_interfaces(mvm->hw,164IEEE80211_IFACE_ITER_NORMAL,165iwl_mvm_esr_vif_iterator, &data);166if (data.lift_block) {167mutex_lock(&mvm->mutex);168iwl_mvm_unblock_esr(mvm, bss_vif, IWL_MVM_ESR_BLOCKED_NON_BSS);169mutex_unlock(&mvm->mutex);170}171172return 0;173}174175int iwl_mvm_link_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif,176struct ieee80211_bss_conf *link_conf,177u32 changes, bool active)178{179struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);180unsigned int link_id = link_conf->link_id;181struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id];182struct iwl_mvm_phy_ctxt *phyctxt;183struct iwl_link_config_cmd cmd = {};184u32 ht_flag, flags = 0, flags_mask = 0;185int ret;186unsigned int cmd_id = WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD);187u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 1);188189if (WARN_ON_ONCE(!link_info ||190link_info->fw_link_id == IWL_MVM_FW_LINK_ID_INVALID))191return -EINVAL;192193if (changes & LINK_CONTEXT_MODIFY_ACTIVE) {194/* When activating a link, phy context should be valid;195* when deactivating a link, it also should be valid since196* the link was active before. So, do nothing in this case.197* Since a link is added first with FW_CTXT_INVALID, then we198* can get here in case it's removed before it was activated.199*/200if (!link_info->phy_ctxt)201return 0;202203/* Catch early if driver tries to activate or deactivate a link204* twice.205*/206WARN_ON_ONCE(active == link_info->active);207208/* When deactivating a link session protection should209* be stopped. Also let the firmware know if we can't Tx.210*/211if (!active && vif->type == NL80211_IFTYPE_STATION) {212iwl_mvm_stop_session_protection(mvm, vif);213if (link_info->csa_block_tx) {214cmd.block_tx = 1;215link_info->csa_block_tx = false;216}217}218}219220cmd.link_id = cpu_to_le32(link_info->fw_link_id);221222/* The phy_id, link address and listen_lmac can be modified only until223* the link becomes active, otherwise they will be ignored.224*/225phyctxt = link_info->phy_ctxt;226if (phyctxt)227cmd.phy_id = cpu_to_le32(phyctxt->id);228else229cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID);230cmd.mac_id = cpu_to_le32(mvmvif->id);231232memcpy(cmd.local_link_addr, link_conf->addr, ETH_ALEN);233234cmd.active = cpu_to_le32(active);235236if (vif->type == NL80211_IFTYPE_ADHOC && link_conf->bssid)237memcpy(cmd.ibss_bssid_addr, link_conf->bssid, ETH_ALEN);238239iwl_mvm_set_fw_basic_rates(mvm, vif, link_info,240&cmd.cck_rates, &cmd.ofdm_rates);241242cmd.cck_short_preamble = cpu_to_le32(link_conf->use_short_preamble);243cmd.short_slot = cpu_to_le32(link_conf->use_short_slot);244245/* The fw does not distinguish between ht and fat */246ht_flag = LINK_PROT_FLG_HT_PROT | LINK_PROT_FLG_FAT_PROT;247iwl_mvm_set_fw_protection_flags(mvm, vif, link_conf,248&cmd.protection_flags,249ht_flag, LINK_PROT_FLG_TGG_PROTECT);250251iwl_mvm_set_fw_qos_params(mvm, vif, link_conf, cmd.ac,252&cmd.qos_flags);253254255cmd.bi = cpu_to_le32(link_conf->beacon_int);256cmd.dtim_interval = cpu_to_le32(link_conf->beacon_int *257link_conf->dtim_period);258259if (!link_conf->he_support || iwlwifi_mod_params.disable_11ax ||260(vif->type == NL80211_IFTYPE_STATION && !vif->cfg.assoc)) {261changes &= ~LINK_CONTEXT_MODIFY_HE_PARAMS;262goto send_cmd;263}264265cmd.htc_trig_based_pkt_ext = link_conf->htc_trig_based_pkt_ext;266267if (link_conf->uora_exists) {268cmd.rand_alloc_ecwmin =269link_conf->uora_ocw_range & 0x7;270cmd.rand_alloc_ecwmax =271(link_conf->uora_ocw_range >> 3) & 0x7;272}273274/* ap_sta may be NULL if we're disconnecting */275if (changes & LINK_CONTEXT_MODIFY_HE_PARAMS && mvmvif->ap_sta) {276struct ieee80211_link_sta *link_sta =277link_sta_dereference_check(mvmvif->ap_sta, link_id);278279if (!WARN_ON(!link_sta) && link_sta->he_cap.has_he &&280link_sta->he_cap.he_cap_elem.mac_cap_info[5] &281IEEE80211_HE_MAC_CAP5_OM_CTRL_UL_MU_DATA_DIS_RX)282cmd.ul_mu_data_disable = 1;283}284285/* TODO how to set ndp_fdbk_buff_th_exp? */286287if (iwl_mvm_set_fw_mu_edca_params(mvm, mvmvif->link[link_id],288&cmd.trig_based_txf[0])) {289flags |= LINK_FLG_MU_EDCA_CW;290flags_mask |= LINK_FLG_MU_EDCA_CW;291}292293if (changes & LINK_CONTEXT_MODIFY_EHT_PARAMS) {294struct ieee80211_chanctx_conf *ctx;295struct cfg80211_chan_def *def = NULL;296297rcu_read_lock();298ctx = rcu_dereference(link_conf->chanctx_conf);299if (ctx)300def = iwl_mvm_chanctx_def(mvm, ctx);301302if (iwlwifi_mod_params.disable_11be ||303!link_conf->eht_support || !def ||304iwl_fw_lookup_cmd_ver(mvm->fw, PHY_CONTEXT_CMD, 1) >= 6)305changes &= ~LINK_CONTEXT_MODIFY_EHT_PARAMS;306else307cmd.puncture_mask = cpu_to_le16(def->punctured);308rcu_read_unlock();309}310311cmd.bss_color = link_conf->he_bss_color.color;312313if (!link_conf->he_bss_color.enabled) {314flags |= LINK_FLG_BSS_COLOR_DIS;315flags_mask |= LINK_FLG_BSS_COLOR_DIS;316}317318cmd.frame_time_rts_th = cpu_to_le16(link_conf->frame_time_rts_th);319320/* Block 26-tone RU OFDMA transmissions */321if (link_info->he_ru_2mhz_block) {322flags |= LINK_FLG_RU_2MHZ_BLOCK;323flags_mask |= LINK_FLG_RU_2MHZ_BLOCK;324}325326if (link_conf->nontransmitted) {327ether_addr_copy(cmd.ref_bssid_addr,328link_conf->transmitter_bssid);329cmd.bssid_index = link_conf->bssid_index;330}331332send_cmd:333cmd.modify_mask = cpu_to_le32(changes);334cmd.flags = cpu_to_le32(flags);335if (cmd_ver < 6)336cmd.flags_mask = cpu_to_le32(flags_mask);337cmd.spec_link_id = link_conf->link_id;338if (cmd_ver < 2)339cmd.listen_lmac = cpu_to_le32(link_info->listen_lmac);340341ret = iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_MODIFY);342if (!ret && (changes & LINK_CONTEXT_MODIFY_ACTIVE))343link_info->active = active;344345return ret;346}347348int iwl_mvm_remove_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,349struct ieee80211_bss_conf *link_conf)350{351struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);352unsigned int link_id = link_conf->link_id;353struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id];354struct iwl_link_config_cmd cmd = {};355int ret;356357cmd.link_id = cpu_to_le32(link_info->fw_link_id);358link_info->fw_link_id = IWL_MVM_FW_LINK_ID_INVALID;359cmd.spec_link_id = link_conf->link_id;360cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID);361362ret = iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_REMOVE);363364if (!ret && iwl_mvm_sf_update(mvm, vif, true))365IWL_ERR(mvm, "Failed to update SF state\n");366367return ret;368}369370/* link should be deactivated before removal, so in most cases we need to371* perform these two operations together372*/373int iwl_mvm_disable_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,374struct ieee80211_bss_conf *link_conf)375{376int ret;377378ret = iwl_mvm_link_changed(mvm, vif, link_conf,379LINK_CONTEXT_MODIFY_ACTIVE, false);380if (ret)381return ret;382383ret = iwl_mvm_remove_link(mvm, vif, link_conf);384if (ret)385return ret;386387return ret;388}389390struct iwl_mvm_rssi_to_grade {391s8 rssi[2];392u16 grade;393};394395#define RSSI_TO_GRADE_LINE(_lb, _hb_uhb, _grade) \396{ \397.rssi = {_lb, _hb_uhb}, \398.grade = _grade \399}400401/*402* This array must be sorted by increasing RSSI for proper functionality.403* The grades are actually estimated throughput, represented as fixed-point404* with a scale factor of 1/10.405*/406static const struct iwl_mvm_rssi_to_grade rssi_to_grade_map[] = {407RSSI_TO_GRADE_LINE(-85, -89, 177),408RSSI_TO_GRADE_LINE(-83, -86, 344),409RSSI_TO_GRADE_LINE(-82, -85, 516),410RSSI_TO_GRADE_LINE(-80, -83, 688),411RSSI_TO_GRADE_LINE(-77, -79, 1032),412RSSI_TO_GRADE_LINE(-73, -76, 1376),413RSSI_TO_GRADE_LINE(-70, -74, 1548),414RSSI_TO_GRADE_LINE(-69, -72, 1750),415RSSI_TO_GRADE_LINE(-65, -68, 2064),416RSSI_TO_GRADE_LINE(-61, -66, 2294),417RSSI_TO_GRADE_LINE(-58, -61, 2580),418RSSI_TO_GRADE_LINE(-55, -58, 2868),419RSSI_TO_GRADE_LINE(-46, -55, 3098),420RSSI_TO_GRADE_LINE(-43, -54, 3442)421};422423#define MAX_GRADE (rssi_to_grade_map[ARRAY_SIZE(rssi_to_grade_map) - 1].grade)424425#define DEFAULT_CHAN_LOAD_LB 30426#define DEFAULT_CHAN_LOAD_HB 15427#define DEFAULT_CHAN_LOAD_UHB 0428429/* Factors calculation is done with fixed-point with a scaling factor of 1/256 */430#define SCALE_FACTOR 256431432/* Convert a percentage from [0,100] to [0,255] */433#define NORMALIZE_PERCENT_TO_255(percentage) ((percentage) * SCALE_FACTOR / 100)434435static unsigned int436iwl_mvm_get_puncturing_factor(const struct ieee80211_bss_conf *link_conf)437{438enum nl80211_chan_width chan_width =439link_conf->chanreq.oper.width;440int mhz = nl80211_chan_width_to_mhz(chan_width);441unsigned int n_subchannels, n_punctured, puncturing_penalty;442443if (WARN_ONCE(mhz < 20 || mhz > 320,444"Invalid channel width : (%d)\n", mhz))445return SCALE_FACTOR;446447/* No puncturing, no penalty */448if (mhz < 80)449return SCALE_FACTOR;450451/* total number of subchannels */452n_subchannels = mhz / 20;453/* how many of these are punctured */454n_punctured = hweight16(link_conf->chanreq.oper.punctured);455456puncturing_penalty = n_punctured * SCALE_FACTOR / n_subchannels;457return SCALE_FACTOR - puncturing_penalty;458}459460static unsigned int461iwl_mvm_get_chan_load(struct ieee80211_bss_conf *link_conf)462{463struct ieee80211_vif *vif = link_conf->vif;464struct iwl_mvm_vif_link_info *mvm_link =465iwl_mvm_vif_from_mac80211(link_conf->vif)->link[link_conf->link_id];466const struct element *bss_load_elem;467const struct ieee80211_bss_load_elem *bss_load;468enum nl80211_band band = link_conf->chanreq.oper.chan->band;469const struct cfg80211_bss_ies *ies;470unsigned int chan_load;471u32 chan_load_by_us;472473rcu_read_lock();474if (ieee80211_vif_link_active(vif, link_conf->link_id))475ies = rcu_dereference(link_conf->bss->beacon_ies);476else477ies = rcu_dereference(link_conf->bss->ies);478479if (ies)480bss_load_elem = cfg80211_find_elem(WLAN_EID_QBSS_LOAD,481ies->data, ies->len);482else483bss_load_elem = NULL;484485/* If there isn't BSS Load element, take the defaults */486if (!bss_load_elem ||487bss_load_elem->datalen != sizeof(*bss_load)) {488rcu_read_unlock();489switch (band) {490case NL80211_BAND_2GHZ:491chan_load = DEFAULT_CHAN_LOAD_LB;492break;493case NL80211_BAND_5GHZ:494chan_load = DEFAULT_CHAN_LOAD_HB;495break;496case NL80211_BAND_6GHZ:497chan_load = DEFAULT_CHAN_LOAD_UHB;498break;499default:500chan_load = 0;501break;502}503/* The defaults are given in percentage */504return NORMALIZE_PERCENT_TO_255(chan_load);505}506507bss_load = (const void *)bss_load_elem->data;508/* Channel util is in range 0-255 */509chan_load = bss_load->channel_util;510rcu_read_unlock();511512if (!mvm_link || !mvm_link->active)513return chan_load;514515if (WARN_ONCE(!mvm_link->phy_ctxt,516"Active link (%u) without phy ctxt assigned!\n",517link_conf->link_id))518return chan_load;519520/* channel load by us is given in percentage */521chan_load_by_us =522NORMALIZE_PERCENT_TO_255(mvm_link->phy_ctxt->channel_load_by_us);523524/* Use only values that firmware sends that can possibly be valid */525if (chan_load_by_us <= chan_load)526chan_load -= chan_load_by_us;527528return chan_load;529}530531static unsigned int532iwl_mvm_get_chan_load_factor(struct ieee80211_bss_conf *link_conf)533{534return SCALE_FACTOR - iwl_mvm_get_chan_load(link_conf);535}536537/* This function calculates the grade of a link. Returns 0 in error case */538VISIBLE_IF_IWLWIFI_KUNIT539unsigned int iwl_mvm_get_link_grade(struct ieee80211_bss_conf *link_conf)540{541enum nl80211_band band;542int i, rssi_idx;543s32 link_rssi;544unsigned int grade = MAX_GRADE;545546if (WARN_ON_ONCE(!link_conf))547return 0;548549band = link_conf->chanreq.oper.chan->band;550if (WARN_ONCE(band != NL80211_BAND_2GHZ &&551band != NL80211_BAND_5GHZ &&552band != NL80211_BAND_6GHZ,553"Invalid band (%u)\n", band))554return 0;555556link_rssi = MBM_TO_DBM(link_conf->bss->signal);557/*558* For 6 GHz the RSSI of the beacons is lower than559* the RSSI of the data.560*/561if (band == NL80211_BAND_6GHZ)562link_rssi += 4;563564rssi_idx = band == NL80211_BAND_2GHZ ? 0 : 1;565566/* No valid RSSI - take the lowest grade */567if (!link_rssi)568link_rssi = rssi_to_grade_map[0].rssi[rssi_idx];569570/* Get grade based on RSSI */571for (i = 0; i < ARRAY_SIZE(rssi_to_grade_map); i++) {572const struct iwl_mvm_rssi_to_grade *line =573&rssi_to_grade_map[i];574575if (link_rssi > line->rssi[rssi_idx])576continue;577grade = line->grade;578break;579}580581/* apply the channel load and puncturing factors */582grade = grade * iwl_mvm_get_chan_load_factor(link_conf) / SCALE_FACTOR;583grade = grade * iwl_mvm_get_puncturing_factor(link_conf) / SCALE_FACTOR;584return grade;585}586EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mvm_get_link_grade);587588static589u8 iwl_mvm_set_link_selection_data(struct ieee80211_vif *vif,590struct iwl_mvm_link_sel_data *data,591unsigned long usable_links,592u8 *best_link_idx)593{594u8 n_data = 0;595u16 max_grade = 0;596unsigned long link_id;597598/* TODO: don't select links that weren't discovered in the last scan */599for_each_set_bit(link_id, &usable_links, IEEE80211_MLD_MAX_NUM_LINKS) {600struct ieee80211_bss_conf *link_conf =601link_conf_dereference_protected(vif, link_id);602603if (WARN_ON_ONCE(!link_conf))604continue;605606data[n_data].link_id = link_id;607data[n_data].chandef = &link_conf->chanreq.oper;608data[n_data].signal = link_conf->bss->signal / 100;609data[n_data].grade = iwl_mvm_get_link_grade(link_conf);610611if (data[n_data].grade > max_grade) {612max_grade = data[n_data].grade;613*best_link_idx = n_data;614}615n_data++;616}617618return n_data;619}620621struct iwl_mvm_bw_to_rssi_threshs {622s8 low;623s8 high;624};625626#define BW_TO_RSSI_THRESHOLDS(_bw) \627[IWL_PHY_CHANNEL_MODE ## _bw] = { \628.low = IWL_MVM_LOW_RSSI_THRESH_##_bw##MHZ, \629.high = IWL_MVM_HIGH_RSSI_THRESH_##_bw##MHZ \630}631632s8 iwl_mvm_get_esr_rssi_thresh(struct iwl_mvm *mvm,633const struct cfg80211_chan_def *chandef,634bool low)635{636const struct iwl_mvm_bw_to_rssi_threshs bw_to_rssi_threshs_map[] = {637BW_TO_RSSI_THRESHOLDS(20),638BW_TO_RSSI_THRESHOLDS(40),639BW_TO_RSSI_THRESHOLDS(80),640BW_TO_RSSI_THRESHOLDS(160)641/* 320 MHz has the same thresholds as 20 MHz */642};643const struct iwl_mvm_bw_to_rssi_threshs *threshs;644u8 chan_width = iwl_mvm_get_channel_width(chandef);645646if (WARN_ON(chandef->chan->band != NL80211_BAND_2GHZ &&647chandef->chan->band != NL80211_BAND_5GHZ &&648chandef->chan->band != NL80211_BAND_6GHZ))649return S8_MAX;650651/* 6 GHz will always use 20 MHz thresholds, regardless of the BW */652if (chan_width == IWL_PHY_CHANNEL_MODE320)653chan_width = IWL_PHY_CHANNEL_MODE20;654655threshs = &bw_to_rssi_threshs_map[chan_width];656657return low ? threshs->low : threshs->high;658}659660static u32661iwl_mvm_esr_disallowed_with_link(struct iwl_mvm *mvm,662struct ieee80211_vif *vif,663const struct iwl_mvm_link_sel_data *link,664bool primary)665{666struct wiphy *wiphy = mvm->hw->wiphy;667struct ieee80211_bss_conf *conf;668enum iwl_mvm_esr_state ret = 0;669s8 thresh;670671conf = wiphy_dereference(wiphy, vif->link_conf[link->link_id]);672if (WARN_ON_ONCE(!conf))673return false;674675/* BT Coex effects eSR mode only if one of the links is on LB */676if (link->chandef->chan->band == NL80211_BAND_2GHZ &&677(!iwl_mvm_bt_coex_calculate_esr_mode(mvm, vif, link->signal,678primary)))679ret |= IWL_MVM_ESR_EXIT_COEX;680681thresh = iwl_mvm_get_esr_rssi_thresh(mvm, link->chandef,682false);683684if (link->signal < thresh)685ret |= IWL_MVM_ESR_EXIT_LOW_RSSI;686687if (conf->csa_active)688ret |= IWL_MVM_ESR_EXIT_CSA;689690if (ret) {691IWL_DEBUG_INFO(mvm,692"Link %d is not allowed for esr\n",693link->link_id);694iwl_mvm_print_esr_state(mvm, ret);695}696return ret;697}698699VISIBLE_IF_IWLWIFI_KUNIT700bool iwl_mvm_mld_valid_link_pair(struct ieee80211_vif *vif,701const struct iwl_mvm_link_sel_data *a,702const struct iwl_mvm_link_sel_data *b)703{704struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);705struct iwl_mvm *mvm = mvmvif->mvm;706enum iwl_mvm_esr_state ret = 0;707708/* Per-link considerations */709if (iwl_mvm_esr_disallowed_with_link(mvm, vif, a, true) ||710iwl_mvm_esr_disallowed_with_link(mvm, vif, b, false))711return false;712713if (a->chandef->chan->band == b->chandef->chan->band ||714a->chandef->width != b->chandef->width)715ret |= IWL_MVM_ESR_EXIT_BANDWIDTH;716717if (ret) {718IWL_DEBUG_INFO(mvm,719"Links %d and %d are not a valid pair for EMLSR\n",720a->link_id, b->link_id);721iwl_mvm_print_esr_state(mvm, ret);722return false;723}724725return true;726727}728EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mvm_mld_valid_link_pair);729730/*731* Returns the combined eSR grade of two given links.732* Returns 0 if eSR is not allowed with these 2 links.733*/734static735unsigned int iwl_mvm_get_esr_grade(struct ieee80211_vif *vif,736const struct iwl_mvm_link_sel_data *a,737const struct iwl_mvm_link_sel_data *b,738u8 *primary_id)739{740struct ieee80211_bss_conf *primary_conf;741struct wiphy *wiphy = ieee80211_vif_to_wdev(vif)->wiphy;742unsigned int primary_load;743744lockdep_assert_wiphy(wiphy);745746/* a is always primary, b is always secondary */747if (b->grade > a->grade)748swap(a, b);749750*primary_id = a->link_id;751752if (!iwl_mvm_mld_valid_link_pair(vif, a, b))753return 0;754755primary_conf = wiphy_dereference(wiphy, vif->link_conf[*primary_id]);756757if (WARN_ON_ONCE(!primary_conf))758return 0;759760primary_load = iwl_mvm_get_chan_load(primary_conf);761762return a->grade +763((b->grade * primary_load) / SCALE_FACTOR);764}765766void iwl_mvm_select_links(struct iwl_mvm *mvm, struct ieee80211_vif *vif)767{768struct iwl_mvm_link_sel_data data[IEEE80211_MLD_MAX_NUM_LINKS];769struct iwl_mvm_link_sel_data *best_link;770struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);771u32 max_active_links = iwl_mvm_max_active_links(mvm, vif);772u16 usable_links = ieee80211_vif_usable_links(vif);773u8 best, primary_link, best_in_pair, n_data;774u16 max_esr_grade = 0, new_active_links;775776lockdep_assert_wiphy(mvm->hw->wiphy);777778if (!mvmvif->authorized || !ieee80211_vif_is_mld(vif))779return;780781if (!IWL_MVM_AUTO_EML_ENABLE)782return;783784/* The logic below is a simple version that doesn't suit more than 2785* links786*/787WARN_ON_ONCE(max_active_links > 2);788789n_data = iwl_mvm_set_link_selection_data(vif, data, usable_links,790&best);791792if (WARN(!n_data, "Couldn't find a valid grade for any link!\n"))793return;794795best_link = &data[best];796primary_link = best_link->link_id;797new_active_links = BIT(best_link->link_id);798799/* eSR is not supported/blocked, or only one usable link */800if (max_active_links == 1 || !iwl_mvm_vif_has_esr_cap(mvm, vif) ||801mvmvif->esr_disable_reason || n_data == 1)802goto set_active;803804for (u8 a = 0; a < n_data; a++)805for (u8 b = a + 1; b < n_data; b++) {806u16 esr_grade = iwl_mvm_get_esr_grade(vif, &data[a],807&data[b],808&best_in_pair);809810if (esr_grade <= max_esr_grade)811continue;812813max_esr_grade = esr_grade;814primary_link = best_in_pair;815new_active_links = BIT(data[a].link_id) |816BIT(data[b].link_id);817}818819/* No valid pair was found, go with the best link */820if (hweight16(new_active_links) <= 1)821goto set_active;822823/* For equal grade - prefer EMLSR */824if (best_link->grade > max_esr_grade) {825primary_link = best_link->link_id;826new_active_links = BIT(best_link->link_id);827}828set_active:829IWL_DEBUG_INFO(mvm, "Link selection result: 0x%x. Primary = %d\n",830new_active_links, primary_link);831ieee80211_set_active_links_async(vif, new_active_links);832mvmvif->link_selection_res = new_active_links;833mvmvif->link_selection_primary = primary_link;834}835836u8 iwl_mvm_get_primary_link(struct ieee80211_vif *vif)837{838struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);839840/* relevant data is written with both locks held, so read with either */841lockdep_assert(lockdep_is_held(&mvmvif->mvm->mutex) ||842lockdep_is_held(&mvmvif->mvm->hw->wiphy->mtx));843844if (!ieee80211_vif_is_mld(vif))845return 0;846847/* In AP mode, there is no primary link */848if (vif->type == NL80211_IFTYPE_AP)849return __ffs(vif->active_links);850851if (mvmvif->esr_active &&852!WARN_ON(!(BIT(mvmvif->primary_link) & vif->active_links)))853return mvmvif->primary_link;854855return __ffs(vif->active_links);856}857858/*859* For non-MLO/single link, this will return the deflink/single active link,860* respectively861*/862u8 iwl_mvm_get_other_link(struct ieee80211_vif *vif, u8 link_id)863{864switch (hweight16(vif->active_links)) {865case 0:866return 0;867default:868WARN_ON(1);869fallthrough;870case 1:871return __ffs(vif->active_links);872case 2:873return __ffs(vif->active_links & ~BIT(link_id));874}875}876877/* Reasons that can cause esr prevention */878#define IWL_MVM_ESR_PREVENT_REASONS IWL_MVM_ESR_EXIT_MISSED_BEACON879#define IWL_MVM_PREVENT_ESR_TIMEOUT (HZ * 400)880#define IWL_MVM_ESR_PREVENT_SHORT (HZ * 300)881#define IWL_MVM_ESR_PREVENT_LONG (HZ * 600)882883static bool iwl_mvm_check_esr_prevention(struct iwl_mvm *mvm,884struct iwl_mvm_vif *mvmvif,885enum iwl_mvm_esr_state reason)886{887bool timeout_expired = time_after(jiffies,888mvmvif->last_esr_exit.ts +889IWL_MVM_PREVENT_ESR_TIMEOUT);890unsigned long delay;891892lockdep_assert_held(&mvm->mutex);893894/* Only handle reasons that can cause prevention */895if (!(reason & IWL_MVM_ESR_PREVENT_REASONS))896return false;897898/*899* Reset the counter if more than 400 seconds have passed between one900* exit and the other, or if we exited due to a different reason.901* Will also reset the counter after the long prevention is done.902*/903if (timeout_expired || mvmvif->last_esr_exit.reason != reason) {904mvmvif->exit_same_reason_count = 1;905return false;906}907908mvmvif->exit_same_reason_count++;909if (WARN_ON(mvmvif->exit_same_reason_count < 2 ||910mvmvif->exit_same_reason_count > 3))911return false;912913mvmvif->esr_disable_reason |= IWL_MVM_ESR_BLOCKED_PREVENTION;914915/*916* For the second exit, use a short prevention, and for the third one,917* use a long prevention.918*/919delay = mvmvif->exit_same_reason_count == 2 ?920IWL_MVM_ESR_PREVENT_SHORT :921IWL_MVM_ESR_PREVENT_LONG;922923IWL_DEBUG_INFO(mvm,924"Preventing EMLSR for %ld seconds due to %u exits with the reason = %s (0x%x)\n",925delay / HZ, mvmvif->exit_same_reason_count,926iwl_get_esr_state_string(reason), reason);927928wiphy_delayed_work_queue(mvm->hw->wiphy,929&mvmvif->prevent_esr_done_wk, delay);930return true;931}932933#define IWL_MVM_TRIGGER_LINK_SEL_TIME (IWL_MVM_TRIGGER_LINK_SEL_TIME_SEC * HZ)934935/* API to exit eSR mode */936void iwl_mvm_exit_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif,937enum iwl_mvm_esr_state reason,938u8 link_to_keep)939{940struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);941u16 new_active_links;942bool prevented;943944lockdep_assert_held(&mvm->mutex);945946if (!IWL_MVM_AUTO_EML_ENABLE)947return;948949/* Nothing to do */950if (!mvmvif->esr_active)951return;952953if (WARN_ON(!ieee80211_vif_is_mld(vif) || !mvmvif->authorized))954return;955956if (WARN_ON(!(vif->active_links & BIT(link_to_keep))))957link_to_keep = __ffs(vif->active_links);958959new_active_links = BIT(link_to_keep);960IWL_DEBUG_INFO(mvm,961"Exiting EMLSR. reason = %s (0x%x). Current active links=0x%x, new active links = 0x%x\n",962iwl_get_esr_state_string(reason), reason,963vif->active_links, new_active_links);964965ieee80211_set_active_links_async(vif, new_active_links);966967/* Prevent EMLSR if needed */968prevented = iwl_mvm_check_esr_prevention(mvm, mvmvif, reason);969970/* Remember why and when we exited EMLSR */971mvmvif->last_esr_exit.ts = jiffies;972mvmvif->last_esr_exit.reason = reason;973974/*975* If EMLSR is prevented now - don't try to get back to EMLSR.976* If we exited due to a blocking event, we will try to get back to977* EMLSR when the corresponding unblocking event will happen.978*/979if (prevented || reason & IWL_MVM_BLOCK_ESR_REASONS)980return;981982/* If EMLSR is not blocked - try enabling it again in 30 seconds */983wiphy_delayed_work_queue(mvm->hw->wiphy,984&mvmvif->mlo_int_scan_wk,985round_jiffies_relative(IWL_MVM_TRIGGER_LINK_SEL_TIME));986}987988void iwl_mvm_block_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif,989enum iwl_mvm_esr_state reason,990u8 link_to_keep)991{992struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);993994lockdep_assert_held(&mvm->mutex);995996if (!IWL_MVM_AUTO_EML_ENABLE)997return;998999/* This should be called only with disable reasons */1000if (WARN_ON(!(reason & IWL_MVM_BLOCK_ESR_REASONS)))1001return;10021003if (mvmvif->esr_disable_reason & reason)1004return;10051006IWL_DEBUG_INFO(mvm,1007"Blocking EMLSR mode. reason = %s (0x%x)\n",1008iwl_get_esr_state_string(reason), reason);10091010mvmvif->esr_disable_reason |= reason;10111012iwl_mvm_print_esr_state(mvm, mvmvif->esr_disable_reason);10131014iwl_mvm_exit_esr(mvm, vif, reason, link_to_keep);1015}10161017int iwl_mvm_block_esr_sync(struct iwl_mvm *mvm, struct ieee80211_vif *vif,1018enum iwl_mvm_esr_state reason)1019{1020int primary_link = iwl_mvm_get_primary_link(vif);1021int ret;10221023if (!IWL_MVM_AUTO_EML_ENABLE || !ieee80211_vif_is_mld(vif))1024return 0;10251026/* This should be called only with blocking reasons */1027if (WARN_ON(!(reason & IWL_MVM_BLOCK_ESR_REASONS)))1028return 0;10291030/* leave ESR immediately, not only async with iwl_mvm_block_esr() */1031ret = ieee80211_set_active_links(vif, BIT(primary_link));1032if (ret)1033return ret;10341035mutex_lock(&mvm->mutex);1036/* only additionally block for consistency and to avoid concurrency */1037iwl_mvm_block_esr(mvm, vif, reason, primary_link);1038mutex_unlock(&mvm->mutex);10391040return 0;1041}10421043static void iwl_mvm_esr_unblocked(struct iwl_mvm *mvm,1044struct ieee80211_vif *vif)1045{1046struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);1047bool need_new_sel = time_after(jiffies, mvmvif->last_esr_exit.ts +1048IWL_MVM_TRIGGER_LINK_SEL_TIME);10491050lockdep_assert_held(&mvm->mutex);10511052if (!ieee80211_vif_is_mld(vif) || !mvmvif->authorized ||1053mvmvif->esr_active)1054return;10551056IWL_DEBUG_INFO(mvm, "EMLSR is unblocked\n");10571058/* If we exited due to an EXIT reason, and the exit was in less than1059* 30 seconds, then a MLO scan was scheduled already.1060*/1061if (!need_new_sel &&1062!(mvmvif->last_esr_exit.reason & IWL_MVM_BLOCK_ESR_REASONS)) {1063IWL_DEBUG_INFO(mvm, "Wait for MLO scan\n");1064return;1065}10661067/*1068* If EMLSR was blocked for more than 30 seconds, or the last link1069* selection decided to not enter EMLSR, trigger a new scan.1070*/1071if (need_new_sel || hweight16(mvmvif->link_selection_res) < 2) {1072IWL_DEBUG_INFO(mvm, "Trigger MLO scan\n");1073wiphy_delayed_work_queue(mvm->hw->wiphy,1074&mvmvif->mlo_int_scan_wk, 0);1075/*1076* If EMLSR was blocked for less than 30 seconds, and the last link1077* selection decided to use EMLSR, activate EMLSR using the previous1078* link selection result.1079*/1080} else {1081IWL_DEBUG_INFO(mvm,1082"Use the latest link selection result: 0x%x\n",1083mvmvif->link_selection_res);1084ieee80211_set_active_links_async(vif,1085mvmvif->link_selection_res);1086}1087}10881089void iwl_mvm_unblock_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif,1090enum iwl_mvm_esr_state reason)1091{1092struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);10931094lockdep_assert_held(&mvm->mutex);10951096if (!IWL_MVM_AUTO_EML_ENABLE)1097return;10981099/* This should be called only with disable reasons */1100if (WARN_ON(!(reason & IWL_MVM_BLOCK_ESR_REASONS)))1101return;11021103/* No Change */1104if (!(mvmvif->esr_disable_reason & reason))1105return;11061107mvmvif->esr_disable_reason &= ~reason;11081109IWL_DEBUG_INFO(mvm,1110"Unblocking EMLSR mode. reason = %s (0x%x)\n",1111iwl_get_esr_state_string(reason), reason);1112iwl_mvm_print_esr_state(mvm, mvmvif->esr_disable_reason);11131114if (!mvmvif->esr_disable_reason)1115iwl_mvm_esr_unblocked(mvm, vif);1116}11171118void iwl_mvm_init_link(struct iwl_mvm_vif_link_info *link)1119{1120link->bcast_sta.sta_id = IWL_INVALID_STA;1121link->mcast_sta.sta_id = IWL_INVALID_STA;1122link->ap_sta_id = IWL_INVALID_STA;11231124for (int r = 0; r < NUM_IWL_MVM_SMPS_REQ; r++)1125link->smps_requests[r] =1126IEEE80211_SMPS_AUTOMATIC;1127}112811291130