[PATCH ath-current] wifi: ath12k: Fix low MLO RX throughput on WCN7850
Yingying Tang
yingying.tang at oss.qualcomm.com
Tue Jun 9 22:33:15 PDT 2026
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 at oss.qualcomm.com>
---
drivers/net/wireless/ath/ath12k/dp_peer.c | 4 +++
drivers/net/wireless/ath/ath12k/dp_peer.h | 1 +
drivers/net/wireless/ath/ath12k/dp_rx.c | 7 ++--
drivers/net/wireless/ath/ath12k/hw.h | 16 +++++++++
drivers/net/wireless/ath/ath12k/mac.c | 10 ++++--
drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c | 33 +++++++++++++++++++
drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h | 6 ++++
drivers/net/wireless/ath/ath12k/wifi7/hw.c | 3 ++
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 a1100782d45e..f57f1483c3e4 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 113b8040010f..d4d2ff16e836 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 b108ccd0f637..b5dba7c0155f 100644
--- a/drivers/net/wireless/ath/ath12k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath12k/dp_rx.c
@@ -1344,10 +1344,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 a9888e0521a1..da75d19ae1a0 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 */
@@ -224,6 +228,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
@@ -254,6 +261,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 df2334f3bad6..a7377bc7308b 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -1234,9 +1234,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 945680b3ebdf..2801a38cd1e7 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"
@@ -2240,3 +2241,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 cb3185850439..f687eb69ea8d 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
--
base-commit: 54a5b38e4396530e5b2f12b54d3844e860ab6784
More information about the ath12k
mailing list