summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYingying Tang <yingying.tang@oss.qualcomm.com>2026-06-09 22:33:15 -0700
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>2026-07-13 06:55:17 -0700
commita2fe9dc70f3b8d5716fbcfed5fbfb9cf3948d402 (patch)
tree27f091bbe722f18437ee6ecb8c183614910265fa
parent55f3aa06951cac78b0206bde961c8cf11929a27a (diff)
downloadlinux-a2fe9dc70f3b8d5716fbcfed5fbfb9cf3948d402.tar.gz
linux-a2fe9dc70f3b8d5716fbcfed5fbfb9cf3948d402.zip
wifi: ath12k: Fix low MLO RX throughput on WCN7850
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>
-rw-r--r--drivers/net/wireless/ath/ath12k/dp_peer.c4
-rw-r--r--drivers/net/wireless/ath/ath12k/dp_peer.h1
-rw-r--r--drivers/net/wireless/ath/ath12k/dp_rx.c7
-rw-r--r--drivers/net/wireless/ath/ath12k/hw.h16
-rw-r--r--drivers/net/wireless/ath/ath12k/mac.c10
-rw-r--r--drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c33
-rw-r--r--drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h6
-rw-r--r--drivers/net/wireless/ath/ath12k/wifi7/hw.c3
8 files changed, 73 insertions, 7 deletions
diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.c b/drivers/net/wireless/ath/ath12k/dp_peer.c
index 47d009a0d61f..2660b4c7449b 100644
--- a/drivers/net/wireless/ath/ath12k/dp_peer.c
+++ b/drivers/net/wireless/ath/ath12k/dp_peer.c
@@ -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);
diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.h b/drivers/net/wireless/ath/ath12k/dp_peer.h
index f5067e66f1e1..7c9709bf717b 100644
--- a/drivers/net/wireless/ath/ath12k/dp_peer.h
+++ b/drivers/net/wireless/ath/ath12k/dp_peer.h
@@ -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];
diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c
index 06e74124e57e..8fa0e90b4531 100644
--- a/drivers/net/wireless/ath/ath12k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath12k/dp_rx.c
@@ -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",
diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h
index d135b2936378..86fb8b719613 100644
--- a/drivers/net/wireless/ath/ath12k/hw.h
+++ b/drivers/net/wireless/ath/ath12k/hw.h
@@ -13,6 +13,10 @@
#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;
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index af354bef5c0d..51c4df32e716 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -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);
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c
index cb9dd8cb28b6..95d87dd67872 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c
@@ -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;
+ }
+ }
+}
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h
index 8aa79faf567f..1d3a4788a2dd 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h
+++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h
@@ -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
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
index 3d59fa452ec0..d9fdd2fc8298 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
@@ -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