]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: ath12k: Fix low MLO RX throughput on WCN7850
authorYingying Tang <yingying.tang@oss.qualcomm.com>
Wed, 10 Jun 2026 05:33:15 +0000 (22:33 -0700)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Mon, 13 Jul 2026 13:55:17 +0000 (06:55 -0700)
Commit [1] introduced a regression causing severely degraded MLO RX
throughput on WCN7850.

On WCN7850, there is only a single ar instance, but MLO uses two
link IDs. ath12k_dp_peer->hw_links[] is indexed using ar->hw_link_id,
which causes both MLO link IDs to be stored at the same index.

As a result, an incorrect link ID is assigned to MSDUs in
ath12k_dp_rx_deliver_msdu(), leading to severe MLO RX throughput loss.

Different chipsets identify the per-MSDU link differently:

  - On QCN9274 / IPQ5332, the host owns multiple ar instances and the
    per-MSDU hw_link_id from the RX descriptor maps cleanly through
    dp_peer->hw_links[hw_link_id] to the IEEE link_id.

  - On single-ar chipsets like WCN7850 / QCC2072, there is only one ar
    instance for both MLO links, so dp_peer->hw_links[] has just one
    valid slot and cannot be used to distinguish the two links. To
    resolve the link, walk dp_peer->link_peers[] and match by
    rxcb->peer_id, which on the link_peer side identifies the link
    peer for the MSDU.

Add a new hw_op set_rx_link_id() so each chipset resolves the link
on the RX fast path using whatever signal it actually has, and let
the op itself decide whether to populate rx_status::link_valid and
rx_status::link_id:

  QCN9274 / IPQ5332 : always derive link_id from
                      dp_peer->hw_links[rxcb->hw_link_id] and set
                      link_valid.
  WCN7850 / QCC2072 : walk the link_peers[] of dp_peer to find the
                      link_peer whose peer_id matches rxcb->peer_id,
                      and set link_valid only when a match is found.
                      Otherwise leave link_valid clear so that
                      mac80211 can fall back to its own link
                      resolution path (via addr2 / deflink).

For WCN7850 / QCC2072, walking dp_peer->link_peers[] is bounded by
the number of links actually populated, so introduce a link_peers_map
bitmap (unsigned long) in struct ath12k_dp_peer that tracks populated
slots and use for_each_set_bit() to iterate. Non-MLO clients hit one
slot, current MLO clients hit two; the full ATH12K_NUM_MAX_LINKS
array is never scanned. The bitmap is maintained with WRITE_ONCE() on
the write side (under dp_hw->peer_lock) paired with READ_ONCE() on
both the lockless RX read side and the write-side RMW for KCSAN
correctness.

Also guard the dp_peer dereference in ath12k_mac_peer_cleanup_all()
with a NULL check, since peer->dp_peer can be NULL for self-peers or
peers not yet fully assigned, the pre-existing rcu_assign_pointer()
call there had the same latent issue.

This restores the correct link ID on WCN7850 without changing the
QCN9274 / IPQ5332 data path, which keeps its O(1) hw_links[]
indexing.

Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3

Fixes: 11157e0910fd ("wifi: ath12k: Use ath12k_dp_peer in per packet Tx & Rx paths") # [1]
Signed-off-by: Yingying Tang <yingying.tang@oss.qualcomm.com>
Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>
Link: https://patch.msgid.link/20260610053315.2249912-1-yingying.tang@oss.qualcomm.com
Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
drivers/net/wireless/ath/ath12k/dp_peer.c
drivers/net/wireless/ath/ath12k/dp_peer.h
drivers/net/wireless/ath/ath12k/dp_rx.c
drivers/net/wireless/ath/ath12k/hw.h
drivers/net/wireless/ath/ath12k/mac.c
drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c
drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h
drivers/net/wireless/ath/ath12k/wifi7/hw.c

index 47d009a0d61fef033a94ac3f70e93a5230360d2b..2660b4c7449be2066dad70412d2d12d012d8b102 100644 (file)
@@ -570,6 +570,8 @@ int ath12k_dp_link_peer_assign(struct ath12k_dp *dp, struct ath12k_dp_hw *dp_hw,
        peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id);
 
        rcu_assign_pointer(dp_peer->link_peers[peer->link_id], peer);
