| // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
| /* |
| * Copyright (C) 2024-2025 Intel Corporation |
| */ |
| #include "mlo.h" |
| #include "phy.h" |
| |
| /* Block reasons helper */ |
| #define HANDLE_EMLSR_BLOCKED_REASONS(HOW) \ |
| HOW(PREVENTION) \ |
| HOW(WOWLAN) \ |
| HOW(ROC) \ |
| HOW(NON_BSS) \ |
| HOW(TMP_NON_BSS) \ |
| HOW(TPT) |
| |
| static const char * |
| iwl_mld_get_emlsr_blocked_string(enum iwl_mld_emlsr_blocked blocked) |
| { |
| /* Using switch without "default" will warn about missing entries */ |
| switch (blocked) { |
| #define REASON_CASE(x) case IWL_MLD_EMLSR_BLOCKED_##x: return #x; |
| HANDLE_EMLSR_BLOCKED_REASONS(REASON_CASE) |
| #undef REASON_CASE |
| } |
| |
| return "ERROR"; |
| } |
| |
| static void iwl_mld_print_emlsr_blocked(struct iwl_mld *mld, u32 mask) |
| { |
| #define NAME_FMT(x) "%s" |
| #define NAME_PR(x) (mask & IWL_MLD_EMLSR_BLOCKED_##x) ? "[" #x "]" : "", |
| IWL_DEBUG_INFO(mld, |
| "EMLSR blocked = " HANDLE_EMLSR_BLOCKED_REASONS(NAME_FMT) |
| " (0x%x)\n", |
| HANDLE_EMLSR_BLOCKED_REASONS(NAME_PR) |
| mask); |
| #undef NAME_FMT |
| #undef NAME_PR |
| } |
| |
| /* Exit reasons helper */ |
| #define HANDLE_EMLSR_EXIT_REASONS(HOW) \ |
| HOW(BLOCK) \ |
| HOW(MISSED_BEACON) \ |
| HOW(FAIL_ENTRY) \ |
| HOW(CSA) \ |
| HOW(EQUAL_BAND) \ |
| HOW(LOW_RSSI) \ |
| HOW(LINK_USAGE) \ |
| HOW(BT_COEX) \ |
| HOW(CHAN_LOAD) \ |
| HOW(RFI) \ |
| HOW(FW_REQUEST) \ |
| HOW(INVALID) |
| |
| static const char * |
| iwl_mld_get_emlsr_exit_string(enum iwl_mld_emlsr_exit exit) |
| { |
| /* Using switch without "default" will warn about missing entries */ |
| switch (exit) { |
| #define REASON_CASE(x) case IWL_MLD_EMLSR_EXIT_##x: return #x; |
| HANDLE_EMLSR_EXIT_REASONS(REASON_CASE) |
| #undef REASON_CASE |
| } |
| |
| return "ERROR"; |
| } |
| |
| static void iwl_mld_print_emlsr_exit(struct iwl_mld *mld, u32 mask) |
| { |
| #define NAME_FMT(x) "%s" |
| #define NAME_PR(x) (mask & IWL_MLD_EMLSR_EXIT_##x) ? "[" #x "]" : "", |
| IWL_DEBUG_INFO(mld, |
| "EMLSR exit = " HANDLE_EMLSR_EXIT_REASONS(NAME_FMT) |
| " (0x%x)\n", |
| HANDLE_EMLSR_EXIT_REASONS(NAME_PR) |
| mask); |
| #undef NAME_FMT |
| #undef NAME_PR |
| } |
| |
| void iwl_mld_emlsr_prevent_done_wk(struct wiphy *wiphy, struct wiphy_work *wk) |
| { |
| struct iwl_mld_vif *mld_vif = container_of(wk, struct iwl_mld_vif, |
| emlsr.prevent_done_wk.work); |
| struct ieee80211_vif *vif = |
| container_of((void *)mld_vif, struct ieee80211_vif, drv_priv); |
| |
| if (WARN_ON(!(mld_vif->emlsr.blocked_reasons & |
| IWL_MLD_EMLSR_BLOCKED_PREVENTION))) |
| return; |
| |
| iwl_mld_unblock_emlsr(mld_vif->mld, vif, |
| IWL_MLD_EMLSR_BLOCKED_PREVENTION); |
| } |
| |
| void iwl_mld_emlsr_tmp_non_bss_done_wk(struct wiphy *wiphy, |
| struct wiphy_work *wk) |
| { |
| struct iwl_mld_vif *mld_vif = container_of(wk, struct iwl_mld_vif, |
| emlsr.tmp_non_bss_done_wk.work); |
| struct ieee80211_vif *vif = |
| container_of((void *)mld_vif, struct ieee80211_vif, drv_priv); |
| |
| if (WARN_ON(!(mld_vif->emlsr.blocked_reasons & |
| IWL_MLD_EMLSR_BLOCKED_TMP_NON_BSS))) |
| return; |
| |
| iwl_mld_unblock_emlsr(mld_vif->mld, vif, |
| IWL_MLD_EMLSR_BLOCKED_TMP_NON_BSS); |
| } |
| |
| #define IWL_MLD_TRIGGER_LINK_SEL_TIME (HZ * IWL_MLD_TRIGGER_LINK_SEL_TIME_SEC) |
| #define IWL_MLD_SCAN_EXPIRE_TIME (HZ * IWL_MLD_SCAN_EXPIRE_TIME_SEC) |
| |
| /* Exit reasons that can cause longer EMLSR prevention */ |
| #define IWL_MLD_PREVENT_EMLSR_REASONS (IWL_MLD_EMLSR_EXIT_MISSED_BEACON | \ |
| IWL_MLD_EMLSR_EXIT_LINK_USAGE | \ |
| IWL_MLD_EMLSR_EXIT_FW_REQUEST) |
| #define IWL_MLD_PREVENT_EMLSR_TIMEOUT (HZ * 400) |
| |
| #define IWL_MLD_EMLSR_PREVENT_SHORT (HZ * 300) |
| #define IWL_MLD_EMLSR_PREVENT_LONG (HZ * 600) |
| |
| static void iwl_mld_check_emlsr_prevention(struct iwl_mld *mld, |
| struct iwl_mld_vif *mld_vif, |
| enum iwl_mld_emlsr_exit reason) |
| { |
| unsigned long delay; |
| |
| /* |
| * Reset the counter if more than 400 seconds have passed between one |
| * exit and the other, or if we exited due to a different reason. |
| * Will also reset the counter after the long prevention is done. |
| */ |
| if (time_after(jiffies, mld_vif->emlsr.last_exit_ts + |
| IWL_MLD_PREVENT_EMLSR_TIMEOUT) || |
| mld_vif->emlsr.last_exit_reason != reason) |
| mld_vif->emlsr.exit_repeat_count = 0; |
| |
| mld_vif->emlsr.last_exit_reason = reason; |
| mld_vif->emlsr.last_exit_ts = jiffies; |
| mld_vif->emlsr.exit_repeat_count++; |
| |
| /* |
| * Do not add a prevention when the reason was a block. For a block, |
| * EMLSR will be enabled again on unblock. |
| */ |
| if (reason == IWL_MLD_EMLSR_EXIT_BLOCK) |
| return; |
| |
| /* Set prevention for a minimum of 30 seconds */ |
| mld_vif->emlsr.blocked_reasons |= IWL_MLD_EMLSR_BLOCKED_PREVENTION; |
| delay = IWL_MLD_TRIGGER_LINK_SEL_TIME; |
| |
| /* Handle repeats for reasons that can cause long prevention */ |
| if (mld_vif->emlsr.exit_repeat_count > 1 && |
| reason & IWL_MLD_PREVENT_EMLSR_REASONS) { |
| if (mld_vif->emlsr.exit_repeat_count == 2) |
| delay = IWL_MLD_EMLSR_PREVENT_SHORT; |
| else |
| delay = IWL_MLD_EMLSR_PREVENT_LONG; |
| |
| /* |
| * The timeouts are chosen so that this will not happen, i.e. |
| * IWL_MLD_EMLSR_PREVENT_LONG > IWL_MLD_PREVENT_EMLSR_TIMEOUT |
| */ |
| WARN_ON(mld_vif->emlsr.exit_repeat_count > 3); |
| } |
| |
| IWL_DEBUG_INFO(mld, |
| "Preventing EMLSR for %ld seconds due to %u exits with the reason = %s (0x%x)\n", |
| delay / HZ, mld_vif->emlsr.exit_repeat_count, |
| iwl_mld_get_emlsr_exit_string(reason), reason); |
| |
| wiphy_delayed_work_queue(mld->wiphy, |
| &mld_vif->emlsr.prevent_done_wk, delay); |
| } |
| |
| static void iwl_mld_clear_avg_chan_load_iter(struct ieee80211_hw *hw, |
| struct ieee80211_chanctx_conf *ctx, |
| void *dat) |
| { |
| struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx); |
| |
| /* It is ok to do it for all chanctx (and not only for the ones that |
| * belong to the EMLSR vif) since EMLSR is not allowed if there is |
| * another vif. |
| */ |
| phy->avg_channel_load_not_by_us = 0; |
| } |
| |
| static int _iwl_mld_exit_emlsr(struct iwl_mld *mld, struct ieee80211_vif *vif, |
| enum iwl_mld_emlsr_exit exit, u8 link_to_keep, |
| bool sync) |
| { |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| u16 new_active_links; |
| int ret = 0; |
| |
| lockdep_assert_wiphy(mld->wiphy); |
| |
| /* On entry failure need to exit anyway, even if entered from debugfs */ |
| if (exit != IWL_MLD_EMLSR_EXIT_FAIL_ENTRY && !IWL_MLD_AUTO_EML_ENABLE) |
| return 0; |
| |
| /* Ignore exit request if EMLSR is not active */ |
| if (!iwl_mld_emlsr_active(vif)) |
| return 0; |
| |
| if (WARN_ON(!ieee80211_vif_is_mld(vif) || !mld_vif->authorized)) |
| return 0; |
| |
| if (WARN_ON(!(vif->active_links & BIT(link_to_keep)))) |
| link_to_keep = __ffs(vif->active_links); |
| |
| new_active_links = BIT(link_to_keep); |
| IWL_DEBUG_INFO(mld, |
| "Exiting EMLSR. reason = %s (0x%x). Current active links=0x%x, new active links = 0x%x\n", |
| iwl_mld_get_emlsr_exit_string(exit), exit, |
| vif->active_links, new_active_links); |
| |
| if (sync) |
| ret = ieee80211_set_active_links(vif, new_active_links); |
| else |
| ieee80211_set_active_links_async(vif, new_active_links); |
| |
| /* Update latest exit reason and check EMLSR prevention */ |
| iwl_mld_check_emlsr_prevention(mld, mld_vif, exit); |
| |
| /* channel_load_not_by_us is invalid when in EMLSR. |
| * Clear it so wrong values won't be used. |
| */ |
| ieee80211_iter_chan_contexts_atomic(mld->hw, |
| iwl_mld_clear_avg_chan_load_iter, |
| NULL); |
| |
| return ret; |
| } |
| |
| void iwl_mld_exit_emlsr(struct iwl_mld *mld, struct ieee80211_vif *vif, |
| enum iwl_mld_emlsr_exit exit, u8 link_to_keep) |
| { |
| _iwl_mld_exit_emlsr(mld, vif, exit, link_to_keep, false); |
| } |
| |
| static int _iwl_mld_emlsr_block(struct iwl_mld *mld, struct ieee80211_vif *vif, |
| enum iwl_mld_emlsr_blocked reason, |
| u8 link_to_keep, bool sync) |
| { |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| |
| lockdep_assert_wiphy(mld->wiphy); |
| |
| if (!IWL_MLD_AUTO_EML_ENABLE || !iwl_mld_vif_has_emlsr_cap(vif)) |
| return 0; |
| |
| if (mld_vif->emlsr.blocked_reasons & reason) |
| return 0; |
| |
| mld_vif->emlsr.blocked_reasons |= reason; |
| |
| IWL_DEBUG_INFO(mld, |
| "Blocking EMLSR mode. reason = %s (0x%x)\n", |
| iwl_mld_get_emlsr_blocked_string(reason), reason); |
| iwl_mld_print_emlsr_blocked(mld, mld_vif->emlsr.blocked_reasons); |
| |
| if (reason == IWL_MLD_EMLSR_BLOCKED_TPT) |
| wiphy_delayed_work_cancel(mld_vif->mld->wiphy, |
| &mld_vif->emlsr.check_tpt_wk); |
| |
| return _iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_BLOCK, |
| link_to_keep, sync); |
| } |
| |
| void iwl_mld_block_emlsr(struct iwl_mld *mld, struct ieee80211_vif *vif, |
| enum iwl_mld_emlsr_blocked reason, u8 link_to_keep) |
| { |
| _iwl_mld_emlsr_block(mld, vif, reason, link_to_keep, false); |
| } |
| |
| int iwl_mld_block_emlsr_sync(struct iwl_mld *mld, struct ieee80211_vif *vif, |
| enum iwl_mld_emlsr_blocked reason, u8 link_to_keep) |
| { |
| return _iwl_mld_emlsr_block(mld, vif, reason, link_to_keep, true); |
| } |
| |
| #define IWL_MLD_EMLSR_BLOCKED_TMP_NON_BSS_TIMEOUT (10 * HZ) |
| |
| static void iwl_mld_vif_iter_emlsr_block_tmp_non_bss(void *_data, u8 *mac, |
| struct ieee80211_vif *vif) |
| { |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| int ret; |
| |
| if (!iwl_mld_vif_has_emlsr_cap(vif)) |
| return; |
| |
| ret = iwl_mld_block_emlsr_sync(mld_vif->mld, vif, |
| IWL_MLD_EMLSR_BLOCKED_TMP_NON_BSS, |
| iwl_mld_get_primary_link(vif)); |
| if (ret) |
| return; |
| |
| wiphy_delayed_work_queue(mld_vif->mld->wiphy, |
| &mld_vif->emlsr.tmp_non_bss_done_wk, |
| IWL_MLD_EMLSR_BLOCKED_TMP_NON_BSS_TIMEOUT); |
| } |
| |
| void iwl_mld_emlsr_block_tmp_non_bss(struct iwl_mld *mld) |
| { |
| ieee80211_iterate_active_interfaces_mtx(mld->hw, |
| IEEE80211_IFACE_ITER_NORMAL, |
| iwl_mld_vif_iter_emlsr_block_tmp_non_bss, |
| NULL); |
| } |
| |
| static void _iwl_mld_select_links(struct iwl_mld *mld, |
| struct ieee80211_vif *vif); |
| |
| void iwl_mld_unblock_emlsr(struct iwl_mld *mld, struct ieee80211_vif *vif, |
| enum iwl_mld_emlsr_blocked reason) |
| { |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| |
| lockdep_assert_wiphy(mld->wiphy); |
| |
| if (!IWL_MLD_AUTO_EML_ENABLE || !iwl_mld_vif_has_emlsr_cap(vif)) |
| return; |
| |
| if (!(mld_vif->emlsr.blocked_reasons & reason)) |
| return; |
| |
| mld_vif->emlsr.blocked_reasons &= ~reason; |
| |
| IWL_DEBUG_INFO(mld, |
| "Unblocking EMLSR mode. reason = %s (0x%x)\n", |
| iwl_mld_get_emlsr_blocked_string(reason), reason); |
| iwl_mld_print_emlsr_blocked(mld, mld_vif->emlsr.blocked_reasons); |
| |
| if (reason == IWL_MLD_EMLSR_BLOCKED_TPT) |
| wiphy_delayed_work_queue(mld_vif->mld->wiphy, |
| &mld_vif->emlsr.check_tpt_wk, |
| round_jiffies_relative(IWL_MLD_TPT_COUNT_WINDOW)); |
| |
| if (mld_vif->emlsr.blocked_reasons) |
| return; |
| |
| IWL_DEBUG_INFO(mld, "EMLSR is unblocked\n"); |
| iwl_mld_int_mlo_scan(mld, vif); |
| } |
| |
| static void |
| iwl_mld_vif_iter_emlsr_mode_notif(void *data, u8 *mac, |
| struct ieee80211_vif *vif) |
| { |
| const struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| enum iwl_mvm_fw_esr_recommendation action; |
| const struct iwl_esr_mode_notif *notif = NULL; |
| |
| if (iwl_fw_lookup_notif_ver(mld_vif->mld->fw, DATA_PATH_GROUP, |
| ESR_MODE_NOTIF, 0) > 1) { |
| notif = (void *)data; |
| action = le32_to_cpu(notif->action); |
| } else { |
| const struct iwl_esr_mode_notif_v1 *notif_v1 = (void *)data; |
| |
| action = le32_to_cpu(notif_v1->action); |
| } |
| |
| if (!iwl_mld_vif_has_emlsr_cap(vif)) |
| return; |
| |
| switch (action) { |
| case ESR_RECOMMEND_LEAVE: |
| if (notif) |
| IWL_DEBUG_INFO(mld_vif->mld, |
| "FW recommend leave reason = 0x%x\n", |
| le32_to_cpu(notif->leave_reason_mask)); |
| |
| iwl_mld_exit_emlsr(mld_vif->mld, vif, |
| IWL_MLD_EMLSR_EXIT_FW_REQUEST, |
| iwl_mld_get_primary_link(vif)); |
| break; |
| case ESR_FORCE_LEAVE: |
| if (notif) |
| IWL_DEBUG_INFO(mld_vif->mld, |
| "FW force leave reason = 0x%x\n", |
| le32_to_cpu(notif->leave_reason_mask)); |
| fallthrough; |
| case ESR_RECOMMEND_ENTER: |
| default: |
| IWL_WARN(mld_vif->mld, "Unexpected EMLSR notification: %d\n", |
| action); |
| } |
| } |
| |
| void iwl_mld_handle_emlsr_mode_notif(struct iwl_mld *mld, |
| struct iwl_rx_packet *pkt) |
| { |
| ieee80211_iterate_active_interfaces_mtx(mld->hw, |
| IEEE80211_IFACE_ITER_NORMAL, |
| iwl_mld_vif_iter_emlsr_mode_notif, |
| pkt->data); |
| } |
| |
| static void |
| iwl_mld_vif_iter_disconnect_emlsr(void *data, u8 *mac, |
| struct ieee80211_vif *vif) |
| { |
| if (!iwl_mld_vif_has_emlsr_cap(vif)) |
| return; |
| |
| ieee80211_connection_loss(vif); |
| } |
| |
| void iwl_mld_handle_emlsr_trans_fail_notif(struct iwl_mld *mld, |
| struct iwl_rx_packet *pkt) |
| { |
| const struct iwl_esr_trans_fail_notif *notif = (const void *)pkt->data; |
| u32 fw_link_id = le32_to_cpu(notif->link_id); |
| struct ieee80211_bss_conf *bss_conf = |
| iwl_mld_fw_id_to_link_conf(mld, fw_link_id); |
| |
| IWL_DEBUG_INFO(mld, "Failed to %s EMLSR on link %d (FW: %d), reason %d\n", |
| le32_to_cpu(notif->activation) ? "enter" : "exit", |
| bss_conf ? bss_conf->link_id : -1, |
| le32_to_cpu(notif->link_id), |
| le32_to_cpu(notif->err_code)); |
| |
| if (IWL_FW_CHECK(mld, !bss_conf, |
| "FW reported failure to %sactivate EMLSR on a non-existing link: %d\n", |
| le32_to_cpu(notif->activation) ? "" : "de", |
| fw_link_id)) { |
| ieee80211_iterate_active_interfaces_mtx( |
| mld->hw, IEEE80211_IFACE_ITER_NORMAL, |
| iwl_mld_vif_iter_disconnect_emlsr, NULL); |
| return; |
| } |
| |
| /* Disconnect if we failed to deactivate a link */ |
| if (!le32_to_cpu(notif->activation)) { |
| ieee80211_connection_loss(bss_conf->vif); |
| return; |
| } |
| |
| /* |
| * We failed to activate the second link, go back to the link specified |
| * by the firmware as that is the one that is still valid now. |
| */ |
| iwl_mld_exit_emlsr(mld, bss_conf->vif, IWL_MLD_EMLSR_EXIT_FAIL_ENTRY, |
| bss_conf->link_id); |
| } |
| |
| /* Active non-station link tracking */ |
| static void iwl_mld_count_non_bss_links(void *_data, u8 *mac, |
| struct ieee80211_vif *vif) |
| { |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| int *count = _data; |
| |
| if (ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_STATION) |
| return; |
| |
| *count += iwl_mld_count_active_links(mld_vif->mld, vif); |
| } |
| |
| struct iwl_mld_update_emlsr_block_data { |
| bool block; |
| int result; |
| }; |
| |
| static void |
| iwl_mld_vif_iter_update_emlsr_non_bss_block(void *_data, u8 *mac, |
| struct ieee80211_vif *vif) |
| { |
| struct iwl_mld_update_emlsr_block_data *data = _data; |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| int ret; |
| |
| if (data->block) { |
| ret = iwl_mld_block_emlsr_sync(mld_vif->mld, vif, |
| IWL_MLD_EMLSR_BLOCKED_NON_BSS, |
| iwl_mld_get_primary_link(vif)); |
| if (ret) |
| data->result = ret; |
| } else { |
| iwl_mld_unblock_emlsr(mld_vif->mld, vif, |
| IWL_MLD_EMLSR_BLOCKED_NON_BSS); |
| } |
| } |
| |
| int iwl_mld_emlsr_check_non_bss_block(struct iwl_mld *mld, |
| int pending_link_changes) |
| { |
| /* An active link of a non-station vif blocks EMLSR. Upon activation |
| * block EMLSR on the bss vif. Upon deactivation, check if this link |
| * was the last non-station link active, and if so unblock the bss vif |
| */ |
| struct iwl_mld_update_emlsr_block_data block_data = {}; |
| int count = pending_link_changes; |
| |
| /* No need to count if we are activating a non-BSS link */ |
| if (count <= 0) |
| ieee80211_iterate_active_interfaces_mtx(mld->hw, |
| IEEE80211_IFACE_ITER_NORMAL, |
| iwl_mld_count_non_bss_links, |
| &count); |
| |
| /* |
| * We could skip updating it if the block change did not change (and |
| * pending_link_changes is non-zero). |
| */ |
| block_data.block = !!count; |
| |
| ieee80211_iterate_active_interfaces_mtx(mld->hw, |
| IEEE80211_IFACE_ITER_NORMAL, |
| iwl_mld_vif_iter_update_emlsr_non_bss_block, |
| &block_data); |
| |
| return block_data.result; |
| } |
| |
| #define EMLSR_SEC_LINK_MIN_PERC 10 |
| #define EMLSR_MIN_TX 3000 |
| #define EMLSR_MIN_RX 400 |
| |
| void iwl_mld_emlsr_check_tpt(struct wiphy *wiphy, struct wiphy_work *wk) |
| { |
| struct iwl_mld_vif *mld_vif = container_of(wk, struct iwl_mld_vif, |
| emlsr.check_tpt_wk.work); |
| struct ieee80211_vif *vif = |
| container_of((void *)mld_vif, struct ieee80211_vif, drv_priv); |
| struct iwl_mld *mld = mld_vif->mld; |
| struct iwl_mld_sta *mld_sta; |
| struct iwl_mld_link *sec_link; |
| unsigned long total_tx = 0, total_rx = 0; |
| unsigned long sec_link_tx = 0, sec_link_rx = 0; |
| u8 sec_link_tx_perc, sec_link_rx_perc; |
| s8 sec_link_id; |
| |
| if (!iwl_mld_vif_has_emlsr_cap(vif) || !mld_vif->ap_sta) |
| return; |
| |
| mld_sta = iwl_mld_sta_from_mac80211(mld_vif->ap_sta); |
| |
| /* We only count for the AP sta in a MLO connection */ |
| if (!mld_sta->mpdu_counters) |
| return; |
| |
| /* This wk should only run when the TPT blocker isn't set. |
| * When the blocker is set, the decision to remove it, as well as |
| * clearing the counters is done in DP (to avoid having a wk every |
| * 5 seconds when idle. When the blocker is unset, we are not idle anyway) |
| */ |
| if (WARN_ON(mld_vif->emlsr.blocked_reasons & IWL_MLD_EMLSR_BLOCKED_TPT)) |
| return; |
| /* |
| * TPT is unblocked, need to check if the TPT criteria is still met. |
| * |
| * If EMLSR is active for at least 5 seconds, then we also |
| * need to check the secondary link requirements. |
| */ |
| if (iwl_mld_emlsr_active(vif) && |
| time_is_before_jiffies(mld_vif->emlsr.last_entry_ts + |
| IWL_MLD_TPT_COUNT_WINDOW)) { |
| sec_link_id = iwl_mld_get_other_link(vif, iwl_mld_get_primary_link(vif)); |
| sec_link = iwl_mld_link_dereference_check(mld_vif, sec_link_id); |
| if (WARN_ON_ONCE(!sec_link)) |
| return; |
| /* We need the FW ID here */ |
| sec_link_id = sec_link->fw_id; |
| } else { |
| sec_link_id = -1; |
| } |
| |
| /* Sum up RX and TX MPDUs from the different queues/links */ |
| for (int q = 0; q < mld->trans->info.num_rxqs; q++) { |
| struct iwl_mld_per_q_mpdu_counter *queue_counter = |
| &mld_sta->mpdu_counters[q]; |
| |
| spin_lock_bh(&queue_counter->lock); |
| |
| /* The link IDs that doesn't exist will contain 0 */ |
| for (int link = 0; |
| link < ARRAY_SIZE(queue_counter->per_link); |
| link++) { |
| total_tx += queue_counter->per_link[link].tx; |
| total_rx += queue_counter->per_link[link].rx; |
| } |
| |
| if (sec_link_id != -1) { |
| sec_link_tx += queue_counter->per_link[sec_link_id].tx; |
| sec_link_rx += queue_counter->per_link[sec_link_id].rx; |
| } |
| |
| memset(queue_counter->per_link, 0, |
| sizeof(queue_counter->per_link)); |
| |
| spin_unlock_bh(&queue_counter->lock); |
| } |
| |
| IWL_DEBUG_INFO(mld, "total Tx MPDUs: %ld. total Rx MPDUs: %ld\n", |
| total_tx, total_rx); |
| |
| /* If we don't have enough MPDUs - exit EMLSR */ |
| if (total_tx < IWL_MLD_ENTER_EMLSR_TPT_THRESH && |
| total_rx < IWL_MLD_ENTER_EMLSR_TPT_THRESH) { |
| iwl_mld_block_emlsr(mld, vif, IWL_MLD_EMLSR_BLOCKED_TPT, |
| iwl_mld_get_primary_link(vif)); |
| return; |
| } |
| |
| /* EMLSR is not active */ |
| if (sec_link_id == -1) |
| return; |
| |
| IWL_DEBUG_INFO(mld, "Secondary Link %d: Tx MPDUs: %ld. Rx MPDUs: %ld\n", |
| sec_link_id, sec_link_tx, sec_link_rx); |
| |
| /* Calculate the percentage of the secondary link TX/RX */ |
| sec_link_tx_perc = total_tx ? sec_link_tx * 100 / total_tx : 0; |
| sec_link_rx_perc = total_rx ? sec_link_rx * 100 / total_rx : 0; |
| |
| /* |
| * The TX/RX percentage is checked only if it exceeds the required |
| * minimum. In addition, RX is checked only if the TX check failed. |
| */ |
| if ((total_tx > EMLSR_MIN_TX && |
| sec_link_tx_perc < EMLSR_SEC_LINK_MIN_PERC) || |
| (total_rx > EMLSR_MIN_RX && |
| sec_link_rx_perc < EMLSR_SEC_LINK_MIN_PERC)) { |
| iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_LINK_USAGE, |
| iwl_mld_get_primary_link(vif)); |
| return; |
| } |
| |
| /* Check again when the next window ends */ |
| wiphy_delayed_work_queue(mld_vif->mld->wiphy, |
| &mld_vif->emlsr.check_tpt_wk, |
| round_jiffies_relative(IWL_MLD_TPT_COUNT_WINDOW)); |
| } |
| |
| void iwl_mld_emlsr_unblock_tpt_wk(struct wiphy *wiphy, struct wiphy_work *wk) |
| { |
| struct iwl_mld_vif *mld_vif = container_of(wk, struct iwl_mld_vif, |
| emlsr.unblock_tpt_wk); |
| struct ieee80211_vif *vif = |
| container_of((void *)mld_vif, struct ieee80211_vif, drv_priv); |
| |
| iwl_mld_unblock_emlsr(mld_vif->mld, vif, IWL_MLD_EMLSR_BLOCKED_TPT); |
| } |
| |
| /* |
| * Link selection |
| */ |
| |
| s8 iwl_mld_get_emlsr_rssi_thresh(struct iwl_mld *mld, |
| const struct cfg80211_chan_def *chandef, |
| bool low) |
| { |
| if (WARN_ON(chandef->chan->band != NL80211_BAND_2GHZ && |
| chandef->chan->band != NL80211_BAND_5GHZ && |
| chandef->chan->band != NL80211_BAND_6GHZ)) |
| return S8_MAX; |
| |
| #define RSSI_THRESHOLD(_low, _bw) \ |
| (_low) ? IWL_MLD_LOW_RSSI_THRESH_##_bw##MHZ \ |
| : IWL_MLD_HIGH_RSSI_THRESH_##_bw##MHZ |
| |
| switch (chandef->width) { |
| case NL80211_CHAN_WIDTH_20_NOHT: |
| case NL80211_CHAN_WIDTH_20: |
| /* 320 MHz has the same thresholds as 20 MHz */ |
| case NL80211_CHAN_WIDTH_320: |
| return RSSI_THRESHOLD(low, 20); |
| case NL80211_CHAN_WIDTH_40: |
| return RSSI_THRESHOLD(low, 40); |
| case NL80211_CHAN_WIDTH_80: |
| return RSSI_THRESHOLD(low, 80); |
| case NL80211_CHAN_WIDTH_160: |
| return RSSI_THRESHOLD(low, 160); |
| default: |
| WARN_ON(1); |
| return S8_MAX; |
| } |
| #undef RSSI_THRESHOLD |
| } |
| |
| static u32 |
| iwl_mld_emlsr_disallowed_with_link(struct iwl_mld *mld, |
| struct ieee80211_vif *vif, |
| struct iwl_mld_link_sel_data *link, |
| bool primary) |
| { |
| struct wiphy *wiphy = mld->wiphy; |
| struct ieee80211_bss_conf *conf; |
| u32 ret = 0; |
| |
| conf = wiphy_dereference(wiphy, vif->link_conf[link->link_id]); |
| if (WARN_ON_ONCE(!conf)) |
| return IWL_MLD_EMLSR_EXIT_INVALID; |
| |
| if (link->chandef->chan->band == NL80211_BAND_2GHZ && mld->bt_is_active) |
| ret |= IWL_MLD_EMLSR_EXIT_BT_COEX; |
| |
| if (link->signal < |
| iwl_mld_get_emlsr_rssi_thresh(mld, link->chandef, false)) |
| ret |= IWL_MLD_EMLSR_EXIT_LOW_RSSI; |
| |
| if (conf->csa_active) |
| ret |= IWL_MLD_EMLSR_EXIT_CSA; |
| |
| if (ret) { |
| IWL_DEBUG_INFO(mld, |
| "Link %d is not allowed for EMLSR as %s\n", |
| link->link_id, |
| primary ? "primary" : "secondary"); |
| iwl_mld_print_emlsr_exit(mld, ret); |
| } |
| |
| return ret; |
| } |
| |
| static u8 |
| iwl_mld_set_link_sel_data(struct iwl_mld *mld, |
| struct ieee80211_vif *vif, |
| struct iwl_mld_link_sel_data *data, |
| unsigned long usable_links, |
| u8 *best_link_idx) |
| { |
| u8 n_data = 0; |
| u16 max_grade = 0; |
| unsigned long link_id; |
| |
| /* |
| * TODO: don't select links that weren't discovered in the last scan |
| * This requires mac80211 (or cfg80211) changes to forward/track when |
| * a BSS was last updated. cfg80211 already tracks this information but |
| * it is not exposed within the kernel. |
| */ |
| for_each_set_bit(link_id, &usable_links, IEEE80211_MLD_MAX_NUM_LINKS) { |
| struct ieee80211_bss_conf *link_conf = |
| link_conf_dereference_protected(vif, link_id); |
| |
| if (WARN_ON_ONCE(!link_conf)) |
| continue; |
| |
| /* Ignore any BSS that was not seen in the last MLO scan */ |
| if (ktime_before(link_conf->bss->ts_boottime, |
| mld->scan.last_mlo_scan_time)) |
| continue; |
| |
| data[n_data].link_id = link_id; |
| data[n_data].chandef = &link_conf->chanreq.oper; |
| data[n_data].signal = MBM_TO_DBM(link_conf->bss->signal); |
| data[n_data].grade = iwl_mld_get_link_grade(mld, link_conf); |
| |
| if (n_data == 0 || data[n_data].grade > max_grade) { |
| max_grade = data[n_data].grade; |
| *best_link_idx = n_data; |
| } |
| n_data++; |
| } |
| |
| return n_data; |
| } |
| |
| static u32 |
| iwl_mld_get_min_chan_load_thresh(struct ieee80211_chanctx_conf *chanctx) |
| { |
| const struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(chanctx); |
| |
| switch (phy->chandef.width) { |
| case NL80211_CHAN_WIDTH_320: |
| case NL80211_CHAN_WIDTH_160: |
| return 5; |
| case NL80211_CHAN_WIDTH_80: |
| return 7; |
| default: |
| break; |
| } |
| return 10; |
| } |
| |
| static bool |
| iwl_mld_channel_load_allows_emlsr(struct iwl_mld *mld, |
| struct ieee80211_vif *vif, |
| const struct iwl_mld_link_sel_data *a, |
| const struct iwl_mld_link_sel_data *b) |
| { |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| struct iwl_mld_link *link_a = |
| iwl_mld_link_dereference_check(mld_vif, a->link_id); |
| struct ieee80211_chanctx_conf *chanctx_a = NULL; |
| u32 bw_a, bw_b, ratio; |
| u32 primary_load_perc; |
| |
| if (!link_a || !link_a->active) { |
| IWL_DEBUG_EHT(mld, "Primary link is not active. Can't enter EMLSR\n"); |
| return false; |
| } |
| |
| chanctx_a = wiphy_dereference(mld->wiphy, link_a->chan_ctx); |
| |
| if (WARN_ON(!chanctx_a)) |
| return false; |
| |
| primary_load_perc = |
| iwl_mld_phy_from_mac80211(chanctx_a)->avg_channel_load_not_by_us; |
| |
| IWL_DEBUG_EHT(mld, "Average channel load not by us: %u\n", primary_load_perc); |
| |
| if (primary_load_perc < iwl_mld_get_min_chan_load_thresh(chanctx_a)) { |
| IWL_DEBUG_EHT(mld, "Channel load is below the minimum threshold\n"); |
| return false; |
| } |
| |
| if (iwl_mld_vif_low_latency(mld_vif)) { |
| IWL_DEBUG_EHT(mld, "Low latency vif, EMLSR is allowed\n"); |
| return true; |
| } |
| |
| if (a->chandef->width <= b->chandef->width) |
| return true; |
| |
| bw_a = cfg80211_chandef_get_width(a->chandef); |
| bw_b = cfg80211_chandef_get_width(b->chandef); |
| ratio = bw_a / bw_b; |
| |
| switch (ratio) { |
| case 2: |
| return primary_load_perc > 25; |
| case 4: |
| return primary_load_perc > 40; |
| case 8: |
| case 16: |
| return primary_load_perc > 50; |
| } |
| |
| return false; |
| } |
| |
| VISIBLE_IF_IWLWIFI_KUNIT u32 |
| iwl_mld_emlsr_pair_state(struct ieee80211_vif *vif, |
| struct iwl_mld_link_sel_data *a, |
| struct iwl_mld_link_sel_data *b) |
| { |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| struct iwl_mld *mld = mld_vif->mld; |
| u32 reason_mask = 0; |
| |
| /* Per-link considerations */ |
| reason_mask = iwl_mld_emlsr_disallowed_with_link(mld, vif, a, true); |
| if (reason_mask) |
| return reason_mask; |
| |
| reason_mask = iwl_mld_emlsr_disallowed_with_link(mld, vif, b, false); |
| if (reason_mask) |
| return reason_mask; |
| |
| if (a->chandef->chan->band == b->chandef->chan->band) { |
| const struct cfg80211_chan_def *c_low = a->chandef; |
| const struct cfg80211_chan_def *c_high = b->chandef; |
| u32 c_low_upper_edge, c_high_lower_edge; |
| |
| if (c_low->chan->center_freq > c_high->chan->center_freq) |
| swap(c_low, c_high); |
| |
| c_low_upper_edge = c_low->chan->center_freq + |
| cfg80211_chandef_get_width(c_low) / 2; |
| c_high_lower_edge = c_high->chan->center_freq - |
| cfg80211_chandef_get_width(c_high) / 2; |
| |
| if (a->chandef->chan->band == NL80211_BAND_5GHZ && |
| c_low_upper_edge <= 5330 && c_high_lower_edge >= 5490) { |
| /* This case is fine - HW/FW can deal with it, there's |
| * enough separation between the two channels. |
| */ |
| } else { |
| reason_mask |= IWL_MLD_EMLSR_EXIT_EQUAL_BAND; |
| } |
| } |
| if (!iwl_mld_channel_load_allows_emlsr(mld, vif, a, b)) |
| reason_mask |= IWL_MLD_EMLSR_EXIT_CHAN_LOAD; |
| |
| if (reason_mask) { |
| IWL_DEBUG_INFO(mld, |
| "Links %d and %d are not a valid pair for EMLSR\n", |
| a->link_id, b->link_id); |
| IWL_DEBUG_INFO(mld, |
| "Links bandwidth are: %d and %d\n", |
| nl80211_chan_width_to_mhz(a->chandef->width), |
| nl80211_chan_width_to_mhz(b->chandef->width)); |
| iwl_mld_print_emlsr_exit(mld, reason_mask); |
| } |
| |
| return reason_mask; |
| } |
| EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_emlsr_pair_state); |
| |
| /* Calculation is done with fixed-point with a scaling factor of 1/256 */ |
| #define SCALE_FACTOR 256 |
| |
| /* |
| * Returns the combined grade of two given links. |
| * Returns 0 if EMLSR is not allowed with these 2 links. |
| */ |
| static |
| unsigned int iwl_mld_get_emlsr_grade(struct iwl_mld *mld, |
| struct ieee80211_vif *vif, |
| struct iwl_mld_link_sel_data *a, |
| struct iwl_mld_link_sel_data *b, |
| u8 *primary_id) |
| { |
| struct ieee80211_bss_conf *primary_conf; |
| struct wiphy *wiphy = ieee80211_vif_to_wdev(vif)->wiphy; |
| unsigned int primary_load; |
| |
| lockdep_assert_wiphy(wiphy); |
| |
| /* a is always primary, b is always secondary */ |
| if (b->grade > a->grade) |
| swap(a, b); |
| |
| *primary_id = a->link_id; |
| |
| if (iwl_mld_emlsr_pair_state(vif, a, b)) |
| return 0; |
| |
| primary_conf = wiphy_dereference(wiphy, vif->link_conf[*primary_id]); |
| |
| if (WARN_ON_ONCE(!primary_conf)) |
| return 0; |
| |
| primary_load = iwl_mld_get_chan_load(mld, primary_conf); |
| |
| /* The more the primary link is loaded, the more worthwhile EMLSR becomes */ |
| return a->grade + ((b->grade * primary_load) / SCALE_FACTOR); |
| } |
| |
| static void _iwl_mld_select_links(struct iwl_mld *mld, |
| struct ieee80211_vif *vif) |
| { |
| struct iwl_mld_link_sel_data data[IEEE80211_MLD_MAX_NUM_LINKS]; |
| struct iwl_mld_link_sel_data *best_link; |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| int max_active_links = iwl_mld_max_active_links(mld, vif); |
| u16 new_active, usable_links = ieee80211_vif_usable_links(vif); |
| u8 best_idx, new_primary, n_data; |
| u16 max_grade; |
| |
| lockdep_assert_wiphy(mld->wiphy); |
| |
| if (!mld_vif->authorized || hweight16(usable_links) <= 1) |
| return; |
| |
| if (WARN(ktime_before(mld->scan.last_mlo_scan_time, |
| ktime_sub_ns(ktime_get_boottime_ns(), |
| 5ULL * NSEC_PER_SEC)), |
| "Last MLO scan was too long ago, can't select links\n")) |
| return; |
| |
| /* The logic below is simple and not suited for more than 2 links */ |
| WARN_ON_ONCE(max_active_links > 2); |
| |
| n_data = iwl_mld_set_link_sel_data(mld, vif, data, usable_links, |
| &best_idx); |
| |
| if (!n_data) { |
| IWL_DEBUG_EHT(mld, |
| "Couldn't find a valid grade for any link!\n"); |
| return; |
| } |
| |
| /* Default to selecting the single best link */ |
| best_link = &data[best_idx]; |
| new_primary = best_link->link_id; |
| new_active = BIT(best_link->link_id); |
| max_grade = best_link->grade; |
| |
| /* If EMLSR is not possible, activate the best link */ |
| if (max_active_links == 1 || n_data == 1 || |
| !iwl_mld_vif_has_emlsr_cap(vif) || !IWL_MLD_AUTO_EML_ENABLE || |
| mld_vif->emlsr.blocked_reasons) |
| goto set_active; |
| |
| /* Try to find the best link combination */ |
| for (u8 a = 0; a < n_data; a++) { |
| for (u8 b = a + 1; b < n_data; b++) { |
| u8 best_in_pair; |
| u16 emlsr_grade = |
| iwl_mld_get_emlsr_grade(mld, vif, |
| &data[a], &data[b], |
| &best_in_pair); |
| |
| /* |
| * Prefer (new) EMLSR combination to prefer EMLSR over |
| * a single link. |
| */ |
| if (emlsr_grade < max_grade) |
| continue; |
| |
| max_grade = emlsr_grade; |
| new_primary = best_in_pair; |
| new_active = BIT(data[a].link_id) | |
| BIT(data[b].link_id); |
| } |
| } |
| |
| set_active: |
| IWL_DEBUG_INFO(mld, "Link selection result: 0x%x. Primary = %d\n", |
| new_active, new_primary); |
| |
| mld_vif->emlsr.selected_primary = new_primary; |
| mld_vif->emlsr.selected_links = new_active; |
| |
| ieee80211_set_active_links_async(vif, new_active); |
| } |
| |
| static void iwl_mld_vif_iter_select_links(void *_data, u8 *mac, |
| struct ieee80211_vif *vif) |
| { |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| struct iwl_mld *mld = mld_vif->mld; |
| |
| _iwl_mld_select_links(mld, vif); |
| } |
| |
| void iwl_mld_select_links(struct iwl_mld *mld) |
| { |
| ieee80211_iterate_active_interfaces_mtx(mld->hw, |
| IEEE80211_IFACE_ITER_NORMAL, |
| iwl_mld_vif_iter_select_links, |
| NULL); |
| } |
| |
| static void iwl_mld_emlsr_check_bt_iter(void *_data, u8 *mac, |
| struct ieee80211_vif *vif) |
| { |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| struct iwl_mld *mld = mld_vif->mld; |
| struct ieee80211_bss_conf *link; |
| unsigned int link_id; |
| |
| if (!iwl_mld_vif_has_emlsr_cap(vif)) |
| return; |
| |
| if (!mld->bt_is_active) { |
| iwl_mld_retry_emlsr(mld, vif); |
| return; |
| } |
| |
| /* BT is turned ON but we are not in EMLSR, nothing to do */ |
| if (!iwl_mld_emlsr_active(vif)) |
| return; |
| |
| /* In EMLSR and BT is turned ON */ |
| |
| for_each_vif_active_link(vif, link, link_id) { |
| if (WARN_ON(!link->chanreq.oper.chan)) |
| continue; |
| |
| if (link->chanreq.oper.chan->band == NL80211_BAND_2GHZ) { |
| iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_BT_COEX, |
| iwl_mld_get_primary_link(vif)); |
| return; |
| } |
| } |
| } |
| |
| void iwl_mld_emlsr_check_bt(struct iwl_mld *mld) |
| { |
| ieee80211_iterate_active_interfaces_mtx(mld->hw, |
| IEEE80211_IFACE_ITER_NORMAL, |
| iwl_mld_emlsr_check_bt_iter, |
| NULL); |
| } |
| |
| struct iwl_mld_chan_load_data { |
| struct iwl_mld_phy *phy; |
| u32 prev_chan_load_not_by_us; |
| }; |
| |
| static void iwl_mld_chan_load_update_iter(void *_data, u8 *mac, |
| struct ieee80211_vif *vif) |
| { |
| struct iwl_mld_chan_load_data *data = _data; |
| const struct iwl_mld_phy *phy = data->phy; |
| struct ieee80211_chanctx_conf *chanctx = |
| container_of((const void *)phy, struct ieee80211_chanctx_conf, |
| drv_priv); |
| struct iwl_mld *mld = iwl_mld_vif_from_mac80211(vif)->mld; |
| struct ieee80211_bss_conf *prim_link; |
| unsigned int prim_link_id; |
| |
| prim_link_id = iwl_mld_get_primary_link(vif); |
| prim_link = link_conf_dereference_protected(vif, prim_link_id); |
| |
| if (WARN_ON(!prim_link)) |
| return; |
| |
| if (chanctx != rcu_access_pointer(prim_link->chanctx_conf)) |
| return; |
| |
| if (iwl_mld_emlsr_active(vif)) { |
| int chan_load = iwl_mld_get_chan_load_by_others(mld, prim_link, |
| true); |
| |
| if (chan_load < 0) |
| return; |
| |
| /* chan_load is in range [0,255] */ |
| if (chan_load < NORMALIZE_PERCENT_TO_255(IWL_MLD_EXIT_EMLSR_CHAN_LOAD)) |
| iwl_mld_exit_emlsr(mld, vif, |
| IWL_MLD_EMLSR_EXIT_CHAN_LOAD, |
| prim_link_id); |
| } else { |
| u32 old_chan_load = data->prev_chan_load_not_by_us; |
| u32 new_chan_load = phy->avg_channel_load_not_by_us; |
| u32 min_thresh = iwl_mld_get_min_chan_load_thresh(chanctx); |
| |
| #define THRESHOLD_CROSSED(threshold) \ |
| (old_chan_load <= (threshold) && new_chan_load > (threshold)) |
| |
| if (THRESHOLD_CROSSED(min_thresh) || THRESHOLD_CROSSED(25) || |
| THRESHOLD_CROSSED(40) || THRESHOLD_CROSSED(50)) |
| iwl_mld_retry_emlsr(mld, vif); |
| #undef THRESHOLD_CROSSED |
| } |
| } |
| |
| void iwl_mld_emlsr_check_chan_load(struct ieee80211_hw *hw, |
| struct iwl_mld_phy *phy, |
| u32 prev_chan_load_not_by_us) |
| { |
| struct iwl_mld_chan_load_data data = { |
| .phy = phy, |
| .prev_chan_load_not_by_us = prev_chan_load_not_by_us, |
| }; |
| |
| ieee80211_iterate_active_interfaces_mtx(hw, |
| IEEE80211_IFACE_ITER_NORMAL, |
| iwl_mld_chan_load_update_iter, |
| &data); |
| } |
| |
| void iwl_mld_retry_emlsr(struct iwl_mld *mld, struct ieee80211_vif *vif) |
| { |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| |
| if (!IWL_MLD_AUTO_EML_ENABLE || !iwl_mld_vif_has_emlsr_cap(vif) || |
| iwl_mld_emlsr_active(vif) || mld_vif->emlsr.blocked_reasons) |
| return; |
| |
| iwl_mld_int_mlo_scan(mld, vif); |
| } |
| |
| static void iwl_mld_ignore_tpt_iter(void *data, u8 *mac, |
| struct ieee80211_vif *vif) |
| { |
| struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); |
| struct iwl_mld *mld = mld_vif->mld; |
| struct iwl_mld_sta *mld_sta; |
| bool *start = (void *)data; |
| |
| /* check_tpt_wk is only used when TPT block isn't set */ |
| if (mld_vif->emlsr.blocked_reasons & IWL_MLD_EMLSR_BLOCKED_TPT || |
| !IWL_MLD_AUTO_EML_ENABLE || !mld_vif->ap_sta) |
| return; |
| |
| mld_sta = iwl_mld_sta_from_mac80211(mld_vif->ap_sta); |
| |
| /* We only count for the AP sta in a MLO connection */ |
| if (!mld_sta->mpdu_counters) |
| return; |
| |
| if (*start) { |
| wiphy_delayed_work_cancel(mld_vif->mld->wiphy, |
| &mld_vif->emlsr.check_tpt_wk); |
| IWL_DEBUG_EHT(mld, "TPT check disabled\n"); |
| return; |
| } |
| |
| /* Clear the counters so we start from the beginning */ |
| for (int q = 0; q < mld->trans->info.num_rxqs; q++) { |
| struct iwl_mld_per_q_mpdu_counter *queue_counter = |
| &mld_sta->mpdu_counters[q]; |
| |
| spin_lock_bh(&queue_counter->lock); |
| |
| memset(queue_counter->per_link, 0, |
| sizeof(queue_counter->per_link)); |
| |
| spin_unlock_bh(&queue_counter->lock); |
| } |
| |
| /* Schedule the check in 5 seconds */ |
| wiphy_delayed_work_queue(mld_vif->mld->wiphy, |
| &mld_vif->emlsr.check_tpt_wk, |
| round_jiffies_relative(IWL_MLD_TPT_COUNT_WINDOW)); |
| IWL_DEBUG_EHT(mld, "TPT check enabled\n"); |
| } |
| |
| void iwl_mld_start_ignoring_tpt_updates(struct iwl_mld *mld) |
| { |
| bool start = true; |
| |
| ieee80211_iterate_active_interfaces_mtx(mld->hw, |
| IEEE80211_IFACE_ITER_NORMAL, |
| iwl_mld_ignore_tpt_iter, |
| &start); |
| } |
| |
| void iwl_mld_stop_ignoring_tpt_updates(struct iwl_mld *mld) |
| { |
| bool start = false; |
| |
| ieee80211_iterate_active_interfaces_mtx(mld->hw, |
| IEEE80211_IFACE_ITER_NORMAL, |
| iwl_mld_ignore_tpt_iter, |
| &start); |
| } |