summaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/intel/iwlwifi/mld/low_latency.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/intel/iwlwifi/mld/low_latency.c')
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/low_latency.c339
1 files changed, 339 insertions, 0 deletions
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/low_latency.c b/drivers/net/wireless/intel/iwlwifi/mld/low_latency.c
new file mode 100644
index 000000000000..a4a612afb3b3
--- /dev/null
+++ b/drivers/net/wireless/intel/iwlwifi/mld/low_latency.c
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (C) 2024-2025 Intel Corporation
+ */
+#include "mld.h"
+#include "iface.h"
+#include "low_latency.h"
+#include "hcmd.h"
+#include "power.h"
+#include "mlo.h"
+
+#define MLD_LL_WK_INTERVAL_MSEC 500
+#define MLD_LL_PERIOD (HZ * MLD_LL_WK_INTERVAL_MSEC / 1000)
+#define MLD_LL_ACTIVE_WK_PERIOD (HZ * 10)
+
+/* packets/MLD_LL_WK_PERIOD seconds */
+#define MLD_LL_ENABLE_THRESH 100
+
+static bool iwl_mld_calc_low_latency(struct iwl_mld *mld,
+ unsigned long timestamp)
+{
+ struct iwl_mld_low_latency *ll = &mld->low_latency;
+ bool global_low_latency = false;
+ u8 num_rx_q = mld->trans->num_rx_queues;
+
+ for (int mac_id = 0; mac_id < NUM_MAC_INDEX_DRIVER; mac_id++) {
+ u32 total_vo_vi_pkts = 0;
+ bool ll_period_expired;
+
+ /* If it's not initialized yet, it means we have not yet
+ * received/transmitted any vo/vi packet on this MAC.
+ */
+ if (!ll->window_start[mac_id])
+ continue;
+
+ ll_period_expired =
+ time_after(timestamp, ll->window_start[mac_id] +
+ MLD_LL_ACTIVE_WK_PERIOD);
+
+ if (ll_period_expired)
+ ll->window_start[mac_id] = timestamp;
+
+ for (int q = 0; q < num_rx_q; q++) {
+ struct iwl_mld_low_latency_packets_counters *counters =
+ &mld->low_latency.pkts_counters[q];
+
+ spin_lock_bh(&counters->lock);
+
+ total_vo_vi_pkts += counters->vo_vi[mac_id];
+
+ if (ll_period_expired)
+ counters->vo_vi[mac_id] = 0;
+
+ spin_unlock_bh(&counters->lock);
+ }
+
+ /* enable immediately with enough packets but defer
+ * disabling only if the low-latency period expired and
+ * below threshold.
+ */
+ if (total_vo_vi_pkts > MLD_LL_ENABLE_THRESH)
+ mld->low_latency.result[mac_id] = true;
+ else if (ll_period_expired)
+ mld->low_latency.result[mac_id] = false;
+
+ global_low_latency |= mld->low_latency.result[mac_id];
+ }
+
+ return global_low_latency;
+}
+
+static void iwl_mld_low_latency_iter(void *_data, u8 *mac,
+ struct ieee80211_vif *vif)
+{
+ struct iwl_mld *mld = _data;
+ struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
+ bool prev = mld_vif->low_latency_causes & LOW_LATENCY_TRAFFIC;
+ bool low_latency;
+
+ if (WARN_ON(mld_vif->fw_id >= ARRAY_SIZE(mld->low_latency.result)))
+ return;
+
+ low_latency = mld->low_latency.result[mld_vif->fw_id];
+
+ if (prev != low_latency)
+ iwl_mld_vif_update_low_latency(mld, vif, low_latency,
+ LOW_LATENCY_TRAFFIC);
+}
+
+static void iwl_mld_low_latency_wk(struct wiphy *wiphy, struct wiphy_work *wk)
+{
+ struct iwl_mld *mld = container_of(wk, struct iwl_mld,
+ low_latency.work.work);
+ unsigned long timestamp = jiffies;
+ bool low_latency_active;
+
+ if (mld->fw_status.in_hw_restart)
+ return;
+
+ /* It is assumed that the work was scheduled only after checking
+ * at least MLD_LL_PERIOD has passed since the last update.
+ */
+
+ low_latency_active = iwl_mld_calc_low_latency(mld, timestamp);
+
+ /* Update the timestamp now after the low-latency calculation */
+ mld->low_latency.timestamp = timestamp;
+
+ /* If low-latency is active we need to force re-evaluation after
+ * 10 seconds, so that we can disable low-latency when
+ * the low-latency traffic ends.
+ *
+ * Otherwise, we don't need to run the work because there is nothing to
+ * disable.
+ *
+ * Note that this has no impact on the regular scheduling of the
+ * updates triggered by traffic - those happen whenever the
+ * MLD_LL_PERIOD timeout expire.
+ */
+ if (low_latency_active)
+ wiphy_delayed_work_queue(mld->wiphy, &mld->low_latency.work,
+ MLD_LL_ACTIVE_WK_PERIOD);
+
+ ieee80211_iterate_active_interfaces_mtx(mld->hw,
+ IEEE80211_IFACE_ITER_NORMAL,
+ iwl_mld_low_latency_iter, mld);
+}
+
+int iwl_mld_low_latency_init(struct iwl_mld *mld)
+{
+ struct iwl_mld_low_latency *ll = &mld->low_latency;
+ unsigned long ts = jiffies;
+
+ ll->pkts_counters = kcalloc(mld->trans->num_rx_queues,
+ sizeof(*ll->pkts_counters), GFP_KERNEL);
+ if (!ll->pkts_counters)
+ return -ENOMEM;
+
+ for (int q = 0; q < mld->trans->num_rx_queues; q++)
+ spin_lock_init(&ll->pkts_counters[q].lock);
+
+ wiphy_delayed_work_init(&ll->work, iwl_mld_low_latency_wk);
+
+ ll->timestamp = ts;
+
+ /* The low-latency window_start will be initialized per-MAC on
+ * the first vo/vi packet received/transmitted.
+ */
+
+ return 0;
+}
+
+void iwl_mld_low_latency_free(struct iwl_mld *mld)
+{
+ struct iwl_mld_low_latency *ll = &mld->low_latency;
+
+ kfree(ll->pkts_counters);
+ ll->pkts_counters = NULL;
+}
+
+void iwl_mld_low_latency_restart_cleanup(struct iwl_mld *mld)
+{
+ struct iwl_mld_low_latency *ll = &mld->low_latency;
+
+ ll->timestamp = jiffies;
+
+ memset(ll->window_start, 0, sizeof(ll->window_start));
+ memset(ll->result, 0, sizeof(ll->result));
+
+ for (int q = 0; q < mld->trans->num_rx_queues; q++)
+ memset(ll->pkts_counters[q].vo_vi, 0,
+ sizeof(ll->pkts_counters[q].vo_vi));
+}
+
+static int iwl_mld_send_low_latency_cmd(struct iwl_mld *mld, bool low_latency,
+ u16 mac_id)
+{
+ struct iwl_mac_low_latency_cmd cmd = {
+ .mac_id = cpu_to_le32(mac_id)
+ };
+ u16 cmd_id = WIDE_ID(MAC_CONF_GROUP, LOW_LATENCY_CMD);
+ int ret;
+
+ if (low_latency) {
+ /* Currently we don't care about the direction */
+ cmd.low_latency_rx = 1;
+ cmd.low_latency_tx = 1;
+ }
+
+ ret = iwl_mld_send_cmd_pdu(mld, cmd_id, &cmd);
+ if (ret)
+ IWL_ERR(mld, "Failed to send low latency command\n");
+
+ return ret;
+}
+
+static void iwl_mld_vif_set_low_latency(struct iwl_mld_vif *mld_vif, bool set,
+ enum iwl_mld_low_latency_cause cause)
+{
+ if (set)
+ mld_vif->low_latency_causes |= cause;
+ else
+ mld_vif->low_latency_causes &= ~cause;
+}
+
+void iwl_mld_vif_update_low_latency(struct iwl_mld *mld,
+ struct ieee80211_vif *vif,
+ bool low_latency,
+ enum iwl_mld_low_latency_cause cause)
+{
+ struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
+ bool prev;
+
+ prev = iwl_mld_vif_low_latency(mld_vif);
+ iwl_mld_vif_set_low_latency(mld_vif, low_latency, cause);
+
+ low_latency = iwl_mld_vif_low_latency(mld_vif);
+ if (low_latency == prev)
+ return;
+
+ if (iwl_mld_send_low_latency_cmd(mld, low_latency, mld_vif->fw_id)) {
+ /* revert to previous low-latency state */
+ iwl_mld_vif_set_low_latency(mld_vif, prev, cause);
+ return;
+ }
+
+ if (low_latency)
+ iwl_mld_leave_omi_bw_reduction(mld);
+
+ if (ieee80211_vif_type_p2p(vif) != NL80211_IFTYPE_P2P_CLIENT)
+ return;
+
+ iwl_mld_update_mac_power(mld, vif, false);
+
+ if (low_latency)
+ iwl_mld_retry_emlsr(mld, vif);
+}
+
+static bool iwl_mld_is_vo_vi_pkt(struct ieee80211_hdr *hdr)
+{
+ u8 tid;
+ static const u8 tid_to_mac80211_ac[] = {
+ IEEE80211_AC_BE,
+ IEEE80211_AC_BK,
+ IEEE80211_AC_BK,
+ IEEE80211_AC_BE,
+ IEEE80211_AC_VI,
+ IEEE80211_AC_VI,
+ IEEE80211_AC_VO,
+ IEEE80211_AC_VO,
+ };
+
+ if (!hdr || !ieee80211_is_data_qos(hdr->frame_control))
+ return false;
+
+ tid = ieee80211_get_tid(hdr);
+ if (tid >= IWL_MAX_TID_COUNT)
+ return false;
+
+ return tid_to_mac80211_ac[tid] < IEEE80211_AC_VI;
+}
+
+void iwl_mld_low_latency_update_counters(struct iwl_mld *mld,
+ struct ieee80211_hdr *hdr,
+ struct ieee80211_sta *sta,
+ u8 queue)
+{
+ struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
+ struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(mld_sta->vif);
+ struct iwl_mld_low_latency_packets_counters *counters;
+ unsigned long ts = jiffies ? jiffies : 1;
+ u8 fw_id = mld_vif->fw_id;
+
+ /* we should have failed op mode init if NULL */
+ if (WARN_ON_ONCE(!mld->low_latency.pkts_counters))
+ return;
+
+ if (WARN_ON_ONCE(fw_id >= ARRAY_SIZE(counters->vo_vi) ||
+ queue >= mld->trans->num_rx_queues))
+ return;
+
+ if (mld->low_latency.stopped)
+ return;
+
+ if (!iwl_mld_is_vo_vi_pkt(hdr))
+ return;
+
+ counters = &mld->low_latency.pkts_counters[queue];
+
+ spin_lock_bh(&counters->lock);
+ counters->vo_vi[fw_id]++;
+ spin_unlock_bh(&counters->lock);
+
+ /* Initialize the window_start on the first vo/vi packet */
+ if (!mld->low_latency.window_start[fw_id])
+ mld->low_latency.window_start[fw_id] = ts;
+
+ if (time_is_before_jiffies(mld->low_latency.timestamp + MLD_LL_PERIOD))
+ wiphy_delayed_work_queue(mld->wiphy, &mld->low_latency.work,
+ 0);
+}
+
+void iwl_mld_low_latency_stop(struct iwl_mld *mld)
+{
+ lockdep_assert_wiphy(mld->wiphy);
+
+ mld->low_latency.stopped = true;
+
+ wiphy_delayed_work_cancel(mld->wiphy, &mld->low_latency.work);
+}
+
+void iwl_mld_low_latency_restart(struct iwl_mld *mld)
+{
+ struct iwl_mld_low_latency *ll = &mld->low_latency;
+ bool low_latency = false;
+ unsigned long ts = jiffies;
+
+ lockdep_assert_wiphy(mld->wiphy);
+
+ ll->timestamp = ts;
+ mld->low_latency.stopped = false;
+
+ for (int mac = 0; mac < NUM_MAC_INDEX_DRIVER; mac++) {
+ ll->window_start[mac] = 0;
+ low_latency |= ll->result[mac];
+
+ for (int q = 0; q < mld->trans->num_rx_queues; q++) {
+ spin_lock_bh(&ll->pkts_counters[q].lock);
+ ll->pkts_counters[q].vo_vi[mac] = 0;
+ spin_unlock_bh(&ll->pkts_counters[q].lock);
+ }
+ }
+
+ /* if low latency is active, force re-evaluation to cover the case of
+ * no traffic.
+ */
+ if (low_latency)
+ wiphy_delayed_work_queue(mld->wiphy, &ll->work, MLD_LL_PERIOD);
+}