+       WRITE_ONCE(dp_peer->link_peers_map,
+                  READ_ONCE(dp_peer->link_peers_map) | BIT(peer->link_id));
 
        rcu_assign_pointer(dp_hw->dp_peers[peerid_index], dp_peer);
 
@@ -632,6 +634,8 @@ void ath12k_dp_link_peer_unassign(struct ath12k_dp *dp, struct ath12k_dp_hw *dp_
        peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id);
 
        rcu_assign_pointer(dp_peer->link_peers[peer->link_id], NULL);
+       WRITE_ONCE(dp_peer->link_peers_map,
+                  READ_ONCE(dp_peer->link_peers_map) & ~BIT(peer->link_id));
 
        rcu_assign_pointer(dp_hw->dp_peers[peerid_index], NULL);
 
index f5067e66f1e182ec1cdd45e6173725150723faff..7c9709bf717b4d225be091f2c7b54833622ed16c 100644 (file)
@@ -140,6 +140,7 @@ struct ath12k_dp_peer {
 
        /* Info used in MMIC verification of * RX fragments */
        struct ieee80211_key_conf *keys[WMI_MAX_KEY_INDEX + 1];
+       unsigned long link_peers_map;
        struct ath12k_dp_link_peer __rcu *link_peers[ATH12K_NUM_MAX_LINKS];
        struct ath12k_reoq_buf reoq_bufs[IEEE80211_NUM_TIDS + 1];
        struct ath12k_dp_rx_tid rx_tid[IEEE80211_NUM_TIDS + 1];
index 06e74124e57eb91fc02a730e333dbac6c69bf049..8fa0e90b45316052a081ca1ab010e93697a06692 100644 (file)
@@ -1384,10 +1384,9 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc
 
        pubsta = peer ? peer->sta : NULL;
 
-       if (pubsta && pubsta->valid_links) {
-               status->link_valid = 1;
-               status->link_id = peer->hw_links[rxcb->hw_link_id];
-       }
+       status->link_valid = 0;
+       if (pubsta && pubsta->valid_links)
+               ath12k_hw_set_rx_link_id(dp->hw_params, peer, rxcb, status);
 
        ath12k_dbg(dp->ab, ATH12K_DBG_DATA,
                   "rx skb %p len %u peer %pM %d %s sn %u %s%s%s%s%s%s%s%s%s%s rate_idx %u vht_nss %u freq %u band %u flag 0x%x fcs-err %i mic-err %i amsdu-more %i\n",
index d135b29363788b9aa5d7b4caa3ac901fe7e215b3..86fb8b71961350dd26e33fa6e7f2b66d89d723ca 100644 (file)
 #include "wmi.h"
 #include "hal.h"
 
+struct ath12k_dp_peer;
+struct ath12k_skb_rxcb;
+struct ieee80211_rx_status;
+
 /* Target configuration defines */
 
 /* Num VDEVS per radio */
@@ -243,6 +247,9 @@ struct ath12k_hw_ops {
        bool (*dp_srng_is_tx_comp_ring)(int ring_num);
        bool (*is_frame_link_agnostic)(struct ath12k_link_vif *arvif,
                                       struct ieee80211_mgmt *mgmt);
+       void (*set_rx_link_id)(struct ath12k_dp_peer *dp_peer,
+                              struct ath12k_skb_rxcb *rxcb,
+                              struct ieee80211_rx_status *status);
 };
 
 static inline
@@ -273,6 +280,15 @@ static inline int ath12k_hw_mac_id_to_srng_id(const struct ath12k_hw_params *hw,
        return 0;
 }
 
+static inline void ath12k_hw_set_rx_link_id(const struct ath12k_hw_params *hw,
+                                           struct ath12k_dp_peer *dp_peer,
+                                           struct ath12k_skb_rxcb *rxcb,
+                                           struct ieee80211_rx_status *status)
+{
+       if (hw->hw_ops->set_rx_link_id)
+               hw->hw_ops->set_rx_link_id(dp_peer, rxcb, status);
+}
+
 struct ath12k_fw_ie {
        __le32 id;
        __le32 len;
index af354bef5c0d79bfea3e09ec2858500d29052df3..51c4df32e71695585e52dafd0dd5c8912eaece80 100644 (file)
@@ -1238,9 +1238,13 @@ void ath12k_mac_peer_cleanup_all(struct ath12k *ar)
                /* cleanup dp peer */
                spin_lock_bh(&dp_hw->peer_lock);
                dp_peer = peer->dp_peer;
-               peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id);
-               rcu_assign_pointer(dp_peer->link_peers[peer->link_id], NULL);
-               rcu_assign_pointer(dp_hw->dp_peers[peerid_index], NULL);
+               if (dp_peer) {
+                       peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id);
+                       rcu_assign_pointer(dp_peer->link_peers[peer->link_id], NULL);
+                       WRITE_ONCE(dp_peer->link_peers_map,
+                                  READ_ONCE(dp_peer->link_peers_map) & ~BIT(peer->link_id));
+                       rcu_assign_pointer(dp_hw->dp_peers[peerid_index], NULL);
+               }
                spin_unlock_bh(&dp_hw->peer_lock);
 
                ath12k_dp_link_peer_rhash_delete(dp, peer);
index cb9dd8cb28b6716fb59d7753b5ac7073be9e7f69..95d87dd67872f72d920e27ed91d4dc4f7f0082b8 100644 (file)
@@ -5,6 +5,7 @@
  */
 
 #include "dp_rx.h"
+#include "../dp_peer.h"
 #include "../dp_tx.h"
 #include "../peer.h"
 #include "hal_qcn9274.h"
@@ -2315,3 +2316,35 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab,
 
        return tlv_tag == HAL_RX_MPDU_START;
 }
