* [PATCH 0/5] wifi: ath12k: fixes to improve MLO station stability
@ 2026-07-27 16:27 Jose Ignacio Tornos Martinez
2026-07-27 16:27 ` [PATCH 1/5] wifi: ath12k: fix MLO station firmware crash recovery Jose Ignacio Tornos Martinez
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-07-27 16:27 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
This series improves firmware crash recovery and connection stability
for MLO (Wi-Fi 7) stations on ath12k. Both work correctly for non-MLO
(Wi-Fi 6) stations, where the driver handles the full teardown and
reconnect cycle without issues and maintains stable connections. This
series brings MLO to the same level.
Without these fixes, a firmware crash during an active MLO connection
results in kernel warnings, NULL pointer dereferences, 20+ second
recovery delays, and periodic disconnections both after recovery and
during normal operation under heavy traffic.
Patches 1-3 fix firmware crash recovery:
Patch 1 moves the RECOVERY flag clear to reconfig_complete and adds
CRASH_FLUSH guards to prevent operations on dead firmware.
Patch 2 prevents scans during recovery to avoid NULL dereferences.
Patch 3 fixes ML peer ID desync between host and firmware by syncing
the peer ID from firmware's actual value.
Patches 4-5 fix MLO beacon miss handling:
Patch 4 fixes beacon_iter to iterate all MLO links instead of only
deflink for BSSID matching and connection_loss_work
cancellation.
Patch 5 complements patch 4 by skipping connection_loss_work for MLO
entirely, since its cancellation mechanism is unreliable and
the mac80211 probe triggered by ieee80211_beacon_loss() is
sufficient to detect real AP unreachability, as done by other
MLO-capable drivers such as mt76 and rtw89.
Tested on WCN7850 with MLO (Wi-Fi 7).
Firmware crashes were observed to occur spontaneously during normal
MLO operation with intense traffic testing, though rarely.
The debugfs simulate_fw_crash interface was used for systematic testing
and reproduction during active MLO connections with traffic.
Note: I will have limited availability from mid-August to
mid-September. I will address any review feedback before then
or promptly after returning.
Jose Ignacio Tornos Martinez (5):
wifi: ath12k: fix MLO station firmware crash recovery
wifi: ath12k: prevent scan during firmware recovery
wifi: ath12k: fix MLO dp_peer ID desync with firmware
wifi: ath12k: fix MLO beacon handling using per-link addressing
wifi: ath12k: skip connection_loss_work for MLO beacon miss
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/5] wifi: ath12k: fix MLO station firmware crash recovery
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 ` Jose Ignacio Tornos Martinez
2026-07-27 16:27 ` [PATCH 2/5] wifi: ath12k: prevent scan during firmware recovery Jose Ignacio Tornos Martinez
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-07-27 16:27 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
ATH12K_FLAG_RECOVERY is cleared too early in
ath12k_core_reconfigure_on_crash(), before mac80211 runs
ieee80211_reconfig(). By the time mac80211 calls back into the driver
(sta_state, change_vif_links, set_key), the RECOVERY flag is already false,
so the driver treats recovery callbacks as normal operations.
This causes several problems during MLO recovery:
- ath12k_mac_op_sta_state() tries to activate MLO links during the
AUTH->ASSOC transition, calling ieee80211_set_active_links()
recursively, which triggers a WARNING at net/mac80211/link.c.
- ath12k_mac_op_change_vif_links() processes link removal during
reconfig, causing inconsistent state.
- ath12k_mac_set_key() fails with "cannot install key for non-existent
peer" because peers do not exist yet during reconfig. Keys will be
re-established during normal reconnection after
ieee80211_hw_restart_disconnect() triggers a fresh association.
- ath12k_mac_flush() waits for pending TX to complete, but after a
firmware crash the TX will never complete, causing a 20 second timeout.
- ath12k_mac_station_remove() calls ath12k_bss_disassoc() and
ath12k_mac_vdev_stop() which send WMI commands to dead firmware,
causing timeouts that delay recovery.
- ath12k_clear_peer_keys() tries to look up and clear peer keys, but
peers are already gone after firmware crash.
- ath12k_dp_rx_ampdu_stop() dereferences per-link station state that
may not be valid during crash teardown.
- ath12k_peer_mlo_link_peers_delete() sends WMI peer delete commands
for each MLO link peer. With dead firmware these time out and can
trigger cascading resets.
- HAL srng source ring operations (ath12k_hal_srng_src_num_free,
ath12k_hal_srng_src_get_next_entry, ath12k_hal_srng_access_end)
may attempt MMIO access to hardware that is no longer responsive
if TX paths race with the crash. Guard these to prevent potential
hard lockups on unresponsive hardware.
These issues were observed during sporadic firmware crashes in MLO
operation. To allow systematic testing and reproduction, the debugfs
simulate_fw_crash interface was used to trigger controlled firmware
crashes during active MLO connections with traffic.
Fix by moving clear_bit(ATH12K_FLAG_RECOVERY) from
ath12k_core_reconfigure_on_crash() to ath12k_mac_op_reconfig_complete(),
so the flag stays set through the entire mac80211 reconfig phase. Add
ATH12K_FLAG_RECOVERY checks in change_vif_links, set_key, and sta_state
to skip operations that are invalid during recovery. Add
ATH12K_FLAG_CRASH_FLUSH checks in mac_flush, station_remove,
clear_peer_keys, dp_rx_ampdu_stop, peer_mlo_link_peers_delete, and the
HAL srng source ring helpers to return immediately when the firmware is
dead.
Tested on WCN7850 with MLO (Wi-Fi 7).
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
drivers/net/wireless/ath/ath12k/core.c | 2 --
drivers/net/wireless/ath/ath12k/dp_rx.c | 3 +++
drivers/net/wireless/ath/ath12k/hal.c | 10 ++++++++++
drivers/net/wireless/ath/ath12k/mac.c | 19 +++++++++++++++++--
drivers/net/wireless/ath/ath12k/peer.c | 6 ++++++
5 files changed, 36 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
index 742d4fd1b598..5c3883b19da1 100644
--- a/drivers/net/wireless/ath/ath12k/core.c
+++ b/drivers/net/wireless/ath/ath12k/core.c
@@ -1410,8 +1410,6 @@ static int ath12k_core_reconfigure_on_crash(struct ath12k_base *ab)
if (ret)
goto err_hal_srng_deinit;
- clear_bit(ATH12K_FLAG_RECOVERY, &ab->dev_flags);
-
return 0;
err_hal_srng_deinit:
diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c
index 8fa0e90b4531..473855ded8a7 100644
--- a/drivers/net/wireless/ath/ath12k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath12k/dp_rx.c
@@ -751,6 +751,9 @@ int ath12k_dp_rx_ampdu_stop(struct ath12k *ar,
lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy);
+ if (test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags))
+ return 0;
+
arsta = wiphy_dereference(ath12k_ar_to_hw(ar)->wiphy,
ahsta->link[link_id]);
if (!arsta)
diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c
index 071cb5d30931..6d3f4bca46a3 100644
--- a/drivers/net/wireless/ath/ath12k/hal.c
+++ b/drivers/net/wireless/ath/ath12k/hal.c
@@ -376,6 +376,9 @@ int ath12k_hal_srng_src_num_free(struct ath12k_base *ab, struct hal_srng *srng,
lockdep_assert_held(&srng->lock);
+ if (unlikely(test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)))
+ return 0;
+
hp = srng->u.src_ring.hp;
if (sync_hw_ptr) {
@@ -419,6 +422,9 @@ void *ath12k_hal_srng_src_get_next_entry(struct ath12k_base *ab,
lockdep_assert_held(&srng->lock);
+ if (unlikely(test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)))
+ return NULL;
+
/* TODO: Using % is expensive, but we have to do this since size of some
* SRNG rings is not power of 2 (due to descriptor sizes). Need to see
* if separate function is defined for rings having power of 2 ring size
@@ -524,6 +530,10 @@ void ath12k_hal_srng_access_end(struct ath12k_base *ab, struct hal_srng *srng)
{
lockdep_assert_held(&srng->lock);
+ if (srng->ring_dir == HAL_SRNG_DIR_SRC &&
+ unlikely(test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)))
+ return;
+
if (srng->flags & HAL_SRNG_FLAGS_LMAC_RING) {
/* For LMAC rings, ring pointer updates are done through FW and
* hence written to a shared memory location that is read by FW
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index 51c4df32e716..c559ced9e524 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -4278,6 +4278,9 @@ ath12k_mac_op_change_vif_links(struct ieee80211_hw *hw,
lockdep_assert_wiphy(hw->wiphy);
+ if (old_links && test_bit(ATH12K_FLAG_RECOVERY, &ah->radio[0].ab->dev_flags))
+ return -EINVAL;
+
ath12k_generic_dbg(ATH12K_DBG_MAC,
"mac vif link changed for MLD %pM old_links 0x%x new_links 0x%x\n",
vif->addr, old_links, new_links);
@@ -5956,6 +5959,9 @@ static int ath12k_clear_peer_keys(struct ath12k_link_vif *arvif,
lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy);
+ if (test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags))
+ return 0;
+
spin_lock_bh(&dp->dp_lock);
peer = ath12k_dp_link_peer_find_by_vdev_and_addr(dp, arvif->vdev_id, addr);
if (!peer || !peer->dp_peer) {
@@ -6031,6 +6037,8 @@ static int ath12k_mac_set_key(struct ath12k *ar, enum set_key_cmd cmd,
spin_unlock_bh(&dp->dp_lock);
if (cmd == SET_KEY) {
+ if (test_bit(ATH12K_FLAG_RECOVERY, &ab->dev_flags))
+ return 0;
ath12k_warn(ab, "cannot install key for non-existent peer %pM\n",
peer_addr);
return -EOPNOTSUPP;
@@ -7045,7 +7053,8 @@ static int ath12k_mac_station_remove(struct ath12k *ar,
wiphy_work_cancel(ar->ah->hw->wiphy, &arsta->update_wk);
- if (ahvif->vdev_type == WMI_VDEV_TYPE_STA) {
+ if (ahvif->vdev_type == WMI_VDEV_TYPE_STA &&
+ !test_bit(ATH12K_FLAG_CRASH_FLUSH, &ar->ab->dev_flags)) {
ath12k_bss_disassoc(ar, arvif);
ret = ath12k_mac_vdev_stop(arvif);
if (ret)
@@ -7795,7 +7804,8 @@ int ath12k_mac_op_sta_state(struct ieee80211_hw *hw,
* about to move to the associated state.
*/
if (ieee80211_vif_is_mld(vif) && vif->type == NL80211_IFTYPE_STATION &&
- old_state == IEEE80211_STA_AUTH && new_state == IEEE80211_STA_ASSOC) {
+ old_state == IEEE80211_STA_AUTH && new_state == IEEE80211_STA_ASSOC &&
+ !test_bit(ATH12K_FLAG_RECOVERY, &ah->radio[0].ab->dev_flags)) {
/* TODO: for now only do link selection for single device
* MLO case. Other cases would be handled in the future.
*/
@@ -12596,6 +12606,9 @@ static int ath12k_mac_flush(struct ath12k *ar)
long time_left;
int ret = 0;
+ if (test_bit(ATH12K_FLAG_CRASH_FLUSH, &ar->ab->dev_flags))
+ return -ESHUTDOWN;
+
time_left = wait_event_timeout(ar->dp.tx_empty_waitq,
(atomic_read(&ar->dp.num_tx_pending) == 0),
ATH12K_FLUSH_TIMEOUT);
@@ -13494,6 +13507,8 @@ ath12k_mac_op_reconfig_complete(struct ieee80211_hw *hw,
for_each_ar(ah, ar, i) {
ab = ar->ab;
+ clear_bit(ATH12K_FLAG_RECOVERY, &ab->dev_flags);
+
ath12k_warn(ar->ab, "pdev %d successfully recovered\n",
ar->pdev->pdev_id);
diff --git a/drivers/net/wireless/ath/ath12k/peer.c b/drivers/net/wireless/ath/ath12k/peer.c
index 2681a047d4d5..e96eb78bbc0c 100644
--- a/drivers/net/wireless/ath/ath12k/peer.c
+++ b/drivers/net/wireless/ath/ath12k/peer.c
@@ -297,6 +297,12 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st
if (!sta->mlo)
return -EINVAL;
+ /* During firmware crash, peers are already gone. Skip WMI peer delete
+ * to avoid timeouts that delay recovery and can trigger cascading resets.
+ */
+ if (test_bit(ATH12K_FLAG_CRASH_FLUSH, &ah->radio[0].ab->dev_flags))
+ return 0;
+
/* FW expects delete of all link peers at once before waiting for reception
* of peer unmap or delete responses
*/
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 2/5] wifi: ath12k: prevent scan during firmware recovery
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-27 16:27 ` Jose Ignacio Tornos Martinez
2026-07-27 16:27 ` [PATCH 3/5] wifi: ath12k: fix MLO dp_peer ID desync with firmware Jose Ignacio Tornos Martinez
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-07-27 16:27 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
When firmware crashes while wpa_supplicant has an active connection,
wpa_supplicant may immediately attempt a scan via nl80211 while the
firmware is still powering on through MHI. ath12k_mac_op_hw_scan()
proceeds without checking the recovery state, accessing partially
initialized radio structures which leads to a NULL pointer dereference:
BUG: unable to handle page fault for address: 0000000000001508
RIP: ath12k_mac_op_hw_scan+0x148/0x2b0 [ath12k]
Call Trace:
drv_hw_scan+0x88/0x140 [mac80211]
__ieee80211_start_scan+0x2bc/0x6b0 [mac80211]
nl80211_trigger_scan+0x54a/0x9f0 [cfg80211]
Fix by checking ATH12K_FLAG_RECOVERY at the start of
ath12k_mac_op_hw_scan() and returning -EBUSY. mac80211 will retry the
scan after the recovery completes.
Tested on WCN7850 with MLO (Wi-Fi 7).
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
drivers/net/wireless/ath/ath12k/mac.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index f33976fc1233..924c3c21ca78 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -5707,6 +5707,9 @@ int ath12k_mac_op_hw_scan(struct ieee80211_hw *hw,
lockdep_assert_wiphy(hw->wiphy);
+ if (test_bit(ATH12K_FLAG_RECOVERY, &ah->radio[0].ab->dev_flags))
+ return -EBUSY;
+
chan_list = kzalloc_objs(*chan_list, hw_req->req.n_channels);
if (!chan_list)
return -ENOMEM;
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 3/5] wifi: ath12k: fix MLO dp_peer ID desync with firmware
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-27 16:27 ` [PATCH 2/5] wifi: ath12k: prevent scan during firmware recovery Jose Ignacio Tornos Martinez
@ 2026-07-27 16:27 ` 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-27 16:27 ` [PATCH 5/5] wifi: ath12k: skip connection_loss_work for MLO beacon miss Jose Ignacio Tornos Martinez
4 siblings, 0 replies; 6+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-07-27 16:27 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
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
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 4/5] wifi: ath12k: fix MLO beacon handling using per-link addressing
2026-07-27 16:27 [PATCH 0/5] wifi: ath12k: fixes to improve MLO station stability Jose Ignacio Tornos Martinez
` (2 preceding siblings ...)
2026-07-27 16:27 ` [PATCH 3/5] wifi: ath12k: fix MLO dp_peer ID desync with firmware Jose Ignacio Tornos Martinez
@ 2026-07-27 16:27 ` 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
4 siblings, 0 replies; 6+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-07-27 16:27 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
ath12k_mac_handle_beacon_iter() uses ahvif->deflink for both BSSID
matching and connection_loss_work cancellation. In MLO, deflink is
only the first link created for the MLD VIF and does not represent
the other links. This causes two problems:
1. Beacon BSSID matching only checks deflink's BSS config
(vif->bss_conf.bssid), so beacons received on non-deflink links
never match and never cancel connection_loss_work.
2. Only deflink's connection_loss_work is cancelled, leaving
non-deflink connection_loss_work timers running even when beacons
are being received normally.
When the firmware reports a beacon miss event on any link,
ath12k_mac_handle_beacon_miss() queues connection_loss_work on that
link with a 3-second timeout. If a beacon is received before the
timeout, ath12k_mac_handle_beacon_iter() should cancel it. But the
deflink-only handling means that for non-deflink MLO links, beacons
are never matched and connection_loss_work is never cancelled through
this path.
Fix by handling non-MLO and MLO cases separately. For non-MLO, keep
the existing deflink behavior. For MLO, iterate all active links to
match the beacon BSSID against each link's BSS config. When a match
is found, cancel connection_loss_work on all links because the
work callback calls per-VIF ieee80211_connection_loss() regardless
of which link queued it.
Tested on WCN7850 with MLO (Wi-Fi 7).
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
drivers/net/wireless/ath/ath12k/mac.c | 36 ++++++++++++++++++++++++---
1 file changed, 32 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index e36a37852fab..ebc35636b4ef 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -1946,15 +1946,43 @@ static void ath12k_mac_handle_beacon_iter(void *data, u8 *mac,
struct sk_buff *skb = data;
struct ieee80211_mgmt *mgmt = (void *)skb->data;
struct ath12k_vif *ahvif = ath12k_vif_to_ahvif(vif);
- struct ath12k_link_vif *arvif = &ahvif->deflink;
+ struct ieee80211_bss_conf *link_conf;
+ struct ath12k_link_vif *arvif;
+ unsigned long links;
+ u8 link_id;
- if (vif->type != NL80211_IFTYPE_STATION || !arvif->is_created)
+ if (vif->type != NL80211_IFTYPE_STATION)
return;
- if (!ether_addr_equal(mgmt->bssid, vif->bss_conf.bssid))
+ if (!ieee80211_vif_is_mld(vif)) {
+ arvif = &ahvif->deflink;
+ if (arvif->is_created &&
+ ether_addr_equal(mgmt->bssid, vif->bss_conf.bssid))
+ cancel_delayed_work(&arvif->connection_loss_work);
return;
+ }
- cancel_delayed_work(&arvif->connection_loss_work);
+ /* For MLO, each link has a different AP BSSID. Check the beacon
+ * against all link BSS configs. If any matches, cancel
+ * connection_loss_work on all links since it calls per-VIF
+ * ieee80211_connection_loss() regardless of which link queued it.
+ */
+ links = ahvif->links_map;
+ for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
+ link_conf = rcu_dereference(vif->link_conf[link_id]);
+ if (link_conf &&
+ ether_addr_equal(mgmt->bssid, link_conf->bssid))
+ goto found;
+ }
+
+ return;
+
+found:
+ for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
+ arvif = rcu_dereference(ahvif->link[link_id]);
+ if (arvif && arvif->is_created)
+ cancel_delayed_work(&arvif->connection_loss_work);
+ }
}
void ath12k_mac_handle_beacon(struct ath12k *ar, struct sk_buff *skb)
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 5/5] wifi: ath12k: skip connection_loss_work for MLO beacon miss
2026-07-27 16:27 [PATCH 0/5] wifi: ath12k: fixes to improve MLO station stability Jose Ignacio Tornos Martinez
` (3 preceding siblings ...)
2026-07-27 16:27 ` [PATCH 4/5] wifi: ath12k: fix MLO beacon handling using per-link addressing Jose Ignacio Tornos Martinez
@ 2026-07-27 16:27 ` Jose Ignacio Tornos Martinez
4 siblings, 0 replies; 6+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-07-27 16:27 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
When firmware reports a beacon miss, ath12k queues connection_loss_work
as a 3-second backup timer that forces a full VIF disconnect via
ieee80211_connection_loss(). This work can only be cancelled when a
beacon frame arrives through WMI in ath12k_mac_handle_beacon_iter().
In MLO, this mechanism causes periodic spurious disconnections because
the firmware does not reliably forward beacon frames to the host via
WMI, making the cancellation path ineffective. The result is a full
VIF disconnect every ~16 seconds during normal MLO operation, easily
reproducible under heavy traffic.
Skip queueing connection_loss_work for MLO VIFs. The mac80211 AP probe
triggered by ieee80211_beacon_loss() is sufficient to detect real AP
unreachability. Other MLO-capable drivers such as mt76 and rtw89
similarly rely on ieee80211_beacon_loss() without adding a backup
disconnect timer.
Tested on WCN7850 with MLO (Wi-Fi 7).
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
drivers/net/wireless/ath/ath12k/mac.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index af09153ad9d9..dfd69822c5ce 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -2008,9 +2008,16 @@ void ath12k_mac_handle_beacon_miss(struct ath12k *ar,
* (done by mac80211) succeeds but beacons do not resume then it
* doesn't make sense to continue operation. Queue connection loss work
* which can be cancelled when beacon is received.
+ *
+ * Skip for MLO because connection_loss_work disconnects the entire
+ * VIF based on a single link's beacon miss, and its cancellation
+ * mechanism (beacon reception via WMI) is unreliable. The mac80211
+ * probe triggered by ieee80211_beacon_loss() above is sufficient
+ * to detect real AP unreachability.
*/
- ieee80211_queue_delayed_work(hw, &arvif->connection_loss_work,
- ATH12K_CONNECTION_LOSS_HZ);
+ if (!ieee80211_vif_is_mld(vif))
+ ieee80211_queue_delayed_work(hw, &arvif->connection_loss_work,
+ ATH12K_CONNECTION_LOSS_HZ);
}
static void ath12k_mac_vif_sta_connection_loss_work(struct work_struct *work)
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-27 16:28 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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-27 16:27 ` [PATCH 2/5] wifi: ath12k: prevent scan during firmware recovery Jose Ignacio Tornos Martinez
2026-07-27 16:27 ` [PATCH 3/5] wifi: ath12k: fix MLO dp_peer ID desync with firmware 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-27 16:27 ` [PATCH 5/5] wifi: ath12k: skip connection_loss_work for MLO beacon miss Jose Ignacio Tornos Martinez
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox