Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/wireless/intel/iwlwifi/mld/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 34 kB image not shown  

Quelle  mlo.c   Sprache: C

 
// 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);
}

Messung V0.5
C=95 H=96 G=95

¤ Dauer der Verarbeitung: 0.20 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.