+
+void
+ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,
+                                      struct ath12k_skb_rxcb *rxcb,
+                                      struct ieee80211_rx_status *status)
+{
+       status->link_valid = 1;
+       status->link_id = dp_peer->hw_links[rxcb->hw_link_id];
+}
+
+void
+ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,
+                                      struct ath12k_skb_rxcb *rxcb,
+                                      struct ieee80211_rx_status *status)
+{
+       struct ath12k_dp_link_peer *link_peer;
+       unsigned long links_map;
+       int i;
+
+       RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
+                        "ath12k set rx link id called without rcu lock");
+
+       links_map = READ_ONCE(dp_peer->link_peers_map);
+       for_each_set_bit(i, &links_map, ATH12K_NUM_MAX_LINKS) {
+               link_peer = rcu_dereference(dp_peer->link_peers[i]);
+               if (link_peer && link_peer->peer_id == rxcb->peer_id) {
+                       status->link_valid = 1;
+                       status->link_id = link_peer->link_id;
+                       return;
+               }
+       }
+}
index 8aa79faf567fd061b1717f7b9da09c8962990027..1d3a4788a2dd91e34d9de471e394251337bc9acc 100644 (file)
@@ -57,4 +57,10 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab,
                                  struct hal_rx_desc *rx_desc);
 int ath12k_wifi7_dp_rx_tid_delete_handler(struct ath12k_base *ab,
                                          struct ath12k_dp_rx_tid_rxq *rx_tid);
+void ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,
+                                           struct ath12k_skb_rxcb *rxcb,
+                                           struct ieee80211_rx_status *status);
+void ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,
+                                           struct ath12k_skb_rxcb *rxcb,
+                                           struct ieee80211_rx_status *status);
 #endif
index 3d59fa452ec03d6abdabb1b76602fee2b4c5a04b..d9fdd2fc82986abef849a0f068b66db33afe02bc 100644 (file)
@@ -158,6 +158,7 @@ static const struct ath12k_hw_ops qcn9274_ops = {
        .get_ring_selector = ath12k_wifi7_hw_get_ring_selector_qcn9274,
        .dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_qcn9274,
        .is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_qcn9274,
+       .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_qcn9274,
 };
 
 static const struct ath12k_hw_ops wcn7850_ops = {
@@ -168,6 +169,7 @@ static const struct ath12k_hw_ops wcn7850_ops = {
        .get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850,
        .dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850,
        .is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850,
+       .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850,
 };
 
 static const struct ath12k_hw_ops qcc2072_ops = {
@@ -178,6 +180,7 @@ static const struct ath12k_hw_ops qcc2072_ops = {
        .get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850,
        .dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850,
        .is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850,
+       .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850,
 };
 
 #define ATH12K_TX_RING_MASK_0 0x1