Path: blob/main/sys/contrib/dev/iwlwifi/mld/mlo.c
107949 views
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause1/*2* Copyright (C) 2024-2025 Intel Corporation3*/4#include "mlo.h"5#include "phy.h"67/* Block reasons helper */8#define HANDLE_EMLSR_BLOCKED_REASONS(HOW) \9HOW(PREVENTION) \10HOW(WOWLAN) \11HOW(ROC) \12HOW(NON_BSS) \13HOW(TMP_NON_BSS) \14HOW(TPT)1516static const char *17iwl_mld_get_emlsr_blocked_string(enum iwl_mld_emlsr_blocked blocked)18{19/* Using switch without "default" will warn about missing entries */20switch (blocked) {21#define REASON_CASE(x) case IWL_MLD_EMLSR_BLOCKED_##x: return #x;22HANDLE_EMLSR_BLOCKED_REASONS(REASON_CASE)23#undef REASON_CASE24}2526return "ERROR";27}2829static void iwl_mld_print_emlsr_blocked(struct iwl_mld *mld, u32 mask)30{31#define NAME_FMT(x) "%s"32#define NAME_PR(x) (mask & IWL_MLD_EMLSR_BLOCKED_##x) ? "[" #x "]" : "",33IWL_DEBUG_INFO(mld,34"EMLSR blocked = " HANDLE_EMLSR_BLOCKED_REASONS(NAME_FMT)35" (0x%x)\n",36HANDLE_EMLSR_BLOCKED_REASONS(NAME_PR)37mask);38#undef NAME_FMT39#undef NAME_PR40}4142/* Exit reasons helper */43#define HANDLE_EMLSR_EXIT_REASONS(HOW) \44HOW(BLOCK) \45HOW(MISSED_BEACON) \46HOW(FAIL_ENTRY) \47HOW(CSA) \48HOW(EQUAL_BAND) \49HOW(LOW_RSSI) \50HOW(LINK_USAGE) \51HOW(BT_COEX) \52HOW(CHAN_LOAD) \53HOW(RFI) \54HOW(FW_REQUEST) \55HOW(INVALID)5657static const char *58iwl_mld_get_emlsr_exit_string(enum iwl_mld_emlsr_exit exit)59{60/* Using switch without "default" will warn about missing entries */61switch (exit) {62#define REASON_CASE(x) case IWL_MLD_EMLSR_EXIT_##x: return #x;63HANDLE_EMLSR_EXIT_REASONS(REASON_CASE)64#undef REASON_CASE65}6667return "ERROR";68}6970static void iwl_mld_print_emlsr_exit(struct iwl_mld *mld, u32 mask)71{72#define NAME_FMT(x) "%s"73#define NAME_PR(x) (mask & IWL_MLD_EMLSR_EXIT_##x) ? "[" #x "]" : "",74IWL_DEBUG_INFO(mld,75"EMLSR exit = " HANDLE_EMLSR_EXIT_REASONS(NAME_FMT)76" (0x%x)\n",77HANDLE_EMLSR_EXIT_REASONS(NAME_PR)78mask);79#undef NAME_FMT80#undef NAME_PR81}8283void iwl_mld_emlsr_prevent_done_wk(struct wiphy *wiphy, struct wiphy_work *wk)84{85struct iwl_mld_vif *mld_vif = container_of(wk, struct iwl_mld_vif,86emlsr.prevent_done_wk.work);87struct ieee80211_vif *vif =88container_of((void *)mld_vif, struct ieee80211_vif, drv_priv);8990if (WARN_ON(!(mld_vif->emlsr.blocked_reasons &91IWL_MLD_EMLSR_BLOCKED_PREVENTION)))92return;9394iwl_mld_unblock_emlsr(mld_vif->mld, vif,95IWL_MLD_EMLSR_BLOCKED_PREVENTION);96}9798void iwl_mld_emlsr_tmp_non_bss_done_wk(struct wiphy *wiphy,99struct wiphy_work *wk)100{101struct iwl_mld_vif *mld_vif = container_of(wk, struct iwl_mld_vif,102emlsr.tmp_non_bss_done_wk.work);103struct ieee80211_vif *vif =104container_of((void *)mld_vif, struct ieee80211_vif, drv_priv);105106if (WARN_ON(!(mld_vif->emlsr.blocked_reasons &107IWL_MLD_EMLSR_BLOCKED_TMP_NON_BSS)))108return;109110iwl_mld_unblock_emlsr(mld_vif->mld, vif,111IWL_MLD_EMLSR_BLOCKED_TMP_NON_BSS);112}113114#define IWL_MLD_TRIGGER_LINK_SEL_TIME (HZ * IWL_MLD_TRIGGER_LINK_SEL_TIME_SEC)115#define IWL_MLD_SCAN_EXPIRE_TIME (HZ * IWL_MLD_SCAN_EXPIRE_TIME_SEC)116117/* Exit reasons that can cause longer EMLSR prevention */118#define IWL_MLD_PREVENT_EMLSR_REASONS (IWL_MLD_EMLSR_EXIT_MISSED_BEACON | \119IWL_MLD_EMLSR_EXIT_LINK_USAGE | \120IWL_MLD_EMLSR_EXIT_FW_REQUEST)121#define IWL_MLD_PREVENT_EMLSR_TIMEOUT (HZ * 400)122123#define IWL_MLD_EMLSR_PREVENT_SHORT (HZ * 300)124#define IWL_MLD_EMLSR_PREVENT_LONG (HZ * 600)125126static void iwl_mld_check_emlsr_prevention(struct iwl_mld *mld,127struct iwl_mld_vif *mld_vif,128enum iwl_mld_emlsr_exit reason)129{130unsigned long delay;131132/*133* Reset the counter if more than 400 seconds have passed between one134* exit and the other, or if we exited due to a different reason.135* Will also reset the counter after the long prevention is done.136*/137if (time_after(jiffies, mld_vif->emlsr.last_exit_ts +138IWL_MLD_PREVENT_EMLSR_TIMEOUT) ||139mld_vif->emlsr.last_exit_reason != reason)140mld_vif->emlsr.exit_repeat_count = 0;141142mld_vif->emlsr.last_exit_reason = reason;143mld_vif->emlsr.last_exit_ts = jiffies;144mld_vif->emlsr.exit_repeat_count++;145146/*147* Do not add a prevention when the reason was a block. For a block,148* EMLSR will be enabled again on unblock.149*/150if (reason == IWL_MLD_EMLSR_EXIT_BLOCK)151return;152153/* Set prevention for a minimum of 30 seconds */154mld_vif->emlsr.blocked_reasons |= IWL_MLD_EMLSR_BLOCKED_PREVENTION;155delay = IWL_MLD_TRIGGER_LINK_SEL_TIME;156157/* Handle repeats for reasons that can cause long prevention */158if (mld_vif->emlsr.exit_repeat_count > 1 &&159reason & IWL_MLD_PREVENT_EMLSR_REASONS) {160if (mld_vif->emlsr.exit_repeat_count == 2)161delay = IWL_MLD_EMLSR_PREVENT_SHORT;162else163delay = IWL_MLD_EMLSR_PREVENT_LONG;164165/*166* The timeouts are chosen so that this will not happen, i.e.167* IWL_MLD_EMLSR_PREVENT_LONG > IWL_MLD_PREVENT_EMLSR_TIMEOUT168*/169WARN_ON(mld_vif->emlsr.exit_repeat_count > 3);170}171172IWL_DEBUG_INFO(mld,173"Preventing EMLSR for %ld seconds due to %u exits with the reason = %s (0x%x)\n",174delay / HZ, mld_vif->emlsr.exit_repeat_count,175iwl_mld_get_emlsr_exit_string(reason), reason);176177wiphy_delayed_work_queue(mld->wiphy,178&mld_vif->emlsr.prevent_done_wk, delay);179}180181static void iwl_mld_clear_avg_chan_load_iter(struct ieee80211_hw *hw,182struct ieee80211_chanctx_conf *ctx,183void *dat)184{185struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx);186187/* It is ok to do it for all chanctx (and not only for the ones that188* belong to the EMLSR vif) since EMLSR is not allowed if there is189* another vif.190*/191phy->avg_channel_load_not_by_us = 0;192}193194static int _iwl_mld_exit_emlsr(struct iwl_mld *mld, struct ieee80211_vif *vif,195enum iwl_mld_emlsr_exit exit, u8 link_to_keep,196bool sync)197{198struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);199u16 new_active_links;200int ret = 0;201202lockdep_assert_wiphy(mld->wiphy);203204/* On entry failure need to exit anyway, even if entered from debugfs */205if (exit != IWL_MLD_EMLSR_EXIT_FAIL_ENTRY && !IWL_MLD_AUTO_EML_ENABLE)206return 0;207208/* Ignore exit request if EMLSR is not active */209if (!iwl_mld_emlsr_active(vif))210return 0;211212if (WARN_ON(!ieee80211_vif_is_mld(vif) || !mld_vif->authorized))213return 0;214215if (WARN_ON(!(vif->active_links & BIT(link_to_keep))))216link_to_keep = __ffs(vif->active_links);217218new_active_links = BIT(link_to_keep);219IWL_DEBUG_INFO(mld,220"Exiting EMLSR. reason = %s (0x%x). Current active links=0x%x, new active links = 0x%x\n",221iwl_mld_get_emlsr_exit_string(exit), exit,222vif->active_links, new_active_links);223224if (sync)225ret = ieee80211_set_active_links(vif, new_active_links);226else227ieee80211_set_active_links_async(vif, new_active_links);228229/* Update latest exit reason and check EMLSR prevention */230iwl_mld_check_emlsr_prevention(mld, mld_vif, exit);231232/* channel_load_not_by_us is invalid when in EMLSR.233* Clear it so wrong values won't be used.234*/235ieee80211_iter_chan_contexts_atomic(mld->hw,236iwl_mld_clear_avg_chan_load_iter,237NULL);238239return ret;240}241242void iwl_mld_exit_emlsr(struct iwl_mld *mld, struct ieee80211_vif *vif,243enum iwl_mld_emlsr_exit exit, u8 link_to_keep)244{245_iwl_mld_exit_emlsr(mld, vif, exit, link_to_keep, false);246}247248static int _iwl_mld_emlsr_block(struct iwl_mld *mld, struct ieee80211_vif *vif,249enum iwl_mld_emlsr_blocked reason,250u8 link_to_keep, bool sync)251{252struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);253254lockdep_assert_wiphy(mld->wiphy);255256if (!IWL_MLD_AUTO_EML_ENABLE || !iwl_mld_vif_has_emlsr_cap(vif))257return 0;258259if (mld_vif->emlsr.blocked_reasons & reason)260return 0;261262mld_vif->emlsr.blocked_reasons |= reason;263264IWL_DEBUG_INFO(mld,265"Blocking EMLSR mode. reason = %s (0x%x)\n",266iwl_mld_get_emlsr_blocked_string(reason), reason);267iwl_mld_print_emlsr_blocked(mld, mld_vif->emlsr.blocked_reasons);268269if (reason == IWL_MLD_EMLSR_BLOCKED_TPT)270wiphy_delayed_work_cancel(mld_vif->mld->wiphy,271&mld_vif->emlsr.check_tpt_wk);272273return _iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_BLOCK,274link_to_keep, sync);275}276277void iwl_mld_block_emlsr(struct iwl_mld *mld, struct ieee80211_vif *vif,278enum iwl_mld_emlsr_blocked reason, u8 link_to_keep)279{280_iwl_mld_emlsr_block(mld, vif, reason, link_to_keep, false);281}282283int iwl_mld_block_emlsr_sync(struct iwl_mld *mld, struct ieee80211_vif *vif,284enum iwl_mld_emlsr_blocked reason, u8 link_to_keep)285{286return _iwl_mld_emlsr_block(mld, vif, reason, link_to_keep, true);287}288289#define IWL_MLD_EMLSR_BLOCKED_TMP_NON_BSS_TIMEOUT (10 * HZ)290291static void iwl_mld_vif_iter_emlsr_block_tmp_non_bss(void *_data, u8 *mac,292struct ieee80211_vif *vif)293{294struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);295int ret;296297if (!iwl_mld_vif_has_emlsr_cap(vif))298return;299300ret = iwl_mld_block_emlsr_sync(mld_vif->mld, vif,301IWL_MLD_EMLSR_BLOCKED_TMP_NON_BSS,302iwl_mld_get_primary_link(vif));303if (ret)304return;305306wiphy_delayed_work_queue(mld_vif->mld->wiphy,307&mld_vif->emlsr.tmp_non_bss_done_wk,308IWL_MLD_EMLSR_BLOCKED_TMP_NON_BSS_TIMEOUT);309}310311void iwl_mld_emlsr_block_tmp_non_bss(struct iwl_mld *mld)312{313ieee80211_iterate_active_interfaces_mtx(mld->hw,314IEEE80211_IFACE_ITER_NORMAL,315iwl_mld_vif_iter_emlsr_block_tmp_non_bss,316NULL);317}318319static void _iwl_mld_select_links(struct iwl_mld *mld,320struct ieee80211_vif *vif);321322void iwl_mld_unblock_emlsr(struct iwl_mld *mld, struct ieee80211_vif *vif,323enum iwl_mld_emlsr_blocked reason)324{325struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);326327lockdep_assert_wiphy(mld->wiphy);328329if (!IWL_MLD_AUTO_EML_ENABLE || !iwl_mld_vif_has_emlsr_cap(vif))330return;331332if (!(mld_vif->emlsr.blocked_reasons & reason))333return;334335mld_vif->emlsr.blocked_reasons &= ~reason;336337IWL_DEBUG_INFO(mld,338"Unblocking EMLSR mode. reason = %s (0x%x)\n",339iwl_mld_get_emlsr_blocked_string(reason), reason);340iwl_mld_print_emlsr_blocked(mld, mld_vif->emlsr.blocked_reasons);341342if (reason == IWL_MLD_EMLSR_BLOCKED_TPT)343wiphy_delayed_work_queue(mld_vif->mld->wiphy,344&mld_vif->emlsr.check_tpt_wk,345round_jiffies_relative(IWL_MLD_TPT_COUNT_WINDOW));346347if (mld_vif->emlsr.blocked_reasons)348return;349350IWL_DEBUG_INFO(mld, "EMLSR is unblocked\n");351iwl_mld_int_mlo_scan(mld, vif);352}353354static void355iwl_mld_vif_iter_emlsr_mode_notif(void *data, u8 *mac,356struct ieee80211_vif *vif)357{358const struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);359enum iwl_mvm_fw_esr_recommendation action;360const struct iwl_esr_mode_notif *notif = NULL;361362if (iwl_fw_lookup_notif_ver(mld_vif->mld->fw, DATA_PATH_GROUP,363ESR_MODE_NOTIF, 0) > 1) {364notif = (void *)data;365action = le32_to_cpu(notif->action);366} else {367const struct iwl_esr_mode_notif_v1 *notif_v1 = (void *)data;368369action = le32_to_cpu(notif_v1->action);370}371372if (!iwl_mld_vif_has_emlsr_cap(vif))373return;374375switch (action) {376case ESR_RECOMMEND_LEAVE:377if (notif)378IWL_DEBUG_INFO(mld_vif->mld,379"FW recommend leave reason = 0x%x\n",380le32_to_cpu(notif->leave_reason_mask));381382iwl_mld_exit_emlsr(mld_vif->mld, vif,383IWL_MLD_EMLSR_EXIT_FW_REQUEST,384iwl_mld_get_primary_link(vif));385break;386case ESR_FORCE_LEAVE:387if (notif)388IWL_DEBUG_INFO(mld_vif->mld,389"FW force leave reason = 0x%x\n",390le32_to_cpu(notif->leave_reason_mask));391fallthrough;392case ESR_RECOMMEND_ENTER:393default:394IWL_WARN(mld_vif->mld, "Unexpected EMLSR notification: %d\n",395action);396}397}398399void iwl_mld_handle_emlsr_mode_notif(struct iwl_mld *mld,400struct iwl_rx_packet *pkt)401{402ieee80211_iterate_active_interfaces_mtx(mld->hw,403IEEE80211_IFACE_ITER_NORMAL,404iwl_mld_vif_iter_emlsr_mode_notif,405pkt->data);406}407408static void409iwl_mld_vif_iter_disconnect_emlsr(void *data, u8 *mac,410struct ieee80211_vif *vif)411{412if (!iwl_mld_vif_has_emlsr_cap(vif))413return;414415ieee80211_connection_loss(vif);416}417418void iwl_mld_handle_emlsr_trans_fail_notif(struct iwl_mld *mld,419struct iwl_rx_packet *pkt)420{421const struct iwl_esr_trans_fail_notif *notif = (const void *)pkt->data;422u32 fw_link_id = le32_to_cpu(notif->link_id);423struct ieee80211_bss_conf *bss_conf =424iwl_mld_fw_id_to_link_conf(mld, fw_link_id);425426IWL_DEBUG_INFO(mld, "Failed to %s EMLSR on link %d (FW: %d), reason %d\n",427le32_to_cpu(notif->activation) ? "enter" : "exit",428bss_conf ? bss_conf->link_id : -1,429le32_to_cpu(notif->link_id),430le32_to_cpu(notif->err_code));431432if (IWL_FW_CHECK(mld, !bss_conf,433"FW reported failure to %sactivate EMLSR on a non-existing link: %d\n",434le32_to_cpu(notif->activation) ? "" : "de",435fw_link_id)) {436ieee80211_iterate_active_interfaces_mtx(437mld->hw, IEEE80211_IFACE_ITER_NORMAL,438iwl_mld_vif_iter_disconnect_emlsr, NULL);439return;440}441442/* Disconnect if we failed to deactivate a link */443if (!le32_to_cpu(notif->activation)) {444ieee80211_connection_loss(bss_conf->vif);445return;446}447448/*449* We failed to activate the second link, go back to the link specified450* by the firmware as that is the one that is still valid now.451*/452iwl_mld_exit_emlsr(mld, bss_conf->vif, IWL_MLD_EMLSR_EXIT_FAIL_ENTRY,453bss_conf->link_id);454}455456/* Active non-station link tracking */457static void iwl_mld_count_non_bss_links(void *_data, u8 *mac,458struct ieee80211_vif *vif)459{460struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);461int *count = _data;462463if (ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_STATION)464return;465466*count += iwl_mld_count_active_links(mld_vif->mld, vif);467}468469struct iwl_mld_update_emlsr_block_data {470bool block;471int result;472};473474static void475iwl_mld_vif_iter_update_emlsr_non_bss_block(void *_data, u8 *mac,476struct ieee80211_vif *vif)477{478struct iwl_mld_update_emlsr_block_data *data = _data;479struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);480int ret;481482if (data->block) {483ret = iwl_mld_block_emlsr_sync(mld_vif->mld, vif,484IWL_MLD_EMLSR_BLOCKED_NON_BSS,485iwl_mld_get_primary_link(vif));486if (ret)487data->result = ret;488} else {489iwl_mld_unblock_emlsr(mld_vif->mld, vif,490IWL_MLD_EMLSR_BLOCKED_NON_BSS);491}492}493494int iwl_mld_emlsr_check_non_bss_block(struct iwl_mld *mld,495int pending_link_changes)496{497/* An active link of a non-station vif blocks EMLSR. Upon activation498* block EMLSR on the bss vif. Upon deactivation, check if this link499* was the last non-station link active, and if so unblock the bss vif500*/501struct iwl_mld_update_emlsr_block_data block_data = {};502int count = pending_link_changes;503504/* No need to count if we are activating a non-BSS link */505if (count <= 0)506ieee80211_iterate_active_interfaces_mtx(mld->hw,507IEEE80211_IFACE_ITER_NORMAL,508iwl_mld_count_non_bss_links,509&count);510511/*512* We could skip updating it if the block change did not change (and513* pending_link_changes is non-zero).514*/515block_data.block = !!count;516517ieee80211_iterate_active_interfaces_mtx(mld->hw,518IEEE80211_IFACE_ITER_NORMAL,519iwl_mld_vif_iter_update_emlsr_non_bss_block,520&block_data);521522return block_data.result;523}524525#define EMLSR_SEC_LINK_MIN_PERC 10526#define EMLSR_MIN_TX 3000527#define EMLSR_MIN_RX 400528529void iwl_mld_emlsr_check_tpt(struct wiphy *wiphy, struct wiphy_work *wk)530{531struct iwl_mld_vif *mld_vif = container_of(wk, struct iwl_mld_vif,532emlsr.check_tpt_wk.work);533struct ieee80211_vif *vif =534container_of((void *)mld_vif, struct ieee80211_vif, drv_priv);535struct iwl_mld *mld = mld_vif->mld;536struct iwl_mld_sta *mld_sta;537struct iwl_mld_link *sec_link;538unsigned long total_tx = 0, total_rx = 0;539unsigned long sec_link_tx = 0, sec_link_rx = 0;540u8 sec_link_tx_perc, sec_link_rx_perc;541s8 sec_link_id;542543if (!iwl_mld_vif_has_emlsr_cap(vif) || !mld_vif->ap_sta)544return;545546mld_sta = iwl_mld_sta_from_mac80211(mld_vif->ap_sta);547548/* We only count for the AP sta in a MLO connection */549if (!mld_sta->mpdu_counters)550return;551552/* This wk should only run when the TPT blocker isn't set.553* When the blocker is set, the decision to remove it, as well as554* clearing the counters is done in DP (to avoid having a wk every555* 5 seconds when idle. When the blocker is unset, we are not idle anyway)556*/557if (WARN_ON(mld_vif->emlsr.blocked_reasons & IWL_MLD_EMLSR_BLOCKED_TPT))558return;559/*560* TPT is unblocked, need to check if the TPT criteria is still met.561*562* If EMLSR is active for at least 5 seconds, then we also563* need to check the secondary link requirements.564*/565if (iwl_mld_emlsr_active(vif) &&566time_is_before_jiffies(mld_vif->emlsr.last_entry_ts +567IWL_MLD_TPT_COUNT_WINDOW)) {568sec_link_id = iwl_mld_get_other_link(vif, iwl_mld_get_primary_link(vif));569sec_link = iwl_mld_link_dereference_check(mld_vif, sec_link_id);570if (WARN_ON_ONCE(!sec_link))571return;572/* We need the FW ID here */573sec_link_id = sec_link->fw_id;574} else {575sec_link_id = -1;576}577578/* Sum up RX and TX MPDUs from the different queues/links */579for (int q = 0; q < mld->trans->info.num_rxqs; q++) {580struct iwl_mld_per_q_mpdu_counter *queue_counter =581&mld_sta->mpdu_counters[q];582583spin_lock_bh(&queue_counter->lock);584585/* The link IDs that doesn't exist will contain 0 */586for (int link = 0;587link < ARRAY_SIZE(queue_counter->per_link);588link++) {589total_tx += queue_counter->per_link[link].tx;590total_rx += queue_counter->per_link[link].rx;591}592593if (sec_link_id != -1) {594sec_link_tx += queue_counter->per_link[sec_link_id].tx;595sec_link_rx += queue_counter->per_link[sec_link_id].rx;596}597598memset(queue_counter->per_link, 0,599sizeof(queue_counter->per_link));600601spin_unlock_bh(&queue_counter->lock);602}603604IWL_DEBUG_INFO(mld, "total Tx MPDUs: %ld. total Rx MPDUs: %ld\n",605total_tx, total_rx);606607/* If we don't have enough MPDUs - exit EMLSR */608if (total_tx < IWL_MLD_ENTER_EMLSR_TPT_THRESH &&609total_rx < IWL_MLD_ENTER_EMLSR_TPT_THRESH) {610iwl_mld_block_emlsr(mld, vif, IWL_MLD_EMLSR_BLOCKED_TPT,611iwl_mld_get_primary_link(vif));612return;613}614615/* EMLSR is not active */616if (sec_link_id == -1)617return;618619IWL_DEBUG_INFO(mld, "Secondary Link %d: Tx MPDUs: %ld. Rx MPDUs: %ld\n",620sec_link_id, sec_link_tx, sec_link_rx);621622/* Calculate the percentage of the secondary link TX/RX */623sec_link_tx_perc = total_tx ? sec_link_tx * 100 / total_tx : 0;624sec_link_rx_perc = total_rx ? sec_link_rx * 100 / total_rx : 0;625626/*627* The TX/RX percentage is checked only if it exceeds the required628* minimum. In addition, RX is checked only if the TX check failed.629*/630if ((total_tx > EMLSR_MIN_TX &&631sec_link_tx_perc < EMLSR_SEC_LINK_MIN_PERC) ||632(total_rx > EMLSR_MIN_RX &&633sec_link_rx_perc < EMLSR_SEC_LINK_MIN_PERC)) {634iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_LINK_USAGE,635iwl_mld_get_primary_link(vif));636return;637}638639/* Check again when the next window ends */640wiphy_delayed_work_queue(mld_vif->mld->wiphy,641&mld_vif->emlsr.check_tpt_wk,642round_jiffies_relative(IWL_MLD_TPT_COUNT_WINDOW));643}644645void iwl_mld_emlsr_unblock_tpt_wk(struct wiphy *wiphy, struct wiphy_work *wk)646{647struct iwl_mld_vif *mld_vif = container_of(wk, struct iwl_mld_vif,648emlsr.unblock_tpt_wk);649struct ieee80211_vif *vif =650container_of((void *)mld_vif, struct ieee80211_vif, drv_priv);651652iwl_mld_unblock_emlsr(mld_vif->mld, vif, IWL_MLD_EMLSR_BLOCKED_TPT);653}654655/*656* Link selection657*/658659s8 iwl_mld_get_emlsr_rssi_thresh(struct iwl_mld *mld,660const struct cfg80211_chan_def *chandef,661bool low)662{663if (WARN_ON(chandef->chan->band != NL80211_BAND_2GHZ &&664chandef->chan->band != NL80211_BAND_5GHZ &&665chandef->chan->band != NL80211_BAND_6GHZ))666return S8_MAX;667668#define RSSI_THRESHOLD(_low, _bw) \669(_low) ? IWL_MLD_LOW_RSSI_THRESH_##_bw##MHZ \670: IWL_MLD_HIGH_RSSI_THRESH_##_bw##MHZ671672switch (chandef->width) {673case NL80211_CHAN_WIDTH_20_NOHT:674case NL80211_CHAN_WIDTH_20:675/* 320 MHz has the same thresholds as 20 MHz */676case NL80211_CHAN_WIDTH_320:677return RSSI_THRESHOLD(low, 20);678case NL80211_CHAN_WIDTH_40:679return RSSI_THRESHOLD(low, 40);680case NL80211_CHAN_WIDTH_80:681return RSSI_THRESHOLD(low, 80);682case NL80211_CHAN_WIDTH_160:683return RSSI_THRESHOLD(low, 160);684default:685WARN_ON(1);686return S8_MAX;687}688#undef RSSI_THRESHOLD689}690691static u32692iwl_mld_emlsr_disallowed_with_link(struct iwl_mld *mld,693struct ieee80211_vif *vif,694struct iwl_mld_link_sel_data *link,695bool primary)696{697struct wiphy *wiphy = mld->wiphy;698struct ieee80211_bss_conf *conf;699u32 ret = 0;700701conf = wiphy_dereference(wiphy, vif->link_conf[link->link_id]);702if (WARN_ON_ONCE(!conf))703return IWL_MLD_EMLSR_EXIT_INVALID;704705if (link->chandef->chan->band == NL80211_BAND_2GHZ && mld->bt_is_active)706ret |= IWL_MLD_EMLSR_EXIT_BT_COEX;707708if (link->signal <709iwl_mld_get_emlsr_rssi_thresh(mld, link->chandef, false))710ret |= IWL_MLD_EMLSR_EXIT_LOW_RSSI;711712if (conf->csa_active)713ret |= IWL_MLD_EMLSR_EXIT_CSA;714715if (ret) {716IWL_DEBUG_INFO(mld,717"Link %d is not allowed for EMLSR as %s\n",718link->link_id,719primary ? "primary" : "secondary");720iwl_mld_print_emlsr_exit(mld, ret);721}722723return ret;724}725726static u8727iwl_mld_set_link_sel_data(struct iwl_mld *mld,728struct ieee80211_vif *vif,729struct iwl_mld_link_sel_data *data,730unsigned long usable_links,731u8 *best_link_idx)732{733u8 n_data = 0;734u16 max_grade = 0;735unsigned long link_id;736737/*738* TODO: don't select links that weren't discovered in the last scan739* This requires mac80211 (or cfg80211) changes to forward/track when740* a BSS was last updated. cfg80211 already tracks this information but741* it is not exposed within the kernel.742*/743for_each_set_bit(link_id, &usable_links, IEEE80211_MLD_MAX_NUM_LINKS) {744struct ieee80211_bss_conf *link_conf =745link_conf_dereference_protected(vif, link_id);746747if (WARN_ON_ONCE(!link_conf))748continue;749750/* Ignore any BSS that was not seen in the last MLO scan */751if (ktime_before(link_conf->bss->ts_boottime,752mld->scan.last_mlo_scan_time))753continue;754755data[n_data].link_id = link_id;756data[n_data].chandef = &link_conf->chanreq.oper;757data[n_data].signal = MBM_TO_DBM(link_conf->bss->signal);758data[n_data].grade = iwl_mld_get_link_grade(mld, link_conf);759760if (n_data == 0 || data[n_data].grade > max_grade) {761max_grade = data[n_data].grade;762*best_link_idx = n_data;763}764n_data++;765}766767return n_data;768}769770static u32771iwl_mld_get_min_chan_load_thresh(struct ieee80211_chanctx_conf *chanctx)772{773const struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(chanctx);774775switch (phy->chandef.width) {776case NL80211_CHAN_WIDTH_320:777case NL80211_CHAN_WIDTH_160:778return 5;779case NL80211_CHAN_WIDTH_80:780return 7;781default:782break;783}784return 10;785}786787static bool788iwl_mld_channel_load_allows_emlsr(struct iwl_mld *mld,789struct ieee80211_vif *vif,790const struct iwl_mld_link_sel_data *a,791const struct iwl_mld_link_sel_data *b)792{793struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);794struct iwl_mld_link *link_a =795iwl_mld_link_dereference_check(mld_vif, a->link_id);796struct ieee80211_chanctx_conf *chanctx_a = NULL;797u32 bw_a, bw_b, ratio;798u32 primary_load_perc;799800if (!link_a || !link_a->active) {801IWL_DEBUG_EHT(mld, "Primary link is not active. Can't enter EMLSR\n");802return false;803}804805chanctx_a = wiphy_dereference(mld->wiphy, link_a->chan_ctx);806807if (WARN_ON(!chanctx_a))808return false;809810primary_load_perc =811iwl_mld_phy_from_mac80211(chanctx_a)->avg_channel_load_not_by_us;812813IWL_DEBUG_EHT(mld, "Average channel load not by us: %u\n", primary_load_perc);814815if (primary_load_perc < iwl_mld_get_min_chan_load_thresh(chanctx_a)) {816IWL_DEBUG_EHT(mld, "Channel load is below the minimum threshold\n");817return false;818}819820if (iwl_mld_vif_low_latency(mld_vif)) {821IWL_DEBUG_EHT(mld, "Low latency vif, EMLSR is allowed\n");822return true;823}824825if (a->chandef->width <= b->chandef->width)826return true;827828bw_a = cfg80211_chandef_get_width(a->chandef);829bw_b = cfg80211_chandef_get_width(b->chandef);830ratio = bw_a / bw_b;831832switch (ratio) {833case 2:834return primary_load_perc > 25;835case 4:836return primary_load_perc > 40;837case 8:838case 16:839return primary_load_perc > 50;840}841842return false;843}844845VISIBLE_IF_IWLWIFI_KUNIT u32846iwl_mld_emlsr_pair_state(struct ieee80211_vif *vif,847struct iwl_mld_link_sel_data *a,848struct iwl_mld_link_sel_data *b)849{850struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);851struct iwl_mld *mld = mld_vif->mld;852u32 reason_mask = 0;853854/* Per-link considerations */855reason_mask = iwl_mld_emlsr_disallowed_with_link(mld, vif, a, true);856if (reason_mask)857return reason_mask;858859reason_mask = iwl_mld_emlsr_disallowed_with_link(mld, vif, b, false);860if (reason_mask)861return reason_mask;862863if (a->chandef->chan->band == b->chandef->chan->band) {864const struct cfg80211_chan_def *c_low = a->chandef;865const struct cfg80211_chan_def *c_high = b->chandef;866u32 c_low_upper_edge, c_high_lower_edge;867868if (c_low->chan->center_freq > c_high->chan->center_freq)869swap(c_low, c_high);870871c_low_upper_edge = c_low->chan->center_freq +872cfg80211_chandef_get_width(c_low) / 2;873c_high_lower_edge = c_high->chan->center_freq -874cfg80211_chandef_get_width(c_high) / 2;875876if (a->chandef->chan->band == NL80211_BAND_5GHZ &&877c_low_upper_edge <= 5330 && c_high_lower_edge >= 5490) {878/* This case is fine - HW/FW can deal with it, there's879* enough separation between the two channels.880*/881} else {882reason_mask |= IWL_MLD_EMLSR_EXIT_EQUAL_BAND;883}884}885if (!iwl_mld_channel_load_allows_emlsr(mld, vif, a, b))886reason_mask |= IWL_MLD_EMLSR_EXIT_CHAN_LOAD;887888if (reason_mask) {889IWL_DEBUG_INFO(mld,890"Links %d and %d are not a valid pair for EMLSR\n",891a->link_id, b->link_id);892IWL_DEBUG_INFO(mld,893"Links bandwidth are: %d and %d\n",894nl80211_chan_width_to_mhz(a->chandef->width),895nl80211_chan_width_to_mhz(b->chandef->width));896iwl_mld_print_emlsr_exit(mld, reason_mask);897}898899return reason_mask;900}901EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_emlsr_pair_state);902903/* Calculation is done with fixed-point with a scaling factor of 1/256 */904#define SCALE_FACTOR 256905906/*907* Returns the combined grade of two given links.908* Returns 0 if EMLSR is not allowed with these 2 links.909*/910static911unsigned int iwl_mld_get_emlsr_grade(struct iwl_mld *mld,912struct ieee80211_vif *vif,913struct iwl_mld_link_sel_data *a,914struct iwl_mld_link_sel_data *b,915u8 *primary_id)916{917struct ieee80211_bss_conf *primary_conf;918struct wiphy *wiphy = ieee80211_vif_to_wdev(vif)->wiphy;919unsigned int primary_load;920921lockdep_assert_wiphy(wiphy);922923/* a is always primary, b is always secondary */924if (b->grade > a->grade)925swap(a, b);926927*primary_id = a->link_id;928929if (iwl_mld_emlsr_pair_state(vif, a, b))930return 0;931932primary_conf = wiphy_dereference(wiphy, vif->link_conf[*primary_id]);933934if (WARN_ON_ONCE(!primary_conf))935return 0;936937primary_load = iwl_mld_get_chan_load(mld, primary_conf);938939/* The more the primary link is loaded, the more worthwhile EMLSR becomes */940return a->grade + ((b->grade * primary_load) / SCALE_FACTOR);941}942943static void _iwl_mld_select_links(struct iwl_mld *mld,944struct ieee80211_vif *vif)945{946struct iwl_mld_link_sel_data data[IEEE80211_MLD_MAX_NUM_LINKS];947struct iwl_mld_link_sel_data *best_link;948struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);949int max_active_links = iwl_mld_max_active_links(mld, vif);950u16 new_active, usable_links = ieee80211_vif_usable_links(vif);951u8 best_idx, new_primary, n_data;952u16 max_grade;953954lockdep_assert_wiphy(mld->wiphy);955956if (!mld_vif->authorized || hweight16(usable_links) <= 1)957return;958959if (WARN(ktime_before(mld->scan.last_mlo_scan_time,960ktime_sub_ns(ktime_get_boottime_ns(),9615ULL * NSEC_PER_SEC)),962"Last MLO scan was too long ago, can't select links\n"))963return;964965/* The logic below is simple and not suited for more than 2 links */966WARN_ON_ONCE(max_active_links > 2);967968n_data = iwl_mld_set_link_sel_data(mld, vif, data, usable_links,969&best_idx);970971if (!n_data) {972IWL_DEBUG_EHT(mld,973"Couldn't find a valid grade for any link!\n");974return;975}976977/* Default to selecting the single best link */978best_link = &data[best_idx];979new_primary = best_link->link_id;980new_active = BIT(best_link->link_id);981max_grade = best_link->grade;982983/* If EMLSR is not possible, activate the best link */984if (max_active_links == 1 || n_data == 1 ||985!iwl_mld_vif_has_emlsr_cap(vif) || !IWL_MLD_AUTO_EML_ENABLE ||986mld_vif->emlsr.blocked_reasons)987goto set_active;988989/* Try to find the best link combination */990for (u8 a = 0; a < n_data; a++) {991for (u8 b = a + 1; b < n_data; b++) {992u8 best_in_pair;993u16 emlsr_grade =994iwl_mld_get_emlsr_grade(mld, vif,995&data[a], &data[b],996&best_in_pair);997998/*999* Prefer (new) EMLSR combination to prefer EMLSR over1000* a single link.1001*/1002if (emlsr_grade < max_grade)1003continue;10041005max_grade = emlsr_grade;1006new_primary = best_in_pair;1007new_active = BIT(data[a].link_id) |1008BIT(data[b].link_id);1009}1010}10111012set_active:1013IWL_DEBUG_INFO(mld, "Link selection result: 0x%x. Primary = %d\n",1014new_active, new_primary);10151016mld_vif->emlsr.selected_primary = new_primary;1017mld_vif->emlsr.selected_links = new_active;10181019ieee80211_set_active_links_async(vif, new_active);1020}10211022static void iwl_mld_vif_iter_select_links(void *_data, u8 *mac,1023struct ieee80211_vif *vif)1024{1025struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);1026struct iwl_mld *mld = mld_vif->mld;10271028_iwl_mld_select_links(mld, vif);1029}10301031void iwl_mld_select_links(struct iwl_mld *mld)1032{1033ieee80211_iterate_active_interfaces_mtx(mld->hw,1034IEEE80211_IFACE_ITER_NORMAL,1035iwl_mld_vif_iter_select_links,1036NULL);1037}10381039static void iwl_mld_emlsr_check_bt_iter(void *_data, u8 *mac,1040struct ieee80211_vif *vif)1041{1042struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);1043struct iwl_mld *mld = mld_vif->mld;1044struct ieee80211_bss_conf *link;1045unsigned int link_id;10461047if (!iwl_mld_vif_has_emlsr_cap(vif))1048return;10491050if (!mld->bt_is_active) {1051iwl_mld_retry_emlsr(mld, vif);1052return;1053}10541055/* BT is turned ON but we are not in EMLSR, nothing to do */1056if (!iwl_mld_emlsr_active(vif))1057return;10581059/* In EMLSR and BT is turned ON */10601061for_each_vif_active_link(vif, link, link_id) {1062if (WARN_ON(!link->chanreq.oper.chan))1063continue;10641065if (link->chanreq.oper.chan->band == NL80211_BAND_2GHZ) {1066iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_BT_COEX,1067iwl_mld_get_primary_link(vif));1068return;1069}1070}1071}10721073void iwl_mld_emlsr_check_bt(struct iwl_mld *mld)1074{1075ieee80211_iterate_active_interfaces_mtx(mld->hw,1076IEEE80211_IFACE_ITER_NORMAL,1077iwl_mld_emlsr_check_bt_iter,1078NULL);1079}10801081struct iwl_mld_chan_load_data {1082struct iwl_mld_phy *phy;1083u32 prev_chan_load_not_by_us;1084};10851086static void iwl_mld_chan_load_update_iter(void *_data, u8 *mac,1087struct ieee80211_vif *vif)1088{1089struct iwl_mld_chan_load_data *data = _data;1090const struct iwl_mld_phy *phy = data->phy;1091struct ieee80211_chanctx_conf *chanctx =1092container_of((const void *)phy, struct ieee80211_chanctx_conf,1093drv_priv);1094struct iwl_mld *mld = iwl_mld_vif_from_mac80211(vif)->mld;1095struct ieee80211_bss_conf *prim_link;1096unsigned int prim_link_id;10971098prim_link_id = iwl_mld_get_primary_link(vif);1099prim_link = link_conf_dereference_protected(vif, prim_link_id);11001101if (WARN_ON(!prim_link))1102return;11031104if (chanctx != rcu_access_pointer(prim_link->chanctx_conf))1105return;11061107if (iwl_mld_emlsr_active(vif)) {1108int chan_load = iwl_mld_get_chan_load_by_others(mld, prim_link,1109true);11101111if (chan_load < 0)1112return;11131114/* chan_load is in range [0,255] */1115if (chan_load < NORMALIZE_PERCENT_TO_255(IWL_MLD_EXIT_EMLSR_CHAN_LOAD))1116iwl_mld_exit_emlsr(mld, vif,1117IWL_MLD_EMLSR_EXIT_CHAN_LOAD,1118prim_link_id);1119} else {1120u32 old_chan_load = data->prev_chan_load_not_by_us;1121u32 new_chan_load = phy->avg_channel_load_not_by_us;1122u32 min_thresh = iwl_mld_get_min_chan_load_thresh(chanctx);11231124#define THRESHOLD_CROSSED(threshold) \1125(old_chan_load <= (threshold) && new_chan_load > (threshold))11261127if (THRESHOLD_CROSSED(min_thresh) || THRESHOLD_CROSSED(25) ||1128THRESHOLD_CROSSED(40) || THRESHOLD_CROSSED(50))1129iwl_mld_retry_emlsr(mld, vif);1130#undef THRESHOLD_CROSSED1131}1132}11331134void iwl_mld_emlsr_check_chan_load(struct ieee80211_hw *hw,1135struct iwl_mld_phy *phy,1136u32 prev_chan_load_not_by_us)1137{1138struct iwl_mld_chan_load_data data = {1139.phy = phy,1140.prev_chan_load_not_by_us = prev_chan_load_not_by_us,1141};11421143ieee80211_iterate_active_interfaces_mtx(hw,1144IEEE80211_IFACE_ITER_NORMAL,1145iwl_mld_chan_load_update_iter,1146&data);1147}11481149void iwl_mld_retry_emlsr(struct iwl_mld *mld, struct ieee80211_vif *vif)1150{1151struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);11521153if (!IWL_MLD_AUTO_EML_ENABLE || !iwl_mld_vif_has_emlsr_cap(vif) ||1154iwl_mld_emlsr_active(vif) || mld_vif->emlsr.blocked_reasons)1155return;11561157iwl_mld_int_mlo_scan(mld, vif);1158}11591160static void iwl_mld_ignore_tpt_iter(void *data, u8 *mac,1161struct ieee80211_vif *vif)1162{1163struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);1164struct iwl_mld *mld = mld_vif->mld;1165struct iwl_mld_sta *mld_sta;1166bool *start = (void *)data;11671168/* check_tpt_wk is only used when TPT block isn't set */1169if (mld_vif->emlsr.blocked_reasons & IWL_MLD_EMLSR_BLOCKED_TPT ||1170!IWL_MLD_AUTO_EML_ENABLE || !mld_vif->ap_sta)1171return;11721173mld_sta = iwl_mld_sta_from_mac80211(mld_vif->ap_sta);11741175/* We only count for the AP sta in a MLO connection */1176if (!mld_sta->mpdu_counters)1177return;11781179if (*start) {1180wiphy_delayed_work_cancel(mld_vif->mld->wiphy,1181&mld_vif->emlsr.check_tpt_wk);1182IWL_DEBUG_EHT(mld, "TPT check disabled\n");1183return;1184}11851186/* Clear the counters so we start from the beginning */1187for (int q = 0; q < mld->trans->info.num_rxqs; q++) {1188struct iwl_mld_per_q_mpdu_counter *queue_counter =1189&mld_sta->mpdu_counters[q];11901191spin_lock_bh(&queue_counter->lock);11921193memset(queue_counter->per_link, 0,1194sizeof(queue_counter->per_link));11951196spin_unlock_bh(&queue_counter->lock);1197}11981199/* Schedule the check in 5 seconds */1200wiphy_delayed_work_queue(mld_vif->mld->wiphy,1201&mld_vif->emlsr.check_tpt_wk,1202round_jiffies_relative(IWL_MLD_TPT_COUNT_WINDOW));1203IWL_DEBUG_EHT(mld, "TPT check enabled\n");1204}12051206void iwl_mld_start_ignoring_tpt_updates(struct iwl_mld *mld)1207{1208bool start = true;12091210ieee80211_iterate_active_interfaces_mtx(mld->hw,1211IEEE80211_IFACE_ITER_NORMAL,1212iwl_mld_ignore_tpt_iter,1213&start);1214}12151216void iwl_mld_stop_ignoring_tpt_updates(struct iwl_mld *mld)1217{1218bool start = false;12191220ieee80211_iterate_active_interfaces_mtx(mld->hw,1221IEEE80211_IFACE_ITER_NORMAL,1222iwl_mld_ignore_tpt_iter,1223&start);1224}122512261227