From: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
To: jjohnson@kernel.org
Cc: ath11k@lists.infradead.org, ath12k@lists.infradead.org,
linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org,
Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
Subject: [PATCH 3/5] wifi: ath12k: fix MLO dp_peer ID desync with firmware
Date: Mon, 27 Jul 2026 18:27:46 +0200 [thread overview]
Message-ID: <20260727162748.963275-4-jtornosm@redhat.com> (raw)
In-Reply-To: <20260727162748.963275-1-jtornosm@redhat.com>
TX completions fail with:
"dp_tx: failed to find the peer with peer_id <N>"
where <N> is an ML peer_id (host-assigned ml_peer_id OR'd with
ATH12K_PEER_ML_ID_VALID, BIT(13) = 0x2000). This happens because
the firmware uses an internal monotonic counter for ML peer IDs that
does not necessarily match the host-assigned ml_peer_id. The desync
has been observed in normal MLO operation and is particularly
reproducible after firmware crash recovery, where the
create-delete-create cycle guarantees the firmware increments its
internal counter.
Currently, ath12k_dp_peer_create() stores the host-assigned
ml_peer_id (with ATH12K_PEER_ML_ID_VALID OR'd in) as dp_peer->peer_id
and immediately populates the dp_peers[] RCU lookup table at that
index. When the firmware later uses a different peer_id in its data
path descriptors, ath12k_dp_peer_find_by_peerid() looks up the wrong
index and returns NULL, causing the "failed to find peer" error on
every TX completion.
Fix this by initializing dp_peer->peer_id to ATH12K_DP_PEER_ID_INVALID
for MLO peers at creation time, deferring the dp_peers[] RCU table
population. When ath12k_dp_peer_find_by_peerid() encounters a lookup
miss for an ML peer_id (one with ATH12K_PEER_ML_ID_VALID set), it
walks dp_peers_list for an MLO peer whose peer_id is still INVALID,
syncs peer_id from the firmware's actual value, and populates the
dp_peers[] table. This lazy sync approach ensures the host always uses
the firmware's real ML peer_id regardless of any internal firmware
counter behavior.
Guard the dp_peers[] RCU table clear in ath12k_dp_peer_delete() and
ath12k_mac_dp_peer_cleanup() against ATH12K_DP_PEER_ID_INVALID to
avoid clearing uninitialized slots. Replace per-peer clear_bit() with
bitmap_zero() in ath12k_mac_dp_peer_cleanup() to avoid out-of-bounds
access when peer_id has not been synced yet.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
drivers/net/wireless/ath/ath12k/dp_peer.c | 45 ++++++++++++++++-------
drivers/net/wireless/ath/ath12k/mac.c | 7 +++-
2 files changed, 36 insertions(+), 16 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.c b/drivers/net/wireless/ath/ath12k/dp_peer.c
index 2660b4c7449b..0a7ef81425f5 100644
--- a/drivers/net/wireless/ath/ath12k/dp_peer.c
+++ b/drivers/net/wireless/ath/ath12k/dp_peer.c
@@ -413,8 +413,10 @@ u16 ath12k_dp_peer_get_peerid_index(struct ath12k_dp *dp, u16 peer_id)
struct ath12k_dp_peer *ath12k_dp_peer_find_by_peerid(struct ath12k_pdev_dp *dp_pdev,
u16 peer_id)
{
- u16 index;
+ struct ath12k_dp_hw *dp_hw = dp_pdev->dp_hw;
+ struct ath12k_dp_peer *dp_peer, *tmp;
struct ath12k_dp *dp = dp_pdev->dp;
+ u16 index;
RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
"ath12k dp peer find by peerid index called without rcu lock");
@@ -423,8 +425,23 @@ struct ath12k_dp_peer *ath12k_dp_peer_find_by_peerid(struct ath12k_pdev_dp *dp_p
return NULL;
index = ath12k_dp_peer_get_peerid_index(dp, peer_id);
+ dp_peer = rcu_dereference(dp_hw->dp_peers[index]);
+
+ if (!dp_peer && (peer_id & ATH12K_PEER_ML_ID_VALID)) {
+ spin_lock_bh(&dp_hw->peer_lock);
+ list_for_each_entry(tmp, &dp_hw->dp_peers_list, list) {
+ if (tmp->is_mlo &&
+ tmp->peer_id == ATH12K_DP_PEER_ID_INVALID) {
+ tmp->peer_id = peer_id;
+ rcu_assign_pointer(dp_hw->dp_peers[index], tmp);
+ dp_peer = tmp;
+ break;
+ }
+ }
+ spin_unlock_bh(&dp_hw->peer_lock);
+ }
- return rcu_dereference(dp_pdev->dp_hw->dp_peers[index]);
+ return dp_peer;
}
EXPORT_SYMBOL(ath12k_dp_peer_find_by_peerid);
@@ -472,11 +489,11 @@ int ath12k_dp_peer_create(struct ath12k_dp_hw *dp_hw, u8 *addr,
dp_peer->is_mlo = params->is_mlo;
/*
- * For MLO client, the host assigns the ML peer ID, so set peer_id in dp_peer
- * For non-MLO client, host gets link peer ID from firmware and will be
- * assigned at the time of link peer creation
+ * peer_id starts as INVALID for both MLO and non-MLO clients.
+ * For MLO: synced from firmware's first data path descriptor.
+ * For non-MLO: assigned at the time of link peer creation.
*/
- dp_peer->peer_id = params->is_mlo ? params->peer_id : ATH12K_DP_PEER_ID_INVALID;
+ dp_peer->peer_id = ATH12K_DP_PEER_ID_INVALID;
dp_peer->ucast_ra_only = params->ucast_ra_only;
dp_peer->sec_type = HAL_ENCRYPT_TYPE_OPEN;
@@ -488,15 +505,12 @@ int ath12k_dp_peer_create(struct ath12k_dp_hw *dp_hw, u8 *addr,
list_add(&dp_peer->list, &dp_hw->dp_peers_list);
/*
- * For MLO client, the peer_id for ath12k_dp_peer is allocated by host
- * and that peer_id is known at this point, and hence this ath12k_dp_peer
- * can be added to the RCU table using the peer_id.
+ * For MLO client, the dp_peers[] RCU table entry will be populated
+ * when the firmware's actual peer_id is learned from the first data
+ * path descriptor (in ath12k_dp_peer_find_by_peerid).
* For non-MLO client, this addition to RCU table shall be done at the
* time of assignment of ath12k_dp_link_peer to ath12k_dp_peer.
*/
- if (dp_peer->is_mlo)
- rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], dp_peer);
-
spin_unlock_bh(&dp_hw->peer_lock);
return 0;
@@ -515,8 +529,11 @@ void ath12k_dp_peer_delete(struct ath12k_dp_hw *dp_hw, u8 *addr,
return;
}
- if (dp_peer->is_mlo)
- rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL);
+ if (dp_peer->is_mlo) {
+ if (dp_peer->peer_id != ATH12K_DP_PEER_ID_INVALID)
+ rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id],
+ NULL);
+ }
list_del(&dp_peer->list);
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index 3fa62deb8186..aa03491fb59a 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -1287,13 +1287,16 @@ void ath12k_mac_dp_peer_cleanup(struct ath12k_hw *ah)
spin_lock_bh(&dp_hw->peer_lock);
list_for_each_entry_safe(dp_peer, tmp, &dp_hw->dp_peers_list, list) {
if (dp_peer->is_mlo) {
- rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL);
- clear_bit(dp_peer->peer_id, ah->free_ml_peer_id_map);
+ if (dp_peer->peer_id != ATH12K_DP_PEER_ID_INVALID)
+ rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id],
+ NULL);
}
list_move(&dp_peer->list, &peers);
}
+ bitmap_zero(ah->free_ml_peer_id_map, ATH12K_MAX_MLO_PEERS);
+
spin_unlock_bh(&dp_hw->peer_lock);
synchronize_rcu();
--
2.54.0
next prev parent reply other threads:[~2026-07-27 16:28 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-27 16:27 [PATCH 0/5] wifi: ath12k: fixes to improve MLO station stability Jose Ignacio Tornos Martinez
2026-07-27 16:27 ` [PATCH 1/5] wifi: ath12k: fix MLO station firmware crash recovery Jose Ignacio Tornos Martinez
2026-07-31 8:48 ` Baochen Qiang
2026-08-03 15:02 ` Jose Ignacio Tornos Martinez
2026-07-27 16:27 ` [PATCH 2/5] wifi: ath12k: prevent scan during firmware recovery Jose Ignacio Tornos Martinez
2026-07-31 8:16 ` Baochen Qiang
2026-08-03 15:08 ` Jose Ignacio Tornos Martinez
2026-07-27 16:27 ` Jose Ignacio Tornos Martinez [this message]
2026-07-31 8:02 ` [PATCH 3/5] wifi: ath12k: fix MLO dp_peer ID desync with firmware Baochen Qiang
2026-08-03 15:12 ` Jose Ignacio Tornos Martinez
2026-07-27 16:27 ` [PATCH 4/5] wifi: ath12k: fix MLO beacon handling using per-link addressing Jose Ignacio Tornos Martinez
2026-07-31 8:48 ` Baochen Qiang
2026-08-03 15:18 ` Jose Ignacio Tornos Martinez
2026-07-27 16:27 ` [PATCH 5/5] wifi: ath12k: skip connection_loss_work for MLO beacon miss Jose Ignacio Tornos Martinez
2026-07-31 8:48 ` Baochen Qiang
2026-08-03 15:22 ` Jose Ignacio Tornos Martinez
2026-07-31 22:04 ` [PATCH 0/5] wifi: ath12k: fixes to improve MLO station stability Jeff Johnson
2026-08-03 14:56 ` Jose Ignacio Tornos Martinez
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260727162748.963275-4-jtornosm@redhat.com \
--to=jtornosm@redhat.com \
--cc=ath11k@lists.infradead.org \
--cc=ath12k@lists.infradead.org \
--cc=jjohnson@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-wireless@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.