Linux wireless drivers development
 help / color / mirror / Atom feed
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 v2 1/3] wifi: ath12k: fix MLO station firmware crash recovery
Date: Tue,  4 Aug 2026 19:49:59 +0200	[thread overview]
Message-ID: <20260804175004.1761075-2-jtornosm@redhat.com> (raw)
In-Reply-To: <20260804175004.1761075-1-jtornosm@redhat.com>

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.

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, and peer_mlo_link_peers_delete 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>
---
v2: Rebase and address comments from Baochen Qiang:
    - Remove CRASH_FLUSH guards from HAL srng source ring helpers
    (ath12k_hal_srng_src_num_free, ath12k_hal_srng_src_get_next_entry,
    ath12k_hal_srng_access_end). They were a layering violation — the
    HAL should not know about device crash semantics. They were added
    as a precautionary measure to abort as soon as possible, but
    ath12k_wifi7_dp_tx() already has a CRASH_FLUSH check before any
    HAL call.
    - Fix radio[0] usage in change_vif_links, sta_state, and
    peer_mlo_link_peers_delete to use per-radio ab.
v1: https://lore.kernel.org/all/20260727162748.963275-2-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/mac.c   | 27 ++++++++++++++++++++++++-
 drivers/net/wireless/ath/ath12k/peer.c  |  6 ++++++
 4 files changed, 35 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
index 262a2045309b..5c0ee74808cd 100644
--- a/drivers/net/wireless/ath/ath12k/core.c
+++ b/drivers/net/wireless/ath/ath12k/core.c
@@ -1391,8 +1391,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/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index 99bf5cf79d10..7b7110ca6428 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -4341,12 +4341,22 @@ ath12k_mac_op_change_vif_links(struct ieee80211_hw *hw,
 	struct ath12k_vif *ahvif = ath12k_vif_to_ahvif(vif);
 	unsigned long to_remove = old_links & ~new_links;
 	unsigned long to_add = ~old_links & new_links;
+	unsigned long existing = old_links;
 	struct ath12k_hw *ah = ath12k_hw_to_ah(hw);
 	struct ath12k_link_vif *arvif;
 	u8 link_id;
 
 	lockdep_assert_wiphy(hw->wiphy);
 
+	if (old_links) {
+		for_each_set_bit(link_id, &existing, IEEE80211_MLD_MAX_NUM_LINKS) {
+			arvif = wiphy_dereference(hw->wiphy, ahvif->link[link_id]);
+			if (arvif && arvif->ar &&
+			    test_bit(ATH12K_FLAG_RECOVERY, &arvif->ar->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);
@@ -6025,6 +6035,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) {
@@ -6100,6 +6113,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;
@@ -7100,7 +7115,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)
@@ -7861,6 +7877,10 @@ int ath12k_mac_op_sta_state(struct ieee80211_hw *hw,
 		 * MLO case. Other cases would be handled in the future.
 		 */
 		ab = ah->radio[0].ab;
+		if (test_bit(ATH12K_FLAG_RECOVERY, &ab->dev_flags)) {
+			ret = 0;
+			goto exit;
+		}
 		if (ab->ag->num_devices == 1) {
 			ret = ath12k_mac_select_links(ab, vif, hw, &selected_links);
 			if (ret) {
@@ -12661,6 +12681,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);
@@ -13559,6 +13582,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 80edebf0e364..d64e62192cd7 100644
--- a/drivers/net/wireless/ath/ath12k/peer.c
+++ b/drivers/net/wireless/ath/ath12k/peer.c
@@ -376,12 +376,18 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st
 	unsigned long links;
 	struct ath12k *ar;
 	u8 link_id;
+	int i;
 
 	lockdep_assert_wiphy(ah->hw->wiphy);
 
 	if (!sta->mlo)
 		return -EINVAL;
 
+	for_each_ar(ah, ar, i) {
+		if (test_bit(ATH12K_FLAG_CRASH_FLUSH, &ar->ab->dev_flags))
+			return 0;
+	}
+
 	struct ath12k_peer_delete_wait *waits __free(kfree) =
 				kzalloc_objs(*waits, IEEE80211_MLD_MAX_NUM_LINKS);
 	if (!waits)
-- 
2.54.0


  reply	other threads:[~2026-08-04 17:50 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-04 17:49 [PATCH v2 0/3] wifi: ath12k: fixes to improve MLO station stability Jose Ignacio Tornos Martinez
2026-08-04 17:49 ` Jose Ignacio Tornos Martinez [this message]
2026-08-04 17:50 ` [PATCH v2 2/3] wifi: ath12k: prevent scan during firmware recovery Jose Ignacio Tornos Martinez
2026-08-04 17:50 ` [PATCH v2 3/3] wifi: ath12k: skip connection_loss_work for MLO beacon miss 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=20260804175004.1761075-2-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox