Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH 06/13] wifi: mt76: mt7925: add comprehensive NULL pointer protection for MLO
From: Zac @ 2026-01-20 20:10 UTC (permalink / raw)
  To: sean.wang
  Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
	linux, lorenzo, nbd, ryder.lee, sean.wang, zac, zbowling
In-Reply-To: <20260120201043.38225-1-zac@zacbowling.com>

From: Zac Bowling <zac@zacbowling.com>

Add NULL pointer checks for functions that return pointers to link-related
structures throughout the mt7925 driver. During MLO state transitions,
these functions can return NULL when link configuration is not synchronized.

Functions protected:
- mt792x_vif_to_bss_conf(): Returns link BSS configuration
- mt792x_vif_to_link(): Returns driver link state
- mt792x_sta_to_link(): Returns station link state

Files updated:

1. mac.c:
   - mt7925_vif_connect_iter(): Check bss_conf before use
   - mt7925_mac_sta_assoc(): Check bss_conf before use

2. main.c:
   - mt7925_set_key(): Check link_conf and mlink
   - mt7925_mac_link_sta_add(): Check link_conf and mlink
   - mt7925_mac_link_sta_assoc(): Check bss_conf and mlink
   - mt7925_mac_link_sta_remove(): Check bss_conf and mlink
   - mt7925_change_vif_links(): Check conf before use
   - mt7925_assign_vif_chanctx(): Check mconf and mlink
   - mt7925_unassign_vif_chanctx(): Check mconf and mlink
   - mt7925_mgd_prepare_tx(): Check link_conf

3. mcu.c:
   - mt7925_mcu_sta_phy_tlv(): Check link_sta
   - mt7925_mcu_sta_amsdu_tlv(): Check link_sta
   - mt7925_mcu_sta_mld_tlv(): Check link_sta
   - mt7925_mcu_sta_cmd(): Check mlink
   - mt7925_mcu_add_bss_info(): Check link_conf
   - mt7925_mcu_set_chctx(): Check link_conf and mlink

Prevents crashes during:
- BSSID roaming transitions
- MLO setup and teardown
- Hardware reset operations
- Runtime power management

Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 device")
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
 .../net/wireless/mediatek/mt76/mt7925/mac.c   |  6 ++
 .../net/wireless/mediatek/mt76/mt7925/main.c  | 82 ++++++++++++++++---
 .../net/wireless/mediatek/mt76/mt7925/mcu.c   | 22 ++++-
 3 files changed, 97 insertions(+), 13 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index 871b67101976..184efe8afa10 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
@@ -1271,6 +1271,12 @@ mt7925_vif_connect_iter(void *priv, u8 *mac,
 		bss_conf = mt792x_vif_to_bss_conf(vif, i);
 		mconf = mt792x_vif_to_link(mvif, i);
 
+		/* Skip links that don't have bss_conf set up yet in mac80211.
+		 * This can happen during HW reset when link state is inconsistent.
+		 */
+		if (!bss_conf)
+			continue;
+
 		mt76_connac_mcu_uni_add_dev(&dev->mphy, bss_conf, &mconf->mt76,
 					    &mvif->sta.deflink.wcid, true);
 		mt7925_mcu_set_tx(dev, bss_conf);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 05990455ee7d..74a48742e234 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -608,6 +608,10 @@ static int mt7925_set_link_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
 	link_sta = sta ? mt792x_sta_to_link_sta(vif, sta, link_id) : NULL;
 	mconf = mt792x_vif_to_link(mvif, link_id);
 	mlink = mt792x_sta_to_link(msta, link_id);
+
+	if (!link_conf || !mconf || !mlink)
+		return -EINVAL;
+
 	wcid = &mlink->wcid;
 	wcid_keyidx = &wcid->hw_key_idx;
 
@@ -860,12 +864,17 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
 
 	msta = (struct mt792x_sta *)link_sta->sta->drv_priv;
 	mlink = mt792x_sta_to_link(msta, link_id);
+	if (!mlink)
+		return -EINVAL;
 
 	idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT792x_WTBL_STA - 1);
 	if (idx < 0)
 		return -ENOSPC;
 
 	mconf = mt792x_vif_to_link(mvif, link_id);
+	if (!mconf)
+		return -EINVAL;
+
 	mt76_wcid_init(&mlink->wcid, 0);
 	mlink->wcid.sta = 1;
 	mlink->wcid.idx = idx;
@@ -891,6 +900,8 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
 			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
 
 	link_conf = mt792x_vif_to_bss_conf(vif, link_id);
+	if (!link_conf)
+		return -EINVAL;
 
 	/* should update bss info before STA add */
 	if (vif->type == NL80211_IFTYPE_STATION && !link_sta->sta->tdls) {
@@ -997,18 +1008,29 @@ mt7925_mac_set_links(struct mt76_dev *mdev, struct ieee80211_vif *vif)
 {
 	struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
 	struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
-	struct ieee80211_bss_conf *link_conf =
-		mt792x_vif_to_bss_conf(vif, mvif->deflink_id);
-	struct cfg80211_chan_def *chandef = &link_conf->chanreq.oper;
-	enum nl80211_band band = chandef->chan->band, secondary_band;
+	struct ieee80211_bss_conf *link_conf;
+	struct cfg80211_chan_def *chandef;
+	enum nl80211_band band, secondary_band;
+	u16 sel_links;
+	u8 secondary_link_id;
 
-	u16 sel_links = mt76_select_links(vif, 2);
-	u8 secondary_link_id = __ffs(~BIT(mvif->deflink_id) & sel_links);
+	link_conf = mt792x_vif_to_bss_conf(vif, mvif->deflink_id);
+	if (!link_conf)
+		return;
+
+	chandef = &link_conf->chanreq.oper;
+	band = chandef->chan->band;
+
+	sel_links = mt76_select_links(vif, 2);
+	secondary_link_id = __ffs(~BIT(mvif->deflink_id) & sel_links);
 
 	if (!ieee80211_vif_is_mld(vif) || hweight16(sel_links) < 2)
 		return;
 
 	link_conf = mt792x_vif_to_bss_conf(vif, secondary_link_id);
+	if (!link_conf)
+		return;
+
 	secondary_band = link_conf->chanreq.oper.chan->band;
 
 	if (band == NL80211_BAND_2GHZ ||
@@ -1036,6 +1058,8 @@ static void mt7925_mac_link_sta_assoc(struct mt76_dev *mdev,
 
 	msta = (struct mt792x_sta *)link_sta->sta->drv_priv;
 	mlink = mt792x_sta_to_link(msta, link_sta->link_id);
+	if (!mlink)
+		return;
 
 	mt792x_mutex_acquire(dev);
 
@@ -1045,12 +1069,13 @@ static void mt7925_mac_link_sta_assoc(struct mt76_dev *mdev,
 		link_conf = mt792x_vif_to_bss_conf(vif, vif->bss_conf.link_id);
 	}
 
-	if (vif->type == NL80211_IFTYPE_STATION && !link_sta->sta->tdls) {
+	if (link_conf && vif->type == NL80211_IFTYPE_STATION && !link_sta->sta->tdls) {
 		struct mt792x_bss_conf *mconf;
 
 		mconf = mt792x_link_conf_to_mconf(link_conf);
-		mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
-					link_conf, link_sta, true);
+		if (mconf)
+			mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
+						link_conf, link_sta, true);
 	}
 
 	ewma_avg_signal_init(&mlink->avg_ack_signal);
@@ -1097,6 +1122,8 @@ static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev,
 
 	msta = (struct mt792x_sta *)link_sta->sta->drv_priv;
 	mlink = mt792x_sta_to_link(msta, link_id);
+	if (!mlink)
+		return;
 
 	mt7925_roc_abort_sync(dev);
 
@@ -1110,10 +1137,12 @@ static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev,
 
 	link_conf = mt792x_vif_to_bss_conf(vif, link_id);
 
-	if (vif->type == NL80211_IFTYPE_STATION && !link_sta->sta->tdls) {
+	if (link_conf && vif->type == NL80211_IFTYPE_STATION && !link_sta->sta->tdls) {
 		struct mt792x_bss_conf *mconf;
 
 		mconf = mt792x_link_conf_to_mconf(link_conf);
+		if (!mconf)
+			goto out;
 
 		if (ieee80211_vif_is_mld(vif))
 			mt792x_mac_link_bss_remove(dev, mconf, mlink);
@@ -1121,6 +1150,7 @@ static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev,
 			mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx, link_conf,
 						link_sta, false);
 	}
+out:
 
 	spin_lock_bh(&mdev->sta_poll_lock);
 	if (!list_empty(&mlink->wcid.poll_list))
@@ -1308,6 +1338,8 @@ mt7925_mlo_pm_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
 	mt792x_mutex_acquire(dev);
 	for_each_set_bit(i, &valid, IEEE80211_MLD_MAX_NUM_LINKS) {
 		bss_conf = mt792x_vif_to_bss_conf(vif, i);
+		if (!bss_conf)
+			continue;
 		mt7925_mcu_uni_bss_ps(dev, bss_conf);
 	}
 	mt792x_mutex_release(dev);
@@ -1634,6 +1666,8 @@ static void mt7925_ipv6_addr_change(struct ieee80211_hw *hw,
 
 	for_each_set_bit(i, &valid, IEEE80211_MLD_MAX_NUM_LINKS) {
 		bss_conf = mt792x_vif_to_bss_conf(vif, i);
+		if (!bss_conf)
+			continue;
 		__mt7925_ipv6_addr_change(hw, bss_conf, idev);
 	}
 }
@@ -1695,6 +1729,9 @@ mt7925_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 		    [IEEE80211_AC_BK] = 1,
 	};
 
+	if (!mconf)
+		return -EINVAL;
+
 	/* firmware uses access class index */
 	mconf->queue_params[mq_to_aci[queue]] = *params;
 
@@ -1865,6 +1902,8 @@ static void mt7925_vif_cfg_changed(struct ieee80211_hw *hw,
 	if (changed & BSS_CHANGED_ARP_FILTER) {
 		for_each_set_bit(i, &valid, IEEE80211_MLD_MAX_NUM_LINKS) {
 			bss_conf = mt792x_vif_to_bss_conf(vif, i);
+			if (!bss_conf)
+				continue;
 			mt7925_mcu_update_arp_filter(&dev->mt76, bss_conf);
 		}
 	}
@@ -1880,6 +1919,8 @@ static void mt7925_vif_cfg_changed(struct ieee80211_hw *hw,
 			} else if (mvif->mlo_pm_state == MT792x_MLO_CHANGED_PS) {
 				for_each_set_bit(i, &valid, IEEE80211_MLD_MAX_NUM_LINKS) {
 					bss_conf = mt792x_vif_to_bss_conf(vif, i);
+					if (!bss_conf)
+						continue;
 					mt7925_mcu_uni_bss_ps(dev, bss_conf);
 				}
 			}
@@ -1901,7 +1942,12 @@ static void mt7925_link_info_changed(struct ieee80211_hw *hw,
 	struct ieee80211_bss_conf *link_conf;
 
 	mconf = mt792x_vif_to_link(mvif, info->link_id);
+	if (!mconf)
+		return;
+
 	link_conf = mt792x_vif_to_bss_conf(vif, mconf->link_id);
+	if (!link_conf)
+		return;
 
 	mt792x_mutex_acquire(dev);
 
@@ -2025,6 +2071,11 @@ mt7925_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 		mlink = mlinks[link_id];
 		link_conf = mt792x_vif_to_bss_conf(vif, link_id);
 
+		if (!link_conf) {
+			err = -EINVAL;
+			goto free;
+		}
+
 		rcu_assign_pointer(mvif->link_conf[link_id], mconf);
 		rcu_assign_pointer(mvif->sta.link[link_id], mlink);
 
@@ -2105,9 +2156,14 @@ static int mt7925_assign_vif_chanctx(struct ieee80211_hw *hw,
 
 	if (ieee80211_vif_is_mld(vif)) {
 		mconf = mt792x_vif_to_link(mvif, link_conf->link_id);
+		if (!mconf) {
+			mutex_unlock(&dev->mt76.mutex);
+			return -EINVAL;
+		}
+
 		pri_link_conf = mt792x_vif_to_bss_conf(vif, mvif->deflink_id);
 
-		if (vif->type == NL80211_IFTYPE_STATION &&
+		if (pri_link_conf && vif->type == NL80211_IFTYPE_STATION &&
 		    mconf == &mvif->bss_conf)
 			mt7925_mcu_add_bss_info(&dev->phy, NULL, pri_link_conf,
 						NULL, true);
@@ -2136,6 +2192,10 @@ static void mt7925_unassign_vif_chanctx(struct ieee80211_hw *hw,
 
 	if (ieee80211_vif_is_mld(vif)) {
 		mconf = mt792x_vif_to_link(mvif, link_conf->link_id);
+		if (!mconf) {
+			mutex_unlock(&dev->mt76.mutex);
+			return;
+		}
 
 		if (vif->type == NL80211_IFTYPE_STATION &&
 		    mconf == &mvif->bss_conf)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index cf0fdea45cf7..94ec62a4538a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -1087,6 +1087,8 @@ mt7925_mcu_sta_hdr_trans_tlv(struct sk_buff *skb,
 		struct mt792x_link_sta *mlink;
 
 		mlink = mt792x_sta_to_link(msta, link_sta->link_id);
+		if (!mlink)
+			return;
 		wcid = &mlink->wcid;
 	} else {
 		wcid = &mvif->sta.deflink.wcid;
@@ -1120,6 +1122,9 @@ int mt7925_mcu_wtbl_update_hdr_trans(struct mt792x_dev *dev,
 	link_sta = mt792x_sta_to_link_sta(vif, sta, link_id);
 	mconf = mt792x_vif_to_link(mvif, link_id);
 
+	if (!mlink || !mconf)
+		return -EINVAL;
+
 	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76,
 					      &mlink->wcid,
 					      MT7925_STA_UPDATE_MAX_SIZE);
@@ -1741,6 +1746,8 @@ mt7925_mcu_sta_amsdu_tlv(struct sk_buff *skb,
 	amsdu->amsdu_en = true;
 
 	mlink = mt792x_sta_to_link(msta, link_sta->link_id);
+	if (!mlink)
+		return;
 	mlink->wcid.amsdu = true;
 
 	switch (link_sta->agg.max_amsdu_len) {
@@ -1773,6 +1780,10 @@ mt7925_mcu_sta_phy_tlv(struct sk_buff *skb,
 
 	link_conf = mt792x_vif_to_bss_conf(vif, link_sta->link_id);
 	mconf = mt792x_vif_to_link(mvif, link_sta->link_id);
+
+	if (!link_conf || !mconf)
+		return;
+
 	chandef = mconf->mt76.ctx ? &mconf->mt76.ctx->def :
 				    &link_conf->chanreq.oper;
 
@@ -1851,6 +1862,10 @@ mt7925_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb,
 
 	link_conf = mt792x_vif_to_bss_conf(vif, link_sta->link_id);
 	mconf = mt792x_vif_to_link(mvif, link_sta->link_id);
+
+	if (!link_conf || !mconf)
+		return;
+
 	chandef = mconf->mt76.ctx ? &mconf->mt76.ctx->def :
 				    &link_conf->chanreq.oper;
 	band = chandef->chan->band;
@@ -1935,6 +1950,9 @@ mt7925_mcu_sta_mld_tlv(struct sk_buff *skb,
 
 		mconf = mt792x_vif_to_link(mvif, i);
 		mlink = mt792x_sta_to_link(msta, i);
+		if (!mconf || !mlink)
+			continue;
+
 		mld->link[cnt].wlan_id = cpu_to_le16(mlink->wcid.idx);
 		mld->link[cnt++].bss_idx = mconf->mt76.idx;
 
@@ -2027,13 +2045,13 @@ int mt7925_mcu_sta_update(struct mt792x_dev *dev,
 		.rcpi = to_rcpi(rssi),
 	};
 	struct mt792x_sta *msta;
-	struct mt792x_link_sta *mlink;
+	struct mt792x_link_sta *mlink = NULL;
 
 	if (link_sta) {
 		msta = (struct mt792x_sta *)link_sta->sta->drv_priv;
 		mlink = mt792x_sta_to_link(msta, link_sta->link_id);
 	}
-	info.wcid = link_sta ? &mlink->wcid : &mvif->sta.deflink.wcid;
+	info.wcid = (link_sta && mlink) ? &mlink->wcid : &mvif->sta.deflink.wcid;
 	info.newly = state != MT76_STA_INFO_STATE_ASSOC;
 
 	return mt7925_mcu_sta_cmd(&dev->mphy, &info);
-- 
2.52.0


^ permalink raw reply related

* [PATCH 05/13] wifi: mt76: mt7921: fix deadlock in sta removal and suspend ROC abort
From: Zac @ 2026-01-20 20:10 UTC (permalink / raw)
  To: sean.wang
  Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
	linux, lorenzo, nbd, ryder.lee, sean.wang, zac, zbowling
In-Reply-To: <20260120201043.38225-1-zac@zacbowling.com>

From: Zac Bowling <zac@zacbowling.com>

Fix deadlock scenarios in mt7921 ROC (Remain On Channel) abort paths:

1. Suspend path deadlock (pci.c, sdio.c):
   - Previous fix (b74d48c46f) added mutex around mt7921_roc_abort_sync
   - But roc_work acquires mutex, so cancel_work_sync can deadlock
   - Fix: Remove mutex wrappers since mt7921_roc_abort_sync doesn't
     actually need them (it only calls timer_delete_sync, cancel_work_sync,
     and ieee80211_iterate_interfaces which handles its own locking)

2. sta_remove path deadlock:
   - mt7921_mac_sta_remove is called from mt76_sta_remove which holds mutex
   - Calling mt7921_roc_abort_sync → cancel_work_sync can deadlock if
     roc_work is waiting for the mutex
   - Fix: Add mt7921_roc_abort_async (matching mt7925 pattern) that sets
     abort flag and schedules work instead of blocking
   - Add abort flag checking in mt7921_roc_work to handle async abort

The fix mirrors the mt7925 implementation which already handles these
scenarios correctly.

Fixes: b74d48c46f ("wifi: mt76: mt7921: fix mutex handling in multiple paths")
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
 .../net/wireless/mediatek/mt76/mt7921/main.c  | 29 +++++++++++++++----
 .../net/wireless/mediatek/mt76/mt7921/pci.c   |  2 --
 .../net/wireless/mediatek/mt76/mt7921/sdio.c  |  2 --
 3 files changed, 23 insertions(+), 10 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
index 196fcb1e2e94..f3941a25fd6f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -367,17 +367,24 @@ static void mt7921_roc_iter(void *priv, u8 *mac,
 	mt7921_mcu_abort_roc(phy, mvif, phy->roc_token_id);
 }
 
+/* Async ROC abort - safe to call while holding mutex.
+ * Sets abort flag and schedules roc_work for cleanup.
+ */
+static void mt7921_roc_abort_async(struct mt792x_dev *dev)
+{
+	struct mt792x_phy *phy = &dev->phy;
+
+	set_bit(MT76_STATE_ROC_ABORT, &phy->mt76->state);
+	timer_delete(&phy->roc_timer);
+	ieee80211_queue_work(phy->mt76->hw, &phy->roc_work);
+}
+
 void mt7921_roc_abort_sync(struct mt792x_dev *dev)
 {
 	struct mt792x_phy *phy = &dev->phy;
 
 	timer_delete_sync(&phy->roc_timer);
 	cancel_work_sync(&phy->roc_work);
-	/* Note: caller must hold mutex if ieee80211_iterate_interfaces is
-	 * needed for ROC cleanup. Some call sites (like mt7921_mac_sta_remove)
-	 * already hold the mutex via mt76_sta_remove(). For suspend paths,
-	 * the mutex should be acquired before calling this function.
-	 */
 	if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
 		ieee80211_iterate_interfaces(mt76_hw(dev),
 					     IEEE80211_IFACE_ITER_RESUME_ALL,
@@ -392,6 +399,15 @@ void mt7921_roc_work(struct work_struct *work)
 	phy = (struct mt792x_phy *)container_of(work, struct mt792x_phy,
 						roc_work);
 
+	/* Check abort flag before acquiring mutex to prevent deadlock.
+	 * Only send expired callback if ROC was actually active.
+	 */
+	if (test_and_clear_bit(MT76_STATE_ROC_ABORT, &phy->mt76->state)) {
+		if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
+			ieee80211_remain_on_channel_expired(phy->mt76->hw);
+		return;
+	}
+
 	if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
 		return;
 
@@ -888,7 +904,8 @@ void mt7921_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif,
 	struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
 	struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv;
 
-	mt7921_roc_abort_sync(dev);
+	/* Async abort - caller already holds mutex */
+	mt7921_roc_abort_async(dev);
 	mt76_connac_free_pending_tx_skbs(&dev->pm, &msta->deflink.wcid);
 	mt76_connac_pm_wake(&dev->mphy, &dev->pm);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
index 9f76b334b93d..ec9686183251 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
@@ -426,9 +426,7 @@ static int mt7921_pci_suspend(struct device *device)
 	cancel_delayed_work_sync(&pm->ps_work);
 	cancel_work_sync(&pm->wake_work);
 
-	mt792x_mutex_acquire(dev);
 	mt7921_roc_abort_sync(dev);
-	mt792x_mutex_release(dev);
 
 	err = mt792x_mcu_drv_pmctrl(dev);
 	if (err < 0)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
index 92ea2811816f..3421e53dc948 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
@@ -219,9 +219,7 @@ static int mt7921s_suspend(struct device *__dev)
 	cancel_delayed_work_sync(&pm->ps_work);
 	cancel_work_sync(&pm->wake_work);
 
-	mt792x_mutex_acquire(dev);
 	mt7921_roc_abort_sync(dev);
-	mt792x_mutex_release(dev);
 
 	err = mt792x_mcu_drv_pmctrl(dev);
 	if (err < 0)
-- 
2.52.0


^ permalink raw reply related

* [PATCH 04/13] wifi: mt76: mt7921: add mutex protection in critical paths
From: Zac @ 2026-01-20 20:10 UTC (permalink / raw)
  To: sean.wang
  Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
	linux, lorenzo, nbd, ryder.lee, sean.wang, zac, zbowling
In-Reply-To: <20260120201043.38225-1-zac@zacbowling.com>

From: Zac Bowling <zac@zacbowling.com>

Add proper mutex protection for mt7921 driver operations that access
hardware state without proper synchronization. This fixes multiple race
conditions that can cause system instability.

Fixes added:

1. mac.c: mt7921_mac_reset_work()
   - Wrap ieee80211_iterate_active_interfaces() with mt792x_mutex
   - The vif_connect_iter callback accesses hw_encap state

2. main.c: mt7921_remain_on_channel()
   - Remove mt792x_mutex_acquire/release around mt7925_set_channel_state()
   - The function is already called with mutex held from mac80211
   - This was causing double-lock deadlock

3. main.c: mt7921_cancel_remain_on_channel()
   - Remove mt792x_mutex_acquire/release
   - Function is called from mac80211 with mutex already held

4. pci.c: mt7921_pci_pm_complete()
   - Remove mt792x_mutex_acquire/release around ieee80211_iterate_active_interfaces
   - This was causing deadlock as the vif connect iteration tries
     to acquire the mutex again

5. usb.c: mt7921_usb_pm_complete()
   - Same fix as pci.c for USB driver path

These changes prevent both missing mutex protection and mutex deadlocks
in the mt7921 driver.

Fixes: 5c14a5f944b9 ("wifi: mt76: mt7921: introduce remain_on_channel support")
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
 drivers/net/wireless/mediatek/mt76/mt7921/mac.c  | 2 ++
 drivers/net/wireless/mediatek/mt76/mt7921/main.c | 9 +++++++++
 drivers/net/wireless/mediatek/mt76/mt7921/pci.c  | 2 ++
 drivers/net/wireless/mediatek/mt76/mt7921/sdio.c | 2 ++
 4 files changed, 15 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
index 03b4960db73f..f5c882e45bbe 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
@@ -693,9 +693,11 @@ void mt7921_mac_reset_work(struct work_struct *work)
 	clear_bit(MT76_RESET, &dev->mphy.state);
 	pm->suspended = false;
 	ieee80211_wake_queues(hw);
+	mt792x_mutex_acquire(dev);
 	ieee80211_iterate_active_interfaces(hw,
 					    IEEE80211_IFACE_ITER_RESUME_ALL,
 					    mt7921_vif_connect_iter, NULL);
+	mt792x_mutex_release(dev);
 	mt76_connac_power_save_sched(&dev->mt76.phy, pm);
 }
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
index 5fae9a6e273c..196fcb1e2e94 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -373,6 +373,11 @@ void mt7921_roc_abort_sync(struct mt792x_dev *dev)
 
 	timer_delete_sync(&phy->roc_timer);
 	cancel_work_sync(&phy->roc_work);
+	/* Note: caller must hold mutex if ieee80211_iterate_interfaces is
+	 * needed for ROC cleanup. Some call sites (like mt7921_mac_sta_remove)
+	 * already hold the mutex via mt76_sta_remove(). For suspend paths,
+	 * the mutex should be acquired before calling this function.
+	 */
 	if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
 		ieee80211_iterate_interfaces(mt76_hw(dev),
 					     IEEE80211_IFACE_ITER_RESUME_ALL,
@@ -619,6 +624,7 @@ void mt7921_set_runtime_pm(struct mt792x_dev *dev)
 	bool monitor = !!(hw->conf.flags & IEEE80211_CONF_MONITOR);
 
 	pm->enable = pm->enable_user && !monitor;
+	/* Note: caller (debugfs) must hold mutex before calling this function */
 	ieee80211_iterate_active_interfaces(hw,
 					    IEEE80211_IFACE_ITER_RESUME_ALL,
 					    mt7921_pm_interface_iter, dev);
@@ -765,6 +771,9 @@ mt7921_regd_set_6ghz_power_type(struct ieee80211_vif *vif, bool is_add)
 	struct mt792x_dev *dev = phy->dev;
 	u32 valid_vif_num = 0;
 
+	/* Note: caller (mt7921_mac_sta_add/remove via mt76_sta_add/remove)
+	 * already holds dev->mt76.mutex, so we must not acquire it here.
+	 */
 	ieee80211_iterate_active_interfaces(mt76_hw(dev),
 					    IEEE80211_IFACE_ITER_RESUME_ALL,
 					    mt7921_calc_vif_num, &valid_vif_num);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
index ec9686183251..9f76b334b93d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
@@ -426,7 +426,9 @@ static int mt7921_pci_suspend(struct device *device)
 	cancel_delayed_work_sync(&pm->ps_work);
 	cancel_work_sync(&pm->wake_work);
 
+	mt792x_mutex_acquire(dev);
 	mt7921_roc_abort_sync(dev);
+	mt792x_mutex_release(dev);
 
 	err = mt792x_mcu_drv_pmctrl(dev);
 	if (err < 0)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
index 3421e53dc948..92ea2811816f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
@@ -219,7 +219,9 @@ static int mt7921s_suspend(struct device *__dev)
 	cancel_delayed_work_sync(&pm->ps_work);
 	cancel_work_sync(&pm->wake_work);
 
+	mt792x_mutex_acquire(dev);
 	mt7921_roc_abort_sync(dev);
+	mt792x_mutex_release(dev);
 
 	err = mt792x_mcu_drv_pmctrl(dev);
 	if (err < 0)
-- 
2.52.0


^ permalink raw reply related

* [PATCH 03/13] wifi: mt76: mt792x: fix NULL pointer and firmware reload issues
From: Zac @ 2026-01-20 20:10 UTC (permalink / raw)
  To: sean.wang
  Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
	linux, lorenzo, nbd, ryder.lee, sean.wang, zac, zbowling
In-Reply-To: <20260120201043.38225-1-zac@zacbowling.com>

From: Zac Bowling <zac@zacbowling.com>

This patch combines two fixes for the shared mt792x code used by both
MT7921 and MT7925 drivers:

1. Fix NULL pointer dereference in TX path:

Add NULL pointer checks in mt792x_tx() to prevent kernel crashes when
transmitting packets during MLO link removal.

The function calls mt792x_sta_to_link() which can return NULL if the
link is being removed, but the return value was dereferenced without
checking. Similarly, the RCU-protected link_conf and link_sta pointers
were used without NULL validation.

This race can occur when:
- A packet is queued for transmission
- Concurrently, the link is being removed (mt7925_mac_link_sta_remove)
- mt792x_sta_to_link() returns NULL for the removed link
- Kernel crashes on wcid = &mlink->wcid dereference

Fix by checking mlink, conf, and link_sta before use, freeing the SKB
and returning early if any pointer is NULL.

2. Fix firmware reload failure after previous load crash:

If the firmware loading process crashes or is interrupted after
acquiring the patch semaphore but before releasing it, subsequent
firmware load attempts will fail with 'Failed to get patch semaphore'.

Apply the same fix from MT7915 (commit 79dd14f): release the patch
semaphore before starting firmware load and restart MCU firmware to
ensure clean state.

Fixes: c74df1c067f2 ("wifi: mt76: mt792x: introduce mt792x-lib module")
Fixes: 583204ae70f9 ("wifi: mt76: mt792x: move mt7921_load_firmware in mt792x-lib module")
Link: https://github.com/openwrt/mt76/commit/79dd14f2e8161b656341b6653261779199aedbe4
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
 .../net/wireless/mediatek/mt76/mt792x_core.c  | 27 +++++++++++++++++--
 1 file changed, 25 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
index f2ed16feb6c1..05598202b488 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
@@ -95,6 +95,8 @@ void mt792x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control,
 				       IEEE80211_TX_CTRL_MLO_LINK);
 		sta = (struct mt792x_sta *)control->sta->drv_priv;
 		mlink = mt792x_sta_to_link(sta, link_id);
+		if (!mlink)
+			goto free_skb;
 		wcid = &mlink->wcid;
 	}
 
@@ -113,9 +115,12 @@ void mt792x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control,
 		link_id = wcid->link_id;
 		rcu_read_lock();
 		conf = rcu_dereference(vif->link_conf[link_id]);
-		memcpy(hdr->addr2, conf->addr, ETH_ALEN);
-
 		link_sta = rcu_dereference(control->sta->link[link_id]);
+		if (!conf || !link_sta) {
+			rcu_read_unlock();
+			goto free_skb;
+		}
+		memcpy(hdr->addr2, conf->addr, ETH_ALEN);
 		memcpy(hdr->addr1, link_sta->addr, ETH_ALEN);
 
 		if (vif->type == NL80211_IFTYPE_STATION)
@@ -136,6 +141,10 @@ void mt792x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control,
 	}
 
 	mt76_connac_pm_queue_skb(hw, &dev->pm, wcid, skb);
+	return;
+
+free_skb:
+	ieee80211_free_txskb(hw, skb);
 }
 EXPORT_SYMBOL_GPL(mt792x_tx);
 
@@ -927,6 +936,20 @@ int mt792x_load_firmware(struct mt792x_dev *dev)
 {
 	int ret;
 
+	/* Release semaphore if taken by previous failed load attempt.
+	 * This prevents "Failed to get patch semaphore" errors when
+	 * recovering from firmware crashes or suspend/resume failures.
+	 */
+	ret = mt76_connac_mcu_patch_sem_ctrl(&dev->mt76, false);
+	if (ret < 0)
+		dev_dbg(dev->mt76.dev, "Semaphore release returned %d (may be expected)\n", ret);
+
+	/* Always restart MCU to ensure clean state before loading firmware */
+	mt76_connac_mcu_restart(&dev->mt76);
+
+	/* Wait for MCU to be ready after restart */
+	msleep(100);
+
 	ret = mt76_connac2_load_patch(&dev->mt76, mt792x_patch_name(dev));
 	if (ret)
 		return ret;
-- 
2.52.0


^ permalink raw reply related

* [PATCH 02/13] wifi: mt76: fix list corruption in mt76_wcid_cleanup
From: Zac @ 2026-01-20 20:10 UTC (permalink / raw)
  To: sean.wang
  Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
	linux, lorenzo, nbd, ryder.lee, sean.wang, zac, zbowling
In-Reply-To: <20260120201043.38225-1-zac@zacbowling.com>

From: Zac Bowling <zac@zacbowling.com>

mt76_wcid_cleanup() was not removing wcid entries from sta_poll_list
before mt76_reset_device() reinitializes the master list. This leaves
stale pointers in wcid->poll_list, causing list corruption when
mt76_wcid_add_poll() later checks list_empty() and tries to add the
entry back.

The fix adds proper cleanup of poll_list in mt76_wcid_cleanup(),
matching how tx_list is already handled. This is similar to what
mt7996_mac_sta_deinit_link() already does correctly.

Fixes list corruption warnings like:
  list_add corruption. prev->next should be next (ffffffff...)

Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
 drivers/net/wireless/mediatek/mt76/mac80211.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 75772979f438..d0c522909e98 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1716,6 +1716,16 @@ void mt76_wcid_cleanup(struct mt76_dev *dev, struct mt76_wcid *wcid)
 
 	idr_destroy(&wcid->pktid);
 
+	/* Remove from sta_poll_list to prevent list corruption after reset.
+	 * Without this, mt76_reset_device() reinitializes sta_poll_list but
+	 * leaves wcid->poll_list with stale pointers, causing list corruption
+	 * when mt76_wcid_add_poll() checks list_empty().
+	 */
+	spin_lock_bh(&dev->sta_poll_lock);
+	if (!list_empty(&wcid->poll_list))
+		list_del_init(&wcid->poll_list);
+	spin_unlock_bh(&dev->sta_poll_lock);
+
 	spin_lock_bh(&phy->tx_lock);
 
 	if (!list_empty(&wcid->tx_list))
-- 
2.52.0


^ permalink raw reply related

* [PATCH 01/13] wifi: mt76: mt7925: fix potential deadlock in mt7925_roc_abort_sync
From: Zac @ 2026-01-20 20:10 UTC (permalink / raw)
  To: sean.wang
  Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
	linux, lorenzo, nbd, ryder.lee, sean.wang, zac, zbowling,
	Quan Zhou
In-Reply-To: <20260120201043.38225-1-zac@zacbowling.com>

From: Sean Wang <sean.wang@mediatek.com>

roc_abort_sync() can deadlock with roc_work(). roc_work() holds
dev->mt76.mutex, while cancel_work_sync() waits for roc_work()
to finish. If the caller already owns the same mutex, both
sides block and no progress is possible.

This deadlock can occur during station removal when
mt76_sta_state() -> mt76_sta_remove() ->
mt7925_mac_sta_remove_link() -> mt7925_mac_link_sta_remove() ->
mt7925_roc_abort_sync() invokes cancel_work_sync() while
roc_work() is still running and holding dev->mt76.mutex.

This avoids the mutex deadlock and preserves exactly-once
work ownership.

Fixes: 45064d19fd3a ("wifi: mt76: mt7925: fix a potential association failure upon resuming")
Co-developed-by: Quan Zhou <quan.zhou@mediatek.com>
Signed-off-by: Quan Zhou <quan.zhou@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/main.c | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 2d358a96640c..05990455ee7d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -457,12 +457,16 @@ void mt7925_roc_abort_sync(struct mt792x_dev *dev)
 {
 	struct mt792x_phy *phy = &dev->phy;
 
+	if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
+		return;
+
 	timer_delete_sync(&phy->roc_timer);
-	cancel_work_sync(&phy->roc_work);
-	if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
-		ieee80211_iterate_interfaces(mt76_hw(dev),
-					     IEEE80211_IFACE_ITER_RESUME_ALL,
-					     mt7925_roc_iter, (void *)phy);
+
+	cancel_work(&phy->roc_work);
+
+	ieee80211_iterate_interfaces(mt76_hw(dev),
+				     IEEE80211_IFACE_ITER_RESUME_ALL,
+				     mt7925_roc_iter, (void *)phy);
 }
 EXPORT_SYMBOL_GPL(mt7925_roc_abort_sync);
 
-- 
2.52.0


^ permalink raw reply related

* [PATCH v6 00/13] wifi: mt76: stability fixes for deadlocks, NULL derefs, and race conditions
From: Zac @ 2026-01-20 20:10 UTC (permalink / raw)
  To: sean.wang
  Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
	linux, lorenzo, nbd, ryder.lee, sean.wang, zac, zbowling
In-Reply-To: <CAGp9LzrcvW18xKFL-oF3wxRmb73G6PN59Y2NSA2E5idva1wtKg@mail.gmail.com>

From: Zac Bowling <zac@zacbowling.com>

TLDR: This series addresses stability issues in both the MT7921 and MT7925 
WiFi drivers that cause kernel panics, deadlocks, and system hangs 
on various systems using these drivers.

This v6 series is rebased on Sean Wang's upstream deadlock fix already sent
which is now included as patch 01/13. The remaining 12 patches are my stability
fixes.

Changes since v5:
- Rebased on Sean Wang's fix for mt7925_roc_abort_sync deadlock (now patch 1)
  and removed my work around for the same issue as Sean's fix is better.
- Fixed format string warning in patch 12: %lu -> %u for jiffies_to_msecs()
  return type (caught by kernel test robot)
- Added patch 13: fix double wcid initialization race condition - removes
  duplicate mt76_wcid_init() call that occurred after rcu_assign_pointer(),
  which could cause list corruption, memory leaks, and race conditions
  (this is a pre-existing bug in upstream, not introduced by this series)

Zac Bowling (12):
  wifi: mt76: fix list corruption in mt76_wcid_cleanup
  wifi: mt76: mt792x: fix NULL pointer and firmware reload issues
  wifi: mt76: mt7921: add mutex protection in critical paths
  wifi: mt76: mt7921: fix deadlock in sta removal and suspend ROC abort
  wifi: mt76: mt7925: add comprehensive NULL pointer protection for MLO
  wifi: mt76: mt7925: add mutex protection in critical paths
  wifi: mt76: mt7925: add MCU command error handling
  wifi: mt76: mt7925: add lockdep assertions for mutex verification
  wifi: mt76: mt7925: fix MLO roaming and ROC setup issues
  wifi: mt76: mt7925: fix BA session teardown during beacon loss
  wifi: mt76: mt7925: fix ROC deadlocks and race conditions
  wifi: mt76: mt7925: fix double wcid initialization race condition

Sean Wang (1):
  wifi: mt76: mt7925: fix potential deadlock in mt7925_roc_abort_sync

 drivers/net/wireless/mediatek/mt76/mac80211.c    |  10 +
 drivers/net/wireless/mediatek/mt76/mt76.h        |   1 +
 drivers/net/wireless/mediatek/mt76/mt7921/mac.c  |   2 +
 drivers/net/wireless/mediatek/mt76/mt7921/main.c |  28 ++-
 drivers/net/wireless/mediatek/mt76/mt7925/mac.c  |   8 +
 drivers/net/wireless/mediatek/mt76/mt7925/main.c | 303 ++++++++++++++++++++---
 drivers/net/wireless/mediatek/mt76/mt7925/mcu.c  |  48 +++-
 drivers/net/wireless/mediatek/mt76/mt7925/pci.c  |   2 +
 drivers/net/wireless/mediatek/mt76/mt792x.h      |   7 +
 drivers/net/wireless/mediatek/mt76/mt792x_core.c |  27 +-
 10 files changed, 390 insertions(+), 46 deletions(-)

--
2.52.0


^ permalink raw reply

* Re: [PATCH 11/11] wifi: mt76: mt7925: fix ROC deadlocks and race conditions
From: Zac Bowling @ 2026-01-20 17:59 UTC (permalink / raw)
  To: Sean Wang
  Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
	lorenzo, nbd, ryder.lee, sean.wang, stable, linux
In-Reply-To: <CAGp9LzrcvW18xKFL-oF3wxRmb73G6PN59Y2NSA2E5idva1wtKg@mail.gmail.com>

Hi Sean,

Thank you for the detailed feedback and for sharing your deadlock fix.

> 1. Would it be possible to rebase your patchset on top of this fix

Yes, I'll rebase on your patch. I reviewed it, and it's a cleaner solution
than what I implemented. My approach used an async abort with a state flag,
but your `cancel_work()` approach avoids the blocking entirely.

Additionally, last night, after someone ran an AI bot check on my patches,
I found two issues in my current patchset that introduce deadlocks where
your existing patch stops it from hitting.

1. In my patch #3 added I mt792x_mutex_acquire() around
   ieee80211_iterate_active_interfaces(), but this function is called from
   mt7921_mac_sta_add/remove via mt76_sta_add/remove, which already hold
   dev->mutex. I need to remove this mutex wrapper.

2. In my patch #6 I wrapped mt7925_roc_abort_sync() with a mutex in the
   suspend path, but roc_abort_sync calls cancel_work_sync() which can
   deadlock if roc_work is waiting for the mutex. Your fix addresses
   this more elegantly.

I'll prepare a v6 rebased on your patch with these fixes.

> 2. Could you please elaborate on the test scenarios that would trigger
>    ROC rate limiting for MLO authentication failures?

The rate-limiting addresses a real-world scenario we observed with MT7925
when connecting to WiFi 7 APs with MLO + Fast Transition (802.11r) enabled.

When wpa_supplicant attempts Fast Transition roaming between MLO-capable APs,
there's a race condition between disconnect and key setup. The kernel's nl80211
validation requires link_id for MLO group keys (net/wireless/nl80211.c:4828),
but during FT roaming, wdev->valid_links may still be set from the previous
connection when the new key setup begins.

This causes repeated failures:
```
wpa_supplicant: nl80211: kernel reports: link ID must for MLO group key
wpa_supplicant: FT: Failed to set PTK to the driver
```

Each failure triggers a reconnection attempt, which requires ROC commands for
scanning. When these failures happen in rapid succession (we observed 3-4
failures within seconds), the MCU seems to become overwhelmed with messages
like this:

```
Message 00020027 (MCU_UNI_CMD_ROC) timeout
Message 00020027 (MCU_UNI_CMD_ROC) timeout
Message 00020027 (MCU_UNI_CMD_ROC) timeout
```

This leads to firmware reset, which triggers more reconnection attempts,
creating a cascading failure loop.

Reproduction manifests for me at least with:
- Single MT7925 interface in STA mode
- WiFi 7 AP with MLO enabled (multi-link across 5 GHz + 6 GHz)
- 802.11r (Fast Transition) enabled
- Multiple APs with the same SSID (roaming scenario)

I haven't tested with multiple virtual interfaces, but the core issue is
the rapid ROC request rate during the reconnection loop, not the number of
interfaces.

I had someone on the Framework forum post similar dumps showing similar
behavior with their Eeros mesh setup. I'm using some Unifi U7 Pros
with MLO enabled on one of the SSIDs.

So this might not be the right place to fix this. We may need to fix at the
upper-layers. I put this here so folks could work around with my DKMS
package, but a deeper refactor up multiple layers around MLO is probably
needed to really fix this. Fixing here at least validates things are more
stable (but I can't confirm it's really fixed, I don't know what is going
on inside the firmware, and it's internal state issues we can get into).

The root cause is likely in wpa_supplicant/mac80211 (race condition in MLO
key setup timing during FT roaming). However, the rate limiting provides a
defensive measure to prevent firmware crashes. Then I can maybe investigate
the upper-layer issues. Way bigger change, though, unfortunately.

Fix is similar to how TCP implements backoff to handle network congestion -
the congestion isn't TCP's fault, but the backoff prevents cascading failures.

The detailed crash analysis and dmesg logs are in our repository:
https://github.com/zbowling/mt7925/tree/main/crashes

Specifically:
- crash-2026-01-19-mlo-authentication-failure.log (MLO key race analysis)
- crash-2026-01-12-2210-auth-loop-mcu-timeout.log (MCU timeout during auth loop)

If you believe the rate limiting is unnecessary given how ROC operations are
serialized in the firmware, I can remove it. My goal was to prevent the
firmware from entering a reset loop, but if there's a better approach or if
the underlying mac80211/wpa_supplicant issue should be fixed instead now, I'm
happy to adjust. This just seemed to reduce the issue for my MLO setup.

Thank you for offering to prepare an out-of-tree branch - that would be very
helpful for testing the integrated patchset.

Zac Bowling

On Tue, Jan 20, 2026 at 12:25 AM Sean Wang <sean.wang@kernel.org> wrote:
>
> On Tue, Jan 20, 2026 at 12:29 AM Zac <zac@zacbowling.com> wrote:
> >
> > From: Zac Bowling <zac@zacbowling.com>
> >
> > Fix multiple interrelated issues in the remain-on-channel (ROC) handling
> > that cause deadlocks, race conditions, and resource leaks.
> >
> > Problems fixed:
> >
> > 1. Deadlock in sta removal ROC abort path:
> >    When a station is removed while a ROC operation is in progress, the
> >    driver would call mt7925_roc_abort_sync() which waits for ROC completion.
> >    However, the ROC work itself needs to acquire mt792x_mutex which is
> >    already held during station removal, causing a deadlock.
> >
> >    Fix: Use async ROC abort (mt76_connac_mcu_abort_roc) when called from
> >    paths that already hold the mutex, and add MT76_STATE_ROC_ABORT flag
> >    to coordinate between the abort and the ROC timer.
> >
>
> Hi Zac,
>
> Thanks for your continued efforts on the driver.
> We’ve sent a patch to address the mt7925 deadlock at the link below:
> https://lists.infradead.org/pipermail/linux-mediatek/2025-December/102164.html
> We plan to send the same fix to mt7921 as well.
>
> I had a couple of questions and suggestions:
> 1. Would it be possible to rebase your patchset on top of this fix
> (and any other pending patches that are not yet merged)? We noticed
> some conflicts when applying the series, and rebasing it this way
> would make it easier for nbd to integrate the full patchset.
> 2. Could you please elaborate on the test scenarios that would trigger
> ROC rate limiting for MLO authentication failures? If I recall
> correctly, ROC operations are typically handled sequentially unless
> multiple interfaces are created on the same physical device. In that
> case, how many virtual interfaces and which operating modes (GC/STA or
> multiple STAs) are required to reproduce the issue?
>
> I will try to prepare an out-of-tree branch with the current pending
> patches to help your patchset integrate more smoothly. Thanks for
> collecting community issues and fixes and incorporating them into the
> driver.
>
>              Sean
>
> > 2. ROC timer race during suspend:
> >    The ROC timer could fire after the device started suspending but before
> >    the ROC was properly aborted, causing undefined behavior.
> >
> >    Fix: Delete ROC timer synchronously before suspend and check device
> >    state before processing ROC timeout.
> >
> > 3. ROC rate limiting for MLO auth failures:
> >    Rapid ROC requests during MLO authentication can overwhelm the firmware,
> >    causing authentication timeouts. The MT7925 firmware has limited ROC
> >    handling capacity.
> >
> >    Fix: Add rate limiting infrastructure with configurable minimum interval
> >    between ROC requests. Track last ROC completion time and defer new
> >    requests if they arrive too quickly.
> >
> > 4. WCID leak in ROC cleanup:
> >    When ROC operations are aborted, the associated WCID resources were
> >    not being properly released, causing resource exhaustion over time.
> >
> >    Fix: Ensure WCID cleanup happens in all ROC termination paths.
> >
> > 5. Async ROC abort race condition:
> >    The async ROC abort could race with normal ROC completion, causing
> >    double-free or use-after-free of ROC resources.
> >
> >    Fix: Use MT76_STATE_ROC_ABORT flag and proper synchronization to
> >    prevent races between async abort and normal completion paths.
> >
> > These fixes work together to provide robust ROC handling that doesn't
> > deadlock, properly releases resources, and handles edge cases during
> > suspend and MLO operations.
> >
> > Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 device")
> > Signed-off-by: Zac Bowling <zac@zacbowling.com>
> > ---
> >  drivers/net/wireless/mediatek/mt76/mt76.h     |   1 +
> >  .../net/wireless/mediatek/mt76/mt7925/main.c  | 175 ++++++++++++++++--
> >  drivers/net/wireless/mediatek/mt76/mt792x.h   |   7 +
> >  3 files changed, 170 insertions(+), 13 deletions(-)
> >
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
> > index d05e83ea1cac..91f9dd95c89e 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt76.h
> > +++ b/drivers/net/wireless/mediatek/mt76/mt76.h
> > @@ -511,6 +511,7 @@ enum {
> >         MT76_STATE_POWER_OFF,
> >         MT76_STATE_SUSPEND,
> >         MT76_STATE_ROC,
> > +       MT76_STATE_ROC_ABORT,
> >         MT76_STATE_PM,
> >         MT76_STATE_WED_RESET,
> >  };
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> > index cc7ef2c17032..2404f7812897 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> > @@ -453,6 +453,24 @@ static void mt7925_roc_iter(void *priv, u8 *mac,
> >         mt7925_mcu_abort_roc(phy, &mvif->bss_conf, phy->roc_token_id);
> >  }
> >
> > +/* Async ROC abort - safe to call while holding mutex.
> > + * Sets abort flag and lets roc_work handle cleanup without blocking.
> > + * This prevents deadlock when called from sta_remove path which holds mutex.
> > + */
> > +static void mt7925_roc_abort_async(struct mt792x_dev *dev)
> > +{
> > +       struct mt792x_phy *phy = &dev->phy;
> > +
> > +       /* Set abort flag - roc_work checks this before acquiring mutex */
> > +       set_bit(MT76_STATE_ROC_ABORT, &phy->mt76->state);
> > +
> > +       /* Stop timer and schedule work to handle cleanup.
> > +        * Must schedule work since timer may not have fired yet.
> > +        */
> > +       timer_delete(&phy->roc_timer);
> > +       ieee80211_queue_work(phy->mt76->hw, &phy->roc_work);
> > +}
> > +
> >  void mt7925_roc_abort_sync(struct mt792x_dev *dev)
> >  {
> >         struct mt792x_phy *phy = &dev->phy;
> > @@ -473,6 +491,17 @@ void mt7925_roc_work(struct work_struct *work)
> >         phy = (struct mt792x_phy *)container_of(work, struct mt792x_phy,
> >                                                 roc_work);
> >
> > +       /* Check abort flag BEFORE acquiring mutex to prevent deadlock.
> > +        * If abort is requested while we're in the sta_remove path (which
> > +        * holds the mutex), we must not try to acquire it or we'll deadlock.
> > +        * Clear the flags and only notify mac80211 if ROC was actually active.
> > +        */
> > +       if (test_and_clear_bit(MT76_STATE_ROC_ABORT, &phy->mt76->state)) {
> > +               if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
> > +                       ieee80211_remain_on_channel_expired(phy->mt76->hw);
> > +               return;
> > +       }
> > +
> >         if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
> >                 return;
> >
> > @@ -500,14 +529,93 @@ static int mt7925_abort_roc(struct mt792x_phy *phy,
> >         return err;
> >  }
> >
> > +/* ROC rate limiting constants - exponential backoff to prevent MCU overload
> > + * when upper layers trigger rapid reconnection cycles (e.g., MLO auth failures).
> > + * Max backoff ~1.6s, resets after 10s of no timeouts.
> > + */
> > +#define MT7925_ROC_BACKOFF_BASE_MS     100
> > +#define MT7925_ROC_BACKOFF_MAX_MS      1600
> > +#define MT7925_ROC_TIMEOUT_RESET_MS    10000
> > +#define MT7925_ROC_TIMEOUT_WARN_THRESH 5
> > +
> > +/* Check if ROC should be throttled due to recent timeouts.
> > + * Returns delay in jiffies if throttling, 0 if OK to proceed.
> > + */
> > +static unsigned long mt7925_roc_throttle_check(struct mt792x_phy *phy)
> > +{
> > +       unsigned long now = jiffies;
> > +
> > +       /* Reset timeout counter if it's been a while since last timeout */
> > +       if (phy->roc_timeout_count &&
> > +           time_after(now, phy->roc_last_timeout +
> > +                      msecs_to_jiffies(MT7925_ROC_TIMEOUT_RESET_MS))) {
> > +               phy->roc_timeout_count = 0;
> > +               phy->roc_backoff_until = 0;
> > +       }
> > +
> > +       /* Check if we're still in backoff period */
> > +       if (phy->roc_backoff_until && time_before(now, phy->roc_backoff_until))
> > +               return phy->roc_backoff_until - now;
> > +
> > +       return 0;
> > +}
> > +
> > +/* Record ROC timeout and calculate backoff period */
> > +static void mt7925_roc_record_timeout(struct mt792x_phy *phy)
> > +{
> > +       unsigned int backoff_ms;
> > +
> > +       phy->roc_last_timeout = jiffies;
> > +       phy->roc_timeout_count++;
> > +
> > +       /* Exponential backoff: 100ms, 200ms, 400ms, 800ms, 1600ms (capped) */
> > +       backoff_ms = MT7925_ROC_BACKOFF_BASE_MS <<
> > +                    min_t(u8, phy->roc_timeout_count - 1, 4);
> > +       if (backoff_ms > MT7925_ROC_BACKOFF_MAX_MS)
> > +               backoff_ms = MT7925_ROC_BACKOFF_MAX_MS;
> > +
> > +       phy->roc_backoff_until = jiffies + msecs_to_jiffies(backoff_ms);
> > +
> > +       /* Warn if we're seeing repeated timeouts - likely upper layer issue */
> > +       if (phy->roc_timeout_count == MT7925_ROC_TIMEOUT_WARN_THRESH)
> > +               dev_warn(phy->dev->mt76.dev,
> > +                        "mt7925: %u consecutive ROC timeouts, possible mac80211/wpa_supplicant issue (MLO key race?)\n",
> > +                        phy->roc_timeout_count);
> > +}
> > +
> > +/* Clear timeout tracking on successful ROC */
> > +static void mt7925_roc_clear_timeout(struct mt792x_phy *phy)
> > +{
> > +       phy->roc_timeout_count = 0;
> > +       phy->roc_backoff_until = 0;
> > +}
> > +
> >  static int mt7925_set_roc(struct mt792x_phy *phy,
> >                           struct mt792x_bss_conf *mconf,
> >                           struct ieee80211_channel *chan,
> >                           int duration,
> >                           enum mt7925_roc_req type)
> >  {
> > +       unsigned long throttle;
> >         int err;
> >
> > +       /* Check rate limiting - if in backoff period, wait or return busy */
> > +       throttle = mt7925_roc_throttle_check(phy);
> > +       if (throttle) {
> > +               /* For short backoffs, wait; for longer ones, return busy */
> > +               if (throttle < msecs_to_jiffies(200)) {
> > +                       msleep(jiffies_to_msecs(throttle));
> > +               } else {
> > +                       dev_dbg(phy->dev->mt76.dev,
> > +                               "mt7925: ROC throttled, %lu ms remaining\n",
> > +                               jiffies_to_msecs(throttle));
> > +                       return -EBUSY;
> > +               }
> > +       }
> > +
> > +       /* Clear stale abort flag from previous ROC */
> > +       clear_bit(MT76_STATE_ROC_ABORT, &phy->mt76->state);
> > +
> >         if (test_and_set_bit(MT76_STATE_ROC, &phy->mt76->state))
> >                 return -EBUSY;
> >
> > @@ -523,7 +631,11 @@ static int mt7925_set_roc(struct mt792x_phy *phy,
> >         if (!wait_event_timeout(phy->roc_wait, phy->roc_grant, 4 * HZ)) {
> >                 mt7925_mcu_abort_roc(phy, mconf, phy->roc_token_id);
> >                 clear_bit(MT76_STATE_ROC, &phy->mt76->state);
> > +               mt7925_roc_record_timeout(phy);
> >                 err = -ETIMEDOUT;
> > +       } else {
> > +               /* Successful ROC - reset timeout tracking */
> > +               mt7925_roc_clear_timeout(phy);
> >         }
> >
> >  out:
> > @@ -534,8 +646,27 @@ static int mt7925_set_mlo_roc(struct mt792x_phy *phy,
> >                               struct mt792x_bss_conf *mconf,
> >                               u16 sel_links)
> >  {
> > +       unsigned long throttle;
> >         int err;
> >
> > +       /* Check rate limiting - MLO ROC is especially prone to rapid-fire
> > +        * during reconnection cycles after MLO authentication failures.
> > +        */
> > +       throttle = mt7925_roc_throttle_check(phy);
> > +       if (throttle) {
> > +               if (throttle < msecs_to_jiffies(200)) {
> > +                       msleep(jiffies_to_msecs(throttle));
> > +               } else {
> > +                       dev_dbg(phy->dev->mt76.dev,
> > +                               "mt7925: MLO ROC throttled, %lu ms remaining\n",
> > +                               jiffies_to_msecs(throttle));
> > +                       return -EBUSY;
> > +               }
> > +       }
> > +
> > +       /* Clear stale abort flag from previous ROC */
> > +       clear_bit(MT76_STATE_ROC_ABORT, &phy->mt76->state);
> > +
> >         if (WARN_ON_ONCE(test_and_set_bit(MT76_STATE_ROC, &phy->mt76->state)))
> >                 return -EBUSY;
> >
> > @@ -550,7 +681,10 @@ static int mt7925_set_mlo_roc(struct mt792x_phy *phy,
> >         if (!wait_event_timeout(phy->roc_wait, phy->roc_grant, 4 * HZ)) {
> >                 mt7925_mcu_abort_roc(phy, mconf, phy->roc_token_id);
> >                 clear_bit(MT76_STATE_ROC, &phy->mt76->state);
> > +               mt7925_roc_record_timeout(phy);
> >                 err = -ETIMEDOUT;
> > +       } else {
> > +               mt7925_roc_clear_timeout(phy);
> >         }
> >
> >  out:
> > @@ -567,6 +701,7 @@ static int mt7925_remain_on_channel(struct ieee80211_hw *hw,
> >         struct mt792x_phy *phy = mt792x_hw_phy(hw);
> >         int err;
> >
> > +       cancel_work_sync(&phy->roc_work);
> >         mt792x_mutex_acquire(phy->dev);
> >         err = mt7925_set_roc(phy, &mvif->bss_conf,
> >                              chan, duration, MT7925_ROC_REQ_ROC);
> > @@ -874,14 +1009,14 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
> >         if (!mlink)
> >                 return -EINVAL;
> >
> > -       idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT792x_WTBL_STA - 1);
> > -       if (idx < 0)
> > -               return -ENOSPC;
> > -
> >         mconf = mt792x_vif_to_link(mvif, link_id);
> >         if (!mconf)
> >                 return -EINVAL;
> >
> > +       idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT792x_WTBL_STA - 1);
> > +       if (idx < 0)
> > +               return -ENOSPC;
> > +
> >         mt76_wcid_init(&mlink->wcid, 0);
> >         mlink->wcid.sta = 1;
> >         mlink->wcid.idx = idx;
> > @@ -901,14 +1036,16 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
> >
> >         ret = mt76_connac_pm_wake(&dev->mphy, &dev->pm);
> >         if (ret)
> > -               return ret;
> > +               goto err_wcid;
> >
> >         mt7925_mac_wtbl_update(dev, idx,
> >                                MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
> >
> >         link_conf = mt792x_vif_to_bss_conf(vif, link_id);
> > -       if (!link_conf)
> > -               return -EINVAL;
> > +       if (!link_conf) {
> > +               ret = -EINVAL;
> > +               goto err_wcid;
> > +       }
> >
> >         /* should update bss info before STA add */
> >         if (vif->type == NL80211_IFTYPE_STATION && !link_sta->sta->tdls) {
> > @@ -920,7 +1057,7 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
> >                         ret = mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
> >                                                       link_conf, link_sta, false);
> >                 if (ret)
> > -                       return ret;
> > +                       goto err_wcid;
> >         }
> >
> >         if (ieee80211_vif_is_mld(vif) &&
> > @@ -928,28 +1065,34 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
> >                 ret = mt7925_mcu_sta_update(dev, link_sta, vif, true,
> >                                             MT76_STA_INFO_STATE_NONE);
> >                 if (ret)
> > -                       return ret;
> > +                       goto err_wcid;
> >         } else if (ieee80211_vif_is_mld(vif) &&
> >                    link_sta != mlink->pri_link) {
> >                 ret = mt7925_mcu_sta_update(dev, mlink->pri_link, vif,
> >                                             true, MT76_STA_INFO_STATE_ASSOC);
> >                 if (ret)
> > -                       return ret;
> > +                       goto err_wcid;
> >
> >                 ret = mt7925_mcu_sta_update(dev, link_sta, vif, true,
> >                                             MT76_STA_INFO_STATE_ASSOC);
> >                 if (ret)
> > -                       return ret;
> > +                       goto err_wcid;
> >         } else {
> >                 ret = mt7925_mcu_sta_update(dev, link_sta, vif, true,
> >                                             MT76_STA_INFO_STATE_NONE);
> >                 if (ret)
> > -                       return ret;
> > +                       goto err_wcid;
> >         }
> >
> >         mt76_connac_power_save_sched(&dev->mphy, &dev->pm);
> >
> >         return 0;
> > +
> > +err_wcid:
> > +       rcu_assign_pointer(dev->mt76.wcid[idx], NULL);
> > +       mt76_wcid_mask_clear(dev->mt76.wcid_mask, idx);
> > +       mt76_connac_power_save_sched(&dev->mphy, &dev->pm);
> > +       return ret;
> >  }
> >
> >  static int
> > @@ -1135,7 +1278,8 @@ static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev,
> >         if (!mlink)
> >                 return;
> >
> > -       mt7925_roc_abort_sync(dev);
> > +       /* Async abort - caller already holds mutex */
> > +       mt7925_roc_abort_async(dev);
> >
> >         mt76_connac_free_pending_tx_skbs(&dev->pm, &mlink->wcid);
> >         mt76_connac_pm_wake(&dev->mphy, &dev->pm);
> > @@ -1530,6 +1674,8 @@ static int mt7925_suspend(struct ieee80211_hw *hw,
> >         cancel_delayed_work_sync(&dev->pm.ps_work);
> >         mt76_connac_free_pending_tx_skbs(&dev->pm, NULL);
> >
> > +       /* Cancel ROC before quiescing starts */
> > +       mt7925_roc_abort_sync(dev);
> >         mt792x_mutex_acquire(dev);
> >
> >         clear_bit(MT76_STATE_RUNNING, &phy->mt76->state);
> > @@ -1876,6 +2022,8 @@ static void mt7925_mgd_prepare_tx(struct ieee80211_hw *hw,
> >         u16 duration = info->duration ? info->duration :
> >                        jiffies_to_msecs(HZ);
> >
> > +       cancel_work_sync(&mvif->phy->roc_work);
> > +
> >         mt792x_mutex_acquire(dev);
> >         mt7925_set_roc(mvif->phy, &mvif->bss_conf,
> >                        mvif->bss_conf.mt76.ctx->def.chan, duration,
> > @@ -2033,6 +2181,7 @@ mt7925_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
> >         if (old_links == new_links)
> >                 return 0;
> >
> > +       cancel_work_sync(&phy->roc_work);
> >         mt792x_mutex_acquire(dev);
> >
> >         for_each_set_bit(link_id, &rem, IEEE80211_MLD_MAX_NUM_LINKS) {
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
> > index 8388638ed550..d9c1ea709390 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt792x.h
> > +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
> > @@ -186,6 +186,13 @@ struct mt792x_phy {
> >         wait_queue_head_t roc_wait;
> >         u8 roc_token_id;
> >         bool roc_grant;
> > +
> > +       /* ROC rate limiting to prevent MCU overload during rapid reconnection
> > +        * cycles (e.g., MLO authentication failures causing repeated ROC).
> > +        */
> > +       u8 roc_timeout_count;           /* consecutive ROC timeouts */
> > +       unsigned long roc_last_timeout; /* jiffies of last timeout */
> > +       unsigned long roc_backoff_until;/* don't issue ROC until this time */
> >  };
> >
> >  struct mt792x_irq_map {
> > --
> > 2.52.0
> >

^ permalink raw reply

* [PATCH] wifi: mt76: mt7925: fix tx power setting failure after chip reset
From: Leon Yen @ 2026-01-20 16:31 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, deren.wu, sean.wang,
	mingyen.hsieh, michael.lo, allan.wang, quan.zhou, sarick.jiang,
	ryder.lee, shayne.chen, leon.yen

After the chip reset, the procedure to set the tx power will not be
successful because the previous region setting is still remains.
Clear the region setting during MAC initialization and allow it to be
reset to finalize the TX power setting.

Fixes: 3bc62aa4484d ("wifi: mt76: mt7925: add auto regdomain switch support")
Signed-off-by: Leon Yen <leon.yen@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/init.c | 2 ++
 drivers/net/wireless/mediatek/mt76/mt7925/regd.c | 3 ++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
index 3ce5d6fcc69d..c0c5cb9aff75 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
@@ -91,6 +91,8 @@ int mt7925_mac_init(struct mt792x_dev *dev)
 
 	mt7925_mac_init_basic_rates(dev);
 
+	memzero_explicit(&dev->mt76.alpha2, sizeof(dev->mt76.alpha2));
+
 	return 0;
 }
 EXPORT_SYMBOL_GPL(mt7925_mac_init);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c
index 292087e882d1..16f56ee879d4 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c
@@ -232,7 +232,8 @@ int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2)
 	    dev->regd_user)
 		return -EINVAL;
 
-	if (mdev->alpha2[0] != '0' && mdev->alpha2[1] != '0')
+	if ((mdev->alpha2[0] && mdev->alpha2[0] != '0') &&
+	    (mdev->alpha2[1] && mdev->alpha2[1] != '0'))
 		return 0;
 
 	/* do not need to update the same country twice */
-- 
2.45.2


^ permalink raw reply related

* pull-request: ath-next-20260120
From: Jeff Johnson @ 2026-01-20 15:23 UTC (permalink / raw)
  To: linux-wireless, Johannes Berg; +Cc: ath10k, ath11k, ath12k, jjohnson

The following changes since commit 24a57985670e9dac5547e5b7731bf8e7b03d5be8:

  wifi: cfg80211: don't apply HT flags to S1G channels (2026-01-13 10:44:26 +0100)

are available in the Git repository at:

  git://git.kernel.org/pub/scm/linux/kernel/git/ath/ath.git tags/ath-next-20260120

for you to fetch changes up to d8e1f4a193101a72235416f189b01131a57e26e9:

  wifi: ath12k: enable QCC2072 support (2026-01-15 17:19:42 -0800)

----------------------------------------------------------------
ath.git patches for v6.20 (#2)

Highlights for some specific drivers include:

ath11k:
Add support for Channel Frequency Response measurement.

ath12k:
Add support for the QCC2072 chipset.

And of course there is the usual set of cleanups and bug fixes across
the entire family of "ath" drivers.

----------------------------------------------------------------
Alexander Minchev (1):
      wifi: ath12k: remove redundant pci_set_drvdata() call

Alexandru Gagniuc (1):
      wifi: ath11k: move .max_tx_ring to struct ath11k_hw_hal_params

Baochen Qiang (18):
      wifi: ath12k: do WoW offloads only on primary link
      wifi: ath12k: refactor PCI window register access
      wifi: ath12k: refactor REO CMD ring handling
      wifi: ath12k: refactor REO status ring handling
      wifi: ath12k: fix preferred hardware mode calculation
      wifi: ath12k: refactor 320 MHz bandwidth support parsing
      wifi: ath12k: fix mac phy capability parsing
      wifi: ath12k: add hardware registers for QCC2072
      wifi: ath12k: add hardware parameters for QCC2072
      wifi: ath12k: support LPASS_SHARED target memory type
      wifi: ath12k: support downloading auxiliary ucode image for QCC2072
      wifi: ath12k: add HAL descriptor and ops for QCC2072
      wifi: ath12k: add hardware ops support for QCC2072
      wifi: ath12k: handle REO CMD ring for QCC2072
      wifi: ath12k: handle REO status ring for QCC2072
      wifi: ath12k: limit number of channels per WMI command
      wifi: ath12k: send peer meta data version to firmware
      wifi: ath12k: enable QCC2072 support

Chien Wong (1):
      wifi: ath11k: fix comment typo in monitor mode handling

Dan Carpenter (1):
      wifi: ath12k: clean up on error in ath12k_dp_setup()

Krzysztof Kozlowski (1):
      dt-bindings: net: wireless: ath11k: Combine two if:then: clauses

Miaoqing Pan (1):
      wifi: ath12k: fix PCIE_LOCAL_REG_QRTR_NODE_ID definition for QCC2072

Qian Zhang (1):
      wifi: ath11k: Fix failure to connect to a 6 GHz AP

Randy Dunlap (4):
      wifi: ath5k: debug.h: fix enum ath5k_debug_level kernel-doc
      wifi: ath9k: debug.h: fix kernel-doc bad lines and struct ath_tx_stats
      wifi: ath9k: fix kernel-doc warnings in common-debug.h
      wifi: wil6210: fix a bunch of kernel-doc warnings

Rosen Penev (1):
      wifi: ath9k: add OF dependency to AHB

Ross Vandegrift (1):
      wifi: ath11k: add pm quirk for Thinkpad Z13/Z16 Gen1

Venkateswara Naralasetty (6):
      wifi: ath11k: Add initialization and deinitialization sequence for CFR module
      wifi: ath11k: Register debugfs for CFR configuration
      wifi: ath11k: Add support unassociated client CFR
      wifi: ath11k: Register relayfs entries for CFR dump
      wifi: ath11k: Register DBR event handler for CFR data
      wifi: ath11k: Register handler for CFR capture event

 .../bindings/net/wireless/qcom,ath11k.yaml         |    9 -
 drivers/net/wireless/ath/ath11k/Kconfig            |   11 +
 drivers/net/wireless/ath/ath11k/Makefile           |    1 +
 drivers/net/wireless/ath/ath11k/cfr.c              | 1023 ++++++++++++++++++++
 drivers/net/wireless/ath/ath11k/cfr.h              |  308 ++++++
 drivers/net/wireless/ath/ath11k/core.c             |   81 +-
 drivers/net/wireless/ath/ath11k/core.h             |   19 +-
 drivers/net/wireless/ath/ath11k/dbring.c           |   50 +-
 drivers/net/wireless/ath/ath11k/dbring.h           |    8 +-
 drivers/net/wireless/ath/ath11k/debug.h            |    8 +-
 drivers/net/wireless/ath/ath11k/debugfs.c          |    2 +-
 drivers/net/wireless/ath/ath11k/debugfs_sta.c      |  142 ++-
 drivers/net/wireless/ath/ath11k/dp.c               |   12 +-
 drivers/net/wireless/ath/ath11k/dp.h               |    1 -
 drivers/net/wireless/ath/ath11k/dp_tx.c            |    9 +-
 drivers/net/wireless/ath/ath11k/hal.c              |    3 +-
 drivers/net/wireless/ath/ath11k/hw.c               |   19 +-
 drivers/net/wireless/ath/ath11k/hw.h               |    8 +-
 drivers/net/wireless/ath/ath11k/mac.c              |   23 +-
 drivers/net/wireless/ath/ath11k/reg.c              |    9 +-
 drivers/net/wireless/ath/ath11k/wmi.c              |  147 ++-
 drivers/net/wireless/ath/ath11k/wmi.h              |   97 +-
 drivers/net/wireless/ath/ath12k/core.h             |    3 +
 drivers/net/wireless/ath/ath12k/dp.c               |    2 +-
 drivers/net/wireless/ath/ath12k/fw.c               |   10 +-
 drivers/net/wireless/ath/ath12k/fw.h               |    3 +-
 drivers/net/wireless/ath/ath12k/hal.c              |   46 +
 drivers/net/wireless/ath/ath12k/hal.h              |   34 +
 drivers/net/wireless/ath/ath12k/hw.h               |    2 +
 drivers/net/wireless/ath/ath12k/mac.c              |    2 +-
 drivers/net/wireless/ath/ath12k/pci.c              |   18 +-
 drivers/net/wireless/ath/ath12k/pci.h              |    7 +
 drivers/net/wireless/ath/ath12k/qmi.c              |  180 +++-
 drivers/net/wireless/ath/ath12k/qmi.h              |   16 +
 drivers/net/wireless/ath/ath12k/wifi7/Makefile     |    3 +-
 drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c      |   62 +-
 drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h      |    1 +
 drivers/net/wireless/ath/ath12k/wifi7/hal.c        |    8 +
 drivers/net/wireless/ath/ath12k/wifi7/hal.h        |    3 -
 drivers/net/wireless/ath/ath12k/wifi7/hal_desc.h   |   33 +-
 .../net/wireless/ath/ath12k/wifi7/hal_qcc2072.c    |  503 ++++++++++
 .../net/wireless/ath/ath12k/wifi7/hal_qcc2072.h    |   13 +
 .../net/wireless/ath/ath12k/wifi7/hal_qcn9274.c    |    8 +-
 drivers/net/wireless/ath/ath12k/wifi7/hal_rx.c     |   97 +-
 drivers/net/wireless/ath/ath12k/wifi7/hal_rx.h     |   30 +-
 .../net/wireless/ath/ath12k/wifi7/hal_rx_desc.h    |   17 +
 .../net/wireless/ath/ath12k/wifi7/hal_wcn7850.c    |    8 +-
 .../net/wireless/ath/ath12k/wifi7/hal_wcn7850.h    |    1 +
 drivers/net/wireless/ath/ath12k/wifi7/hw.c         |  101 ++
 drivers/net/wireless/ath/ath12k/wifi7/pci.c        |   26 +-
 drivers/net/wireless/ath/ath12k/wifi7/wmi.c        |    5 +
 drivers/net/wireless/ath/ath12k/wmi.c              |   54 +-
 drivers/net/wireless/ath/ath12k/wmi.h              |    7 +
 drivers/net/wireless/ath/ath12k/wow.c              |   16 +
 drivers/net/wireless/ath/ath5k/debug.h             |    4 +-
 drivers/net/wireless/ath/ath9k/Kconfig             |    2 +-
 drivers/net/wireless/ath/ath9k/common-debug.h      |    8 +-
 drivers/net/wireless/ath/ath9k/debug.h             |   15 +-
 drivers/net/wireless/ath/wil6210/wil6210.h         |   33 +-
 59 files changed, 3135 insertions(+), 236 deletions(-)
 create mode 100644 drivers/net/wireless/ath/ath11k/cfr.c
 create mode 100644 drivers/net/wireless/ath/ath11k/cfr.h
 create mode 100644 drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c
 create mode 100644 drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.h

^ permalink raw reply

* Re: [PATCH 11/11] wifi: mt76: mt7925: fix ROC deadlocks and race conditions
From: kernel test robot @ 2026-01-20 13:26 UTC (permalink / raw)
  To: Zac, sean.wang
  Cc: llvm, oe-kbuild-all, deren.wu, kvalo, linux-kernel,
	linux-mediatek, linux-wireless, lorenzo, nbd, ryder.lee,
	sean.wang, stable, linux, zbowling, Zac Bowling
In-Reply-To: <20260120062854.126501-12-zac@zacbowling.com>

Hi Zac,

kernel test robot noticed the following build warnings:

[auto build test WARNING on wireless-next/main]
[also build test WARNING on wireless/main linus/master v6.19-rc6 next-20260119]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Zac/wifi-mt76-fix-list-corruption-in-mt76_wcid_cleanup/20260120-143842
base:   https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
patch link:    https://lore.kernel.org/r/20260120062854.126501-12-zac%40zacbowling.com
patch subject: [PATCH 11/11] wifi: mt76: mt7925: fix ROC deadlocks and race conditions
config: i386-randconfig-015-20260120 (https://download.01.org/0day-ci/archive/20260120/202601202144.ee4DM9Pz-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260120/202601202144.ee4DM9Pz-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601202144.ee4DM9Pz-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/net/wireless/mediatek/mt76/mt7925/main.c:611:5: warning: format specifies type 'unsigned long' but the argument has type 'unsigned int' [-Wformat]
     610 |                                 "mt7925: ROC throttled, %lu ms remaining\n",
         |                                                         ~~~
         |                                                         %u
     611 |                                 jiffies_to_msecs(throttle));
         |                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/dev_printk.h:165:39: note: expanded from macro 'dev_dbg'
     165 |         dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
         |                                      ~~~     ^~~~~~~~~~~
   include/linux/dynamic_debug.h:285:19: note: expanded from macro 'dynamic_dev_dbg'
     285 |                            dev, fmt, ##__VA_ARGS__)
         |                                 ~~~    ^~~~~~~~~~~
   include/linux/dynamic_debug.h:261:59: note: expanded from macro '_dynamic_func_call'
     261 |         _dynamic_func_call_cls(_DPRINTK_CLASS_DFLT, fmt, func, ##__VA_ARGS__)
         |                                                                  ^~~~~~~~~~~
   include/linux/dynamic_debug.h:259:65: note: expanded from macro '_dynamic_func_call_cls'
     259 |         __dynamic_func_call_cls(__UNIQUE_ID(ddebug), cls, fmt, func, ##__VA_ARGS__)
         |                                                                        ^~~~~~~~~~~
   include/linux/dynamic_debug.h:231:15: note: expanded from macro '__dynamic_func_call_cls'
     231 |                 func(&id, ##__VA_ARGS__);                       \
         |                             ^~~~~~~~~~~
   drivers/net/wireless/mediatek/mt76/mt7925/main.c:662:5: warning: format specifies type 'unsigned long' but the argument has type 'unsigned int' [-Wformat]
     661 |                                 "mt7925: MLO ROC throttled, %lu ms remaining\n",
         |                                                             ~~~
         |                                                             %u
     662 |                                 jiffies_to_msecs(throttle));
         |                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/dev_printk.h:165:39: note: expanded from macro 'dev_dbg'
     165 |         dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
         |                                      ~~~     ^~~~~~~~~~~
   include/linux/dynamic_debug.h:285:19: note: expanded from macro 'dynamic_dev_dbg'
     285 |                            dev, fmt, ##__VA_ARGS__)
         |                                 ~~~    ^~~~~~~~~~~
   include/linux/dynamic_debug.h:261:59: note: expanded from macro '_dynamic_func_call'
     261 |         _dynamic_func_call_cls(_DPRINTK_CLASS_DFLT, fmt, func, ##__VA_ARGS__)
         |                                                                  ^~~~~~~~~~~
   include/linux/dynamic_debug.h:259:65: note: expanded from macro '_dynamic_func_call_cls'
     259 |         __dynamic_func_call_cls(__UNIQUE_ID(ddebug), cls, fmt, func, ##__VA_ARGS__)
         |                                                                        ^~~~~~~~~~~
   include/linux/dynamic_debug.h:231:15: note: expanded from macro '__dynamic_func_call_cls'
     231 |                 func(&id, ##__VA_ARGS__);                       \
         |                             ^~~~~~~~~~~
   2 warnings generated.


vim +611 drivers/net/wireless/mediatek/mt76/mt7925/main.c

   592	
   593	static int mt7925_set_roc(struct mt792x_phy *phy,
   594				  struct mt792x_bss_conf *mconf,
   595				  struct ieee80211_channel *chan,
   596				  int duration,
   597				  enum mt7925_roc_req type)
   598	{
   599		unsigned long throttle;
   600		int err;
   601	
   602		/* Check rate limiting - if in backoff period, wait or return busy */
   603		throttle = mt7925_roc_throttle_check(phy);
   604		if (throttle) {
   605			/* For short backoffs, wait; for longer ones, return busy */
   606			if (throttle < msecs_to_jiffies(200)) {
   607				msleep(jiffies_to_msecs(throttle));
   608			} else {
   609				dev_dbg(phy->dev->mt76.dev,
   610					"mt7925: ROC throttled, %lu ms remaining\n",
 > 611					jiffies_to_msecs(throttle));
   612				return -EBUSY;
   613			}
   614		}
   615	
   616		/* Clear stale abort flag from previous ROC */
   617		clear_bit(MT76_STATE_ROC_ABORT, &phy->mt76->state);
   618	
   619		if (test_and_set_bit(MT76_STATE_ROC, &phy->mt76->state))
   620			return -EBUSY;
   621	
   622		phy->roc_grant = false;
   623	
   624		err = mt7925_mcu_set_roc(phy, mconf, chan, duration, type,
   625					 ++phy->roc_token_id);
   626		if (err < 0) {
   627			clear_bit(MT76_STATE_ROC, &phy->mt76->state);
   628			goto out;
   629		}
   630	
   631		if (!wait_event_timeout(phy->roc_wait, phy->roc_grant, 4 * HZ)) {
   632			mt7925_mcu_abort_roc(phy, mconf, phy->roc_token_id);
   633			clear_bit(MT76_STATE_ROC, &phy->mt76->state);
   634			mt7925_roc_record_timeout(phy);
   635			err = -ETIMEDOUT;
   636		} else {
   637			/* Successful ROC - reset timeout tracking */
   638			mt7925_roc_clear_timeout(phy);
   639		}
   640	
   641	out:
   642		return err;
   643	}
   644	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* [PATCH v2] wifi: p54: Fix memory leak in p54_beacon_update()
From: Zilin Guan @ 2026-01-20 13:01 UTC (permalink / raw)
  To: chunkeey
  Cc: quic_rdevanat, johannes.berg, linux-wireless, linux-kernel,
	johannes, jianhao.xu, Zilin Guan

In p54_beacon_update(), beacon is allocated via ieee80211_beacon_get().
If p54_beacon_format_ie_tim() fails, the function returns immediately
without freeing the allocated beacon skb, leading to a memory leak.

Since no other references to this memory exist, it must be freed locally
before returning the error. Fix this by freeing the buffer using
dev_kfree_skb_any() in the error path.

Compile tested only. Issue found using a prototype static analysis tool
and code review.

Fixes: e5ea92a7528d ("p54: AP & Ad-hoc testing")
Signed-off-by: Zilin Guan <zilin@seu.edu.cn>
---
Changes in v2:
- Correct the Fixes tag to point to the commit that introduced this issue.

 drivers/net/wireless/intersil/p54/main.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/intersil/p54/main.c b/drivers/net/wireless/intersil/p54/main.c
index 2ec3655f1a9c..57a62108cbc3 100644
--- a/drivers/net/wireless/intersil/p54/main.c
+++ b/drivers/net/wireless/intersil/p54/main.c
@@ -143,8 +143,10 @@ static int p54_beacon_update(struct p54_common *priv,
 	if (!beacon)
 		return -ENOMEM;
 	ret = p54_beacon_format_ie_tim(beacon);
-	if (ret)
+	if (ret) {
+		dev_kfree_skb_any(beacon);
 		return ret;
+	}
 
 	/*
 	 * During operation, the firmware takes care of beaconing.
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH] wifi: p54: Fix memory leak in p54_beacon_update()
From: Zilin Guan @ 2026-01-20 12:47 UTC (permalink / raw)
  To: johannes; +Cc: chunkeey, linux-kernel, linux-wireless, quic_rdevanat, zilin
In-Reply-To: <01724c7be8e023f6b5cd0ac6b48c3e125bc1fd9b.camel@sipsolutions.net>

On Tue, Jan 20, 2026 at 09:16:05AM +0100, Johannes Berg wrote:
> On Mon, 2026-01-19 at 11:31 +0000, Zilin Guan wrote:
> > In p54_beacon_update(), beacon is allocated via ieee80211_beacon_get().
> > If p54_beacon_format_ie_tim() fails, the function returns immediately
> > without freeing the allocated beacon skb, leading to a memory leak.
> > 
> > Since no other references to this memory exist, it must be freed locally
> > before returning the error. Fix this by freeing the buffer using
> > dev_kfree_skb_any() in the error path.
> > 
> > Compile tested only. Issue found using a prototype static analysis tool
> > and code review.
> > 
> > Fixes: 0ac0d6cedf61 ("p54: Move mac80211 glue code")
> 
> That doesn't seem right, although that commit didn't really "move" code,
> it added unused code ... but I think that it probably could go further
> back.
> 
> johannes

Thanks for pointing this out. I traced it further back and found the issue
was introduced in commit e5ea92a7528d ("p54: AP & Ad-hoc testing").

I will update the Fixes tag and send v2.

Regards,
Zilin Guan

^ permalink raw reply

* Re: [PATCH 11/11] wifi: mt76: mt7925: fix ROC deadlocks and race conditions
From: kernel test robot @ 2026-01-20 11:42 UTC (permalink / raw)
  To: Zac, sean.wang
  Cc: oe-kbuild-all, deren.wu, kvalo, linux-kernel, linux-mediatek,
	linux-wireless, lorenzo, nbd, ryder.lee, sean.wang, stable, linux,
	zbowling, Zac Bowling
In-Reply-To: <20260120062854.126501-12-zac@zacbowling.com>

Hi Zac,

kernel test robot noticed the following build warnings:

[auto build test WARNING on wireless-next/main]
[also build test WARNING on wireless/main linus/master v6.19-rc6 next-20260119]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Zac/wifi-mt76-fix-list-corruption-in-mt76_wcid_cleanup/20260120-143842
base:   https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
patch link:    https://lore.kernel.org/r/20260120062854.126501-12-zac%40zacbowling.com
patch subject: [PATCH 11/11] wifi: mt76: mt7925: fix ROC deadlocks and race conditions
config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20260120/202601201954.zxO1N1DS-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260120/202601201954.zxO1N1DS-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601201954.zxO1N1DS-lkp@intel.com/

All warnings (new ones prefixed by >>):

   In file included from include/linux/printk.h:621,
                    from include/linux/kernel.h:31,
                    from include/linux/skbuff.h:13,
                    from include/linux/if_ether.h:19,
                    from include/linux/etherdevice.h:20,
                    from drivers/net/wireless/mediatek/mt76/mt7925/main.c:4:
   drivers/net/wireless/mediatek/mt76/mt7925/main.c: In function 'mt7925_set_roc':
>> drivers/net/wireless/mediatek/mt76/mt7925/main.c:610:33: warning: format '%lu' expects argument of type 'long unsigned int', but argument 4 has type 'unsigned int' [-Wformat=]
     610 |                                 "mt7925: ROC throttled, %lu ms remaining\n",
         |                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/dynamic_debug.h:231:29: note: in definition of macro '__dynamic_func_call_cls'
     231 |                 func(&id, ##__VA_ARGS__);                       \
         |                             ^~~~~~~~~~~
   include/linux/dynamic_debug.h:261:9: note: in expansion of macro '_dynamic_func_call_cls'
     261 |         _dynamic_func_call_cls(_DPRINTK_CLASS_DFLT, fmt, func, ##__VA_ARGS__)
         |         ^~~~~~~~~~~~~~~~~~~~~~
   include/linux/dynamic_debug.h:284:9: note: in expansion of macro '_dynamic_func_call'
     284 |         _dynamic_func_call(fmt, __dynamic_dev_dbg,              \
         |         ^~~~~~~~~~~~~~~~~~
   include/linux/dev_printk.h:165:9: note: in expansion of macro 'dynamic_dev_dbg'
     165 |         dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
         |         ^~~~~~~~~~~~~~~
   include/linux/dev_printk.h:165:30: note: in expansion of macro 'dev_fmt'
     165 |         dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
         |                              ^~~~~~~
   drivers/net/wireless/mediatek/mt76/mt7925/main.c:609:25: note: in expansion of macro 'dev_dbg'
     609 |                         dev_dbg(phy->dev->mt76.dev,
         |                         ^~~~~~~
   drivers/net/wireless/mediatek/mt76/mt7925/main.c:610:59: note: format string is defined here
     610 |                                 "mt7925: ROC throttled, %lu ms remaining\n",
         |                                                         ~~^
         |                                                           |
         |                                                           long unsigned int
         |                                                         %u
   drivers/net/wireless/mediatek/mt76/mt7925/main.c: In function 'mt7925_set_mlo_roc':
   drivers/net/wireless/mediatek/mt76/mt7925/main.c:661:33: warning: format '%lu' expects argument of type 'long unsigned int', but argument 4 has type 'unsigned int' [-Wformat=]
     661 |                                 "mt7925: MLO ROC throttled, %lu ms remaining\n",
         |                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/dynamic_debug.h:231:29: note: in definition of macro '__dynamic_func_call_cls'
     231 |                 func(&id, ##__VA_ARGS__);                       \
         |                             ^~~~~~~~~~~
   include/linux/dynamic_debug.h:261:9: note: in expansion of macro '_dynamic_func_call_cls'
     261 |         _dynamic_func_call_cls(_DPRINTK_CLASS_DFLT, fmt, func, ##__VA_ARGS__)
         |         ^~~~~~~~~~~~~~~~~~~~~~
   include/linux/dynamic_debug.h:284:9: note: in expansion of macro '_dynamic_func_call'
     284 |         _dynamic_func_call(fmt, __dynamic_dev_dbg,              \
         |         ^~~~~~~~~~~~~~~~~~
   include/linux/dev_printk.h:165:9: note: in expansion of macro 'dynamic_dev_dbg'
     165 |         dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
         |         ^~~~~~~~~~~~~~~
   include/linux/dev_printk.h:165:30: note: in expansion of macro 'dev_fmt'
     165 |         dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
         |                              ^~~~~~~
   drivers/net/wireless/mediatek/mt76/mt7925/main.c:660:25: note: in expansion of macro 'dev_dbg'
     660 |                         dev_dbg(phy->dev->mt76.dev,
         |                         ^~~~~~~
   drivers/net/wireless/mediatek/mt76/mt7925/main.c:661:63: note: format string is defined here
     661 |                                 "mt7925: MLO ROC throttled, %lu ms remaining\n",
         |                                                             ~~^
         |                                                               |
         |                                                               long unsigned int
         |                                                             %u


vim +610 drivers/net/wireless/mediatek/mt76/mt7925/main.c

   592	
   593	static int mt7925_set_roc(struct mt792x_phy *phy,
   594				  struct mt792x_bss_conf *mconf,
   595				  struct ieee80211_channel *chan,
   596				  int duration,
   597				  enum mt7925_roc_req type)
   598	{
   599		unsigned long throttle;
   600		int err;
   601	
   602		/* Check rate limiting - if in backoff period, wait or return busy */
   603		throttle = mt7925_roc_throttle_check(phy);
   604		if (throttle) {
   605			/* For short backoffs, wait; for longer ones, return busy */
   606			if (throttle < msecs_to_jiffies(200)) {
   607				msleep(jiffies_to_msecs(throttle));
   608			} else {
   609				dev_dbg(phy->dev->mt76.dev,
 > 610					"mt7925: ROC throttled, %lu ms remaining\n",
   611					jiffies_to_msecs(throttle));
   612				return -EBUSY;
   613			}
   614		}
   615	
   616		/* Clear stale abort flag from previous ROC */
   617		clear_bit(MT76_STATE_ROC_ABORT, &phy->mt76->state);
   618	
   619		if (test_and_set_bit(MT76_STATE_ROC, &phy->mt76->state))
   620			return -EBUSY;
   621	
   622		phy->roc_grant = false;
   623	
   624		err = mt7925_mcu_set_roc(phy, mconf, chan, duration, type,
   625					 ++phy->roc_token_id);
   626		if (err < 0) {
   627			clear_bit(MT76_STATE_ROC, &phy->mt76->state);
   628			goto out;
   629		}
   630	
   631		if (!wait_event_timeout(phy->roc_wait, phy->roc_grant, 4 * HZ)) {
   632			mt7925_mcu_abort_roc(phy, mconf, phy->roc_token_id);
   633			clear_bit(MT76_STATE_ROC, &phy->mt76->state);
   634			mt7925_roc_record_timeout(phy);
   635			err = -ETIMEDOUT;
   636		} else {
   637			/* Successful ROC - reset timeout tracking */
   638			mt7925_roc_clear_timeout(phy);
   639		}
   640	
   641	out:
   642		return err;
   643	}
   644	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH v2 1/3] sdio: Provide a bustype shutdown function
From: Johannes Berg @ 2026-01-20 10:29 UTC (permalink / raw)
  To: Ulf Hansson, Uwe Kleine-König
  Cc: Ping-Ke Shih, linux-wireless, linux-mmc
In-Reply-To: <CAPDyKFrinbj0QdL4rAP7zCvcnc6kGRQDTbiq1H0nRd+-B+HWnw@mail.gmail.com>

On Tue, 2026-01-20 at 11:27 +0100, Ulf Hansson wrote:
> On Mon, 12 Jan 2026 at 16:47, Uwe Kleine-König
> <u.kleine-koenig@baylibre.com> wrote:
> > 
> > To prepare sdio drivers to migrate away from struct device_driver::shutdown
> > (and then eventually remove that callback) create a serdev driver shutdown
> > callback and migration code to keep the existing behaviour. Note this
> > introduces a warning for each driver that isn't converted yet to that
> > callback at register time.
> > 
> > Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
> 
> Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
> 
> Johannes, please pick this via your tree. And sorry for the delay in
> reviewing this!

Will do, no worries at all, thanks!

johannes

^ permalink raw reply

* Re: [PATCH v2 1/3] sdio: Provide a bustype shutdown function
From: Ulf Hansson @ 2026-01-20 10:27 UTC (permalink / raw)
  To: Uwe Kleine-König, Johannes Berg
  Cc: Ping-Ke Shih, linux-wireless, linux-mmc
In-Reply-To: <397f45c2818f6632151f92b70e547262f373c3b6.1768232321.git.u.kleine-koenig@baylibre.com>

On Mon, 12 Jan 2026 at 16:47, Uwe Kleine-König
<u.kleine-koenig@baylibre.com> wrote:
>
> To prepare sdio drivers to migrate away from struct device_driver::shutdown
> (and then eventually remove that callback) create a serdev driver shutdown
> callback and migration code to keep the existing behaviour. Note this
> introduces a warning for each driver that isn't converted yet to that
> callback at register time.
>
> Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>

Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>

Johannes, please pick this via your tree. And sorry for the delay in
reviewing this!

Kind regards
Uffe

> ---
>  drivers/mmc/core/sdio_bus.c   | 25 +++++++++++++++++++++++++
>  include/linux/mmc/sdio_func.h |  1 +
>  2 files changed, 26 insertions(+)
>
> diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c
> index 10799772494a..6e5bdc2f0cc8 100644
> --- a/drivers/mmc/core/sdio_bus.c
> +++ b/drivers/mmc/core/sdio_bus.c
> @@ -232,6 +232,15 @@ static void sdio_bus_remove(struct device *dev)
>                 pm_runtime_put_sync(dev);
>  }
>
> +static void sdio_bus_shutdown(struct device *dev)
> +{
> +       struct sdio_driver *drv = to_sdio_driver(dev->driver);
> +       struct sdio_func *func = dev_to_sdio_func(dev);
> +
> +       if (dev->driver && drv->shutdown)
> +               drv->shutdown(func);
> +}
> +
>  static const struct dev_pm_ops sdio_bus_pm_ops = {
>         SET_SYSTEM_SLEEP_PM_OPS(pm_generic_suspend, pm_generic_resume)
>         SET_RUNTIME_PM_OPS(
> @@ -248,6 +257,7 @@ static const struct bus_type sdio_bus_type = {
>         .uevent         = sdio_bus_uevent,
>         .probe          = sdio_bus_probe,
>         .remove         = sdio_bus_remove,
> +       .shutdown       = sdio_bus_shutdown,
>         .pm             = &sdio_bus_pm_ops,
>  };
>
> @@ -261,6 +271,14 @@ void sdio_unregister_bus(void)
>         bus_unregister(&sdio_bus_type);
>  }
>
> +static void sdio_legacy_shutdown(struct sdio_func *func)
> +{
> +       struct device *dev = &func->dev;
> +       struct device_driver *driver = dev->driver;
> +
> +       driver->shutdown(dev);
> +}
> +
>  /**
>   *     __sdio_register_driver - register a function driver
>   *     @drv: SDIO function driver
> @@ -272,6 +290,13 @@ int __sdio_register_driver(struct sdio_driver *drv, struct module *owner)
>         drv->drv.bus = &sdio_bus_type;
>         drv->drv.owner = owner;
>
> +       /*
> +        * This driver needs updating. Note that driver_register() warns about
> +        * this, so we're not adding another warning here.
> +        */
> +       if (!drv->shutdown && drv->drv.shutdown)
> +               drv->shutdown = sdio_legacy_shutdown;
> +
>         return driver_register(&drv->drv);
>  }
>  EXPORT_SYMBOL_GPL(__sdio_register_driver);
> diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h
> index fed1f5f4a8d3..4534bf462aac 100644
> --- a/include/linux/mmc/sdio_func.h
> +++ b/include/linux/mmc/sdio_func.h
> @@ -78,6 +78,7 @@ struct sdio_driver {
>
>         int (*probe)(struct sdio_func *, const struct sdio_device_id *);
>         void (*remove)(struct sdio_func *);
> +       void (*shutdown)(struct sdio_func *);
>
>         struct device_driver drv;
>  };
> --
> 2.47.3
>

^ permalink raw reply

* Re: [PATCH v2 1/3] sdio: Provide a bustype shutdown function
From: Ulf Hansson @ 2026-01-20 10:25 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: Ping-Ke Shih, Johannes Berg, linux-wireless, linux-mmc
In-Reply-To: <v5jqdtpdj3zpuyo5owlujvifphjjxyygfrgqnmql664ck6vpc4@yvihqvp4lgka>

On Mon, 19 Jan 2026 at 19:25, Uwe Kleine-König
<u.kleine-koenig@baylibre.com> wrote:
>
> Hello Ulf,
>
> On Mon, Jan 19, 2026 at 04:00:48PM +0100, Ulf Hansson wrote:
> > On Mon, 12 Jan 2026 at 16:47, Uwe Kleine-König
> > <u.kleine-koenig@baylibre.com> wrote:
> > > @@ -272,6 +290,13 @@ int __sdio_register_driver(struct sdio_driver *drv, struct module *owner)
> > >         drv->drv.bus = &sdio_bus_type;
> > >         drv->drv.owner = owner;
> > >
> > > +       /*
> > > +        * This driver needs updating. Note that driver_register() warns about
> > > +        * this, so we're not adding another warning here.
> > > +        */
> > > +       if (!drv->shutdown && drv->drv.shutdown)
> > > +               drv->shutdown = sdio_legacy_shutdown;
> > > +
> >
> > Is this added only to keep the series bisectable or are there other
> > (except those you fix in the series) sdio func drivers that make use
> > of the shutdown callback?
>
> It's kept because I don't know if there are any other sdio driver in
> flight and these would break silently when they are applied between this
> series and the removal of the callbacks from struct device_driver.
>
> > In any case, when are you planning to remove this?
>
> So my plan is to remove this in a series where the last patch is the
> modification to struct driver.

Okay, thanks for clarifying!

Kind regards
Uffe

^ permalink raw reply

* Re: [PATCH wireless-next] wifi: cfg80211: don't apply HT flags to S1G channels
From: Lachlan Hodges @ 2026-01-20 10:08 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, arien.judge
In-Reply-To: <4dd2558fc13dd974bf551653b3db9c70feed73fe.camel@sipsolutions.net>

> > channel=165
> > 
> > vht_oper_centr_freq_seg0_idx=171
> 
> > This is the result of selecting mode AX, channel 165 with a width of 80MHz.
> 
> Well, I guess that could be done in some countries, U-NII-4 goes up to
> channel 177 inclusive? But not for Australia which only has up to 173
> (according to Wikipedia, maybe it's changing, I didn't check better
> sources now.)

Yea should've clarified that earlier.. kind of an important detail ^.^

> I don't think hwsim will get too many wifi8 changes, but yeah there are
> a lot of things brewing now. Seems you (mostly?) got just ahead of it
> though, so it shouldn't be much of an issue, I'd think.

Yea I think we picked a good time to get the major stuff in, now I suppose
it's just a driver .. and the fun challenge of getting S1G into hostapd ._.

lachlan

^ permalink raw reply

* Re: [PATCH iwlwifi] wifi: mac80211: ignore reserved bits in reconfiguration status
From: Johannes Berg @ 2026-01-20  9:09 UTC (permalink / raw)
  To: Miri Korenblit, linux-wireless; +Cc: Benjamin Berg, Ilan Peer
In-Reply-To: <20260118095410.e54a0830f697.I9aef8f4fb6f1b06671bb6cf0e2bd4ec6e4c8bda4@changeid>

On Sun, 2026-01-18 at 09:54 +0200, Miri Korenblit wrote:
> 
> +++ b/net/mac80211/mlme.c
> @@ -10243,7 +10243,7 @@ void ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
>  	for (i = 0; i < mgmt->u.action.u.ml_reconf_resp.count; i++) {
>  		u16 status = get_unaligned_le16(pos + 1);
>  
> -		link_id = *pos;
> +		link_id = u8_get_bits(*pos, 0xf);

Seems like there should be a constant for that? Or maybe even some kind
of struct for the "Reconfiguration Status Duple subfield", rather than
parsing the things separately?

johannes

^ permalink raw reply

* Re: [PATCH 11/11] wifi: mt76: mt7925: fix ROC deadlocks and race conditions
From: Sean Wang @ 2026-01-20  8:25 UTC (permalink / raw)
  To: Zac
  Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
	lorenzo, nbd, ryder.lee, sean.wang, stable, linux, zbowling
In-Reply-To: <20260120062854.126501-12-zac@zacbowling.com>

On Tue, Jan 20, 2026 at 12:29 AM Zac <zac@zacbowling.com> wrote:
>
> From: Zac Bowling <zac@zacbowling.com>
>
> Fix multiple interrelated issues in the remain-on-channel (ROC) handling
> that cause deadlocks, race conditions, and resource leaks.
>
> Problems fixed:
>
> 1. Deadlock in sta removal ROC abort path:
>    When a station is removed while a ROC operation is in progress, the
>    driver would call mt7925_roc_abort_sync() which waits for ROC completion.
>    However, the ROC work itself needs to acquire mt792x_mutex which is
>    already held during station removal, causing a deadlock.
>
>    Fix: Use async ROC abort (mt76_connac_mcu_abort_roc) when called from
>    paths that already hold the mutex, and add MT76_STATE_ROC_ABORT flag
>    to coordinate between the abort and the ROC timer.
>

Hi Zac,

Thanks for your continued efforts on the driver.
We’ve sent a patch to address the mt7925 deadlock at the link below:
https://lists.infradead.org/pipermail/linux-mediatek/2025-December/102164.html
We plan to send the same fix to mt7921 as well.

I had a couple of questions and suggestions:
1. Would it be possible to rebase your patchset on top of this fix
(and any other pending patches that are not yet merged)? We noticed
some conflicts when applying the series, and rebasing it this way
would make it easier for nbd to integrate the full patchset.
2. Could you please elaborate on the test scenarios that would trigger
ROC rate limiting for MLO authentication failures? If I recall
correctly, ROC operations are typically handled sequentially unless
multiple interfaces are created on the same physical device. In that
case, how many virtual interfaces and which operating modes (GC/STA or
multiple STAs) are required to reproduce the issue?

I will try to prepare an out-of-tree branch with the current pending
patches to help your patchset integrate more smoothly. Thanks for
collecting community issues and fixes and incorporating them into the
driver.

             Sean

> 2. ROC timer race during suspend:
>    The ROC timer could fire after the device started suspending but before
>    the ROC was properly aborted, causing undefined behavior.
>
>    Fix: Delete ROC timer synchronously before suspend and check device
>    state before processing ROC timeout.
>
> 3. ROC rate limiting for MLO auth failures:
>    Rapid ROC requests during MLO authentication can overwhelm the firmware,
>    causing authentication timeouts. The MT7925 firmware has limited ROC
>    handling capacity.
>
>    Fix: Add rate limiting infrastructure with configurable minimum interval
>    between ROC requests. Track last ROC completion time and defer new
>    requests if they arrive too quickly.
>
> 4. WCID leak in ROC cleanup:
>    When ROC operations are aborted, the associated WCID resources were
>    not being properly released, causing resource exhaustion over time.
>
>    Fix: Ensure WCID cleanup happens in all ROC termination paths.
>
> 5. Async ROC abort race condition:
>    The async ROC abort could race with normal ROC completion, causing
>    double-free or use-after-free of ROC resources.
>
>    Fix: Use MT76_STATE_ROC_ABORT flag and proper synchronization to
>    prevent races between async abort and normal completion paths.
>
> These fixes work together to provide robust ROC handling that doesn't
> deadlock, properly releases resources, and handles edge cases during
> suspend and MLO operations.
>
> Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 device")
> Signed-off-by: Zac Bowling <zac@zacbowling.com>
> ---
>  drivers/net/wireless/mediatek/mt76/mt76.h     |   1 +
>  .../net/wireless/mediatek/mt76/mt7925/main.c  | 175 ++++++++++++++++--
>  drivers/net/wireless/mediatek/mt76/mt792x.h   |   7 +
>  3 files changed, 170 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
> index d05e83ea1cac..91f9dd95c89e 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt76.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt76.h
> @@ -511,6 +511,7 @@ enum {
>         MT76_STATE_POWER_OFF,
>         MT76_STATE_SUSPEND,
>         MT76_STATE_ROC,
> +       MT76_STATE_ROC_ABORT,
>         MT76_STATE_PM,
>         MT76_STATE_WED_RESET,
>  };
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> index cc7ef2c17032..2404f7812897 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> @@ -453,6 +453,24 @@ static void mt7925_roc_iter(void *priv, u8 *mac,
>         mt7925_mcu_abort_roc(phy, &mvif->bss_conf, phy->roc_token_id);
>  }
>
> +/* Async ROC abort - safe to call while holding mutex.
> + * Sets abort flag and lets roc_work handle cleanup without blocking.
> + * This prevents deadlock when called from sta_remove path which holds mutex.
> + */
> +static void mt7925_roc_abort_async(struct mt792x_dev *dev)
> +{
> +       struct mt792x_phy *phy = &dev->phy;
> +
> +       /* Set abort flag - roc_work checks this before acquiring mutex */
> +       set_bit(MT76_STATE_ROC_ABORT, &phy->mt76->state);
> +
> +       /* Stop timer and schedule work to handle cleanup.
> +        * Must schedule work since timer may not have fired yet.
> +        */
> +       timer_delete(&phy->roc_timer);
> +       ieee80211_queue_work(phy->mt76->hw, &phy->roc_work);
> +}
> +
>  void mt7925_roc_abort_sync(struct mt792x_dev *dev)
>  {
>         struct mt792x_phy *phy = &dev->phy;
> @@ -473,6 +491,17 @@ void mt7925_roc_work(struct work_struct *work)
>         phy = (struct mt792x_phy *)container_of(work, struct mt792x_phy,
>                                                 roc_work);
>
> +       /* Check abort flag BEFORE acquiring mutex to prevent deadlock.
> +        * If abort is requested while we're in the sta_remove path (which
> +        * holds the mutex), we must not try to acquire it or we'll deadlock.
> +        * Clear the flags and only notify mac80211 if ROC was actually active.
> +        */
> +       if (test_and_clear_bit(MT76_STATE_ROC_ABORT, &phy->mt76->state)) {
> +               if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
> +                       ieee80211_remain_on_channel_expired(phy->mt76->hw);
> +               return;
> +       }
> +
>         if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
>                 return;
>
> @@ -500,14 +529,93 @@ static int mt7925_abort_roc(struct mt792x_phy *phy,
>         return err;
>  }
>
> +/* ROC rate limiting constants - exponential backoff to prevent MCU overload
> + * when upper layers trigger rapid reconnection cycles (e.g., MLO auth failures).
> + * Max backoff ~1.6s, resets after 10s of no timeouts.
> + */
> +#define MT7925_ROC_BACKOFF_BASE_MS     100
> +#define MT7925_ROC_BACKOFF_MAX_MS      1600
> +#define MT7925_ROC_TIMEOUT_RESET_MS    10000
> +#define MT7925_ROC_TIMEOUT_WARN_THRESH 5
> +
> +/* Check if ROC should be throttled due to recent timeouts.
> + * Returns delay in jiffies if throttling, 0 if OK to proceed.
> + */
> +static unsigned long mt7925_roc_throttle_check(struct mt792x_phy *phy)
> +{
> +       unsigned long now = jiffies;
> +
> +       /* Reset timeout counter if it's been a while since last timeout */
> +       if (phy->roc_timeout_count &&
> +           time_after(now, phy->roc_last_timeout +
> +                      msecs_to_jiffies(MT7925_ROC_TIMEOUT_RESET_MS))) {
> +               phy->roc_timeout_count = 0;
> +               phy->roc_backoff_until = 0;
> +       }
> +
> +       /* Check if we're still in backoff period */
> +       if (phy->roc_backoff_until && time_before(now, phy->roc_backoff_until))
> +               return phy->roc_backoff_until - now;
> +
> +       return 0;
> +}
> +
> +/* Record ROC timeout and calculate backoff period */
> +static void mt7925_roc_record_timeout(struct mt792x_phy *phy)
> +{
> +       unsigned int backoff_ms;
> +
> +       phy->roc_last_timeout = jiffies;
> +       phy->roc_timeout_count++;
> +
> +       /* Exponential backoff: 100ms, 200ms, 400ms, 800ms, 1600ms (capped) */
> +       backoff_ms = MT7925_ROC_BACKOFF_BASE_MS <<
> +                    min_t(u8, phy->roc_timeout_count - 1, 4);
> +       if (backoff_ms > MT7925_ROC_BACKOFF_MAX_MS)
> +               backoff_ms = MT7925_ROC_BACKOFF_MAX_MS;
> +
> +       phy->roc_backoff_until = jiffies + msecs_to_jiffies(backoff_ms);
> +
> +       /* Warn if we're seeing repeated timeouts - likely upper layer issue */
> +       if (phy->roc_timeout_count == MT7925_ROC_TIMEOUT_WARN_THRESH)
> +               dev_warn(phy->dev->mt76.dev,
> +                        "mt7925: %u consecutive ROC timeouts, possible mac80211/wpa_supplicant issue (MLO key race?)\n",
> +                        phy->roc_timeout_count);
> +}
> +
> +/* Clear timeout tracking on successful ROC */
> +static void mt7925_roc_clear_timeout(struct mt792x_phy *phy)
> +{
> +       phy->roc_timeout_count = 0;
> +       phy->roc_backoff_until = 0;
> +}
> +
>  static int mt7925_set_roc(struct mt792x_phy *phy,
>                           struct mt792x_bss_conf *mconf,
>                           struct ieee80211_channel *chan,
>                           int duration,
>                           enum mt7925_roc_req type)
>  {
> +       unsigned long throttle;
>         int err;
>
> +       /* Check rate limiting - if in backoff period, wait or return busy */
> +       throttle = mt7925_roc_throttle_check(phy);
> +       if (throttle) {
> +               /* For short backoffs, wait; for longer ones, return busy */
> +               if (throttle < msecs_to_jiffies(200)) {
> +                       msleep(jiffies_to_msecs(throttle));
> +               } else {
> +                       dev_dbg(phy->dev->mt76.dev,
> +                               "mt7925: ROC throttled, %lu ms remaining\n",
> +                               jiffies_to_msecs(throttle));
> +                       return -EBUSY;
> +               }
> +       }
> +
> +       /* Clear stale abort flag from previous ROC */
> +       clear_bit(MT76_STATE_ROC_ABORT, &phy->mt76->state);
> +
>         if (test_and_set_bit(MT76_STATE_ROC, &phy->mt76->state))
>                 return -EBUSY;
>
> @@ -523,7 +631,11 @@ static int mt7925_set_roc(struct mt792x_phy *phy,
>         if (!wait_event_timeout(phy->roc_wait, phy->roc_grant, 4 * HZ)) {
>                 mt7925_mcu_abort_roc(phy, mconf, phy->roc_token_id);
>                 clear_bit(MT76_STATE_ROC, &phy->mt76->state);
> +               mt7925_roc_record_timeout(phy);
>                 err = -ETIMEDOUT;
> +       } else {
> +               /* Successful ROC - reset timeout tracking */
> +               mt7925_roc_clear_timeout(phy);
>         }
>
>  out:
> @@ -534,8 +646,27 @@ static int mt7925_set_mlo_roc(struct mt792x_phy *phy,
>                               struct mt792x_bss_conf *mconf,
>                               u16 sel_links)
>  {
> +       unsigned long throttle;
>         int err;
>
> +       /* Check rate limiting - MLO ROC is especially prone to rapid-fire
> +        * during reconnection cycles after MLO authentication failures.
> +        */
> +       throttle = mt7925_roc_throttle_check(phy);
> +       if (throttle) {
> +               if (throttle < msecs_to_jiffies(200)) {
> +                       msleep(jiffies_to_msecs(throttle));
> +               } else {
> +                       dev_dbg(phy->dev->mt76.dev,
> +                               "mt7925: MLO ROC throttled, %lu ms remaining\n",
> +                               jiffies_to_msecs(throttle));
> +                       return -EBUSY;
> +               }
> +       }
> +
> +       /* Clear stale abort flag from previous ROC */
> +       clear_bit(MT76_STATE_ROC_ABORT, &phy->mt76->state);
> +
>         if (WARN_ON_ONCE(test_and_set_bit(MT76_STATE_ROC, &phy->mt76->state)))
>                 return -EBUSY;
>
> @@ -550,7 +681,10 @@ static int mt7925_set_mlo_roc(struct mt792x_phy *phy,
>         if (!wait_event_timeout(phy->roc_wait, phy->roc_grant, 4 * HZ)) {
>                 mt7925_mcu_abort_roc(phy, mconf, phy->roc_token_id);
>                 clear_bit(MT76_STATE_ROC, &phy->mt76->state);
> +               mt7925_roc_record_timeout(phy);
>                 err = -ETIMEDOUT;
> +       } else {
> +               mt7925_roc_clear_timeout(phy);
>         }
>
>  out:
> @@ -567,6 +701,7 @@ static int mt7925_remain_on_channel(struct ieee80211_hw *hw,
>         struct mt792x_phy *phy = mt792x_hw_phy(hw);
>         int err;
>
> +       cancel_work_sync(&phy->roc_work);
>         mt792x_mutex_acquire(phy->dev);
>         err = mt7925_set_roc(phy, &mvif->bss_conf,
>                              chan, duration, MT7925_ROC_REQ_ROC);
> @@ -874,14 +1009,14 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
>         if (!mlink)
>                 return -EINVAL;
>
> -       idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT792x_WTBL_STA - 1);
> -       if (idx < 0)
> -               return -ENOSPC;
> -
>         mconf = mt792x_vif_to_link(mvif, link_id);
>         if (!mconf)
>                 return -EINVAL;
>
> +       idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT792x_WTBL_STA - 1);
> +       if (idx < 0)
> +               return -ENOSPC;
> +
>         mt76_wcid_init(&mlink->wcid, 0);
>         mlink->wcid.sta = 1;
>         mlink->wcid.idx = idx;
> @@ -901,14 +1036,16 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
>
>         ret = mt76_connac_pm_wake(&dev->mphy, &dev->pm);
>         if (ret)
> -               return ret;
> +               goto err_wcid;
>
>         mt7925_mac_wtbl_update(dev, idx,
>                                MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
>
>         link_conf = mt792x_vif_to_bss_conf(vif, link_id);
> -       if (!link_conf)
> -               return -EINVAL;
> +       if (!link_conf) {
> +               ret = -EINVAL;
> +               goto err_wcid;
> +       }
>
>         /* should update bss info before STA add */
>         if (vif->type == NL80211_IFTYPE_STATION && !link_sta->sta->tdls) {
> @@ -920,7 +1057,7 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
>                         ret = mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
>                                                       link_conf, link_sta, false);
>                 if (ret)
> -                       return ret;
> +                       goto err_wcid;
>         }
>
>         if (ieee80211_vif_is_mld(vif) &&
> @@ -928,28 +1065,34 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
>                 ret = mt7925_mcu_sta_update(dev, link_sta, vif, true,
>                                             MT76_STA_INFO_STATE_NONE);
>                 if (ret)
> -                       return ret;
> +                       goto err_wcid;
>         } else if (ieee80211_vif_is_mld(vif) &&
>                    link_sta != mlink->pri_link) {
>                 ret = mt7925_mcu_sta_update(dev, mlink->pri_link, vif,
>                                             true, MT76_STA_INFO_STATE_ASSOC);
>                 if (ret)
> -                       return ret;
> +                       goto err_wcid;
>
>                 ret = mt7925_mcu_sta_update(dev, link_sta, vif, true,
>                                             MT76_STA_INFO_STATE_ASSOC);
>                 if (ret)
> -                       return ret;
> +                       goto err_wcid;
>         } else {
>                 ret = mt7925_mcu_sta_update(dev, link_sta, vif, true,
>                                             MT76_STA_INFO_STATE_NONE);
>                 if (ret)
> -                       return ret;
> +                       goto err_wcid;
>         }
>
>         mt76_connac_power_save_sched(&dev->mphy, &dev->pm);
>
>         return 0;
> +
> +err_wcid:
> +       rcu_assign_pointer(dev->mt76.wcid[idx], NULL);
> +       mt76_wcid_mask_clear(dev->mt76.wcid_mask, idx);
> +       mt76_connac_power_save_sched(&dev->mphy, &dev->pm);
> +       return ret;
>  }
>
>  static int
> @@ -1135,7 +1278,8 @@ static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev,
>         if (!mlink)
>                 return;
>
> -       mt7925_roc_abort_sync(dev);
> +       /* Async abort - caller already holds mutex */
> +       mt7925_roc_abort_async(dev);
>
>         mt76_connac_free_pending_tx_skbs(&dev->pm, &mlink->wcid);
>         mt76_connac_pm_wake(&dev->mphy, &dev->pm);
> @@ -1530,6 +1674,8 @@ static int mt7925_suspend(struct ieee80211_hw *hw,
>         cancel_delayed_work_sync(&dev->pm.ps_work);
>         mt76_connac_free_pending_tx_skbs(&dev->pm, NULL);
>
> +       /* Cancel ROC before quiescing starts */
> +       mt7925_roc_abort_sync(dev);
>         mt792x_mutex_acquire(dev);
>
>         clear_bit(MT76_STATE_RUNNING, &phy->mt76->state);
> @@ -1876,6 +2022,8 @@ static void mt7925_mgd_prepare_tx(struct ieee80211_hw *hw,
>         u16 duration = info->duration ? info->duration :
>                        jiffies_to_msecs(HZ);
>
> +       cancel_work_sync(&mvif->phy->roc_work);
> +
>         mt792x_mutex_acquire(dev);
>         mt7925_set_roc(mvif->phy, &mvif->bss_conf,
>                        mvif->bss_conf.mt76.ctx->def.chan, duration,
> @@ -2033,6 +2181,7 @@ mt7925_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
>         if (old_links == new_links)
>                 return 0;
>
> +       cancel_work_sync(&phy->roc_work);
>         mt792x_mutex_acquire(dev);
>
>         for_each_set_bit(link_id, &rem, IEEE80211_MLD_MAX_NUM_LINKS) {
> diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
> index 8388638ed550..d9c1ea709390 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt792x.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
> @@ -186,6 +186,13 @@ struct mt792x_phy {
>         wait_queue_head_t roc_wait;
>         u8 roc_token_id;
>         bool roc_grant;
> +
> +       /* ROC rate limiting to prevent MCU overload during rapid reconnection
> +        * cycles (e.g., MLO authentication failures causing repeated ROC).
> +        */
> +       u8 roc_timeout_count;           /* consecutive ROC timeouts */
> +       unsigned long roc_last_timeout; /* jiffies of last timeout */
> +       unsigned long roc_backoff_until;/* don't issue ROC until this time */
>  };
>
>  struct mt792x_irq_map {
> --
> 2.52.0
>

^ permalink raw reply

* Re: [PATCH] wifi: p54: Fix memory leak in p54_beacon_update()
From: Johannes Berg @ 2026-01-20  8:16 UTC (permalink / raw)
  To: Zilin Guan, chunkeey; +Cc: quic_rdevanat, linux-wireless, linux-kernel
In-Reply-To: <20260119113145.1433315-1-zilin@seu.edu.cn>

On Mon, 2026-01-19 at 11:31 +0000, Zilin Guan wrote:
> In p54_beacon_update(), beacon is allocated via ieee80211_beacon_get().
> If p54_beacon_format_ie_tim() fails, the function returns immediately
> without freeing the allocated beacon skb, leading to a memory leak.
> 
> Since no other references to this memory exist, it must be freed locally
> before returning the error. Fix this by freeing the buffer using
> dev_kfree_skb_any() in the error path.
> 
> Compile tested only. Issue found using a prototype static analysis tool
> and code review.
> 
> Fixes: 0ac0d6cedf61 ("p54: Move mac80211 glue code")

That doesn't seem right, although that commit didn't really "move" code,
it added unused code ... but I think that it probably could go further
back.

johannes

^ permalink raw reply

* Re: [PATCH] wifi: mac80211: fix NULL pointer dereference in ieee80211_mesh_build_beacon()
From: Johannes Berg @ 2026-01-20  8:12 UTC (permalink / raw)
  To: Jeongjun Park; +Cc: linux-wireless, syzbot+81cd9dc1596563141d19, linux-kernel
In-Reply-To: <20260119150031.201832-1-aha310510@gmail.com>

On Tue, 2026-01-20 at 00:00 +0900, Jeongjun Park wrote:
> NULL pointer dereference bug occurs because ieee80211_mesh_build_beacon()
> does not check the return value of ieee80211_get_sband().
> 
> Therefore, we need to add a return value check to prevent this.

No we don't, please try to understand the code, and why it can even get
here when the mesh interface isn't operating.

johannes

^ permalink raw reply

* Re: [PATCH] mac80211: Fix and improve debugfs TSF access protection
From: syzbot @ 2026-01-20  7:54 UTC (permalink / raw)
  To: johannes
  Cc: arnavrawat2000, johannes, kapoorarnav43, linux-kernel,
	linux-wireless
In-Reply-To: <eece33b4580bb35d28be8ffba7957bde509f59cf.camel@sipsolutions.net>

> On Tue, 2026-01-20 at 01:10 +0530, Arnav Kapoor wrote:
>> This patch comprehensively addresses reviewer feedback on the debugfs TSF
>> access protection:
>
> Please go away until you understood how that contributing to the kernel
> isn't dumping reviewer feedback into an LLM.
>
> johannes

I see the command but can't find the corresponding bug.
The email is sent to  syzbot+HASH@syzkaller.appspotmail.com address
but the HASH does not correspond to any known bug.
Please double check the address.


^ permalink raw reply

* Re: [PATCH] mac80211: Fix and improve debugfs TSF access protection
From: Johannes Berg @ 2026-01-20  7:54 UTC (permalink / raw)
  To: Arnav Kapoor
  Cc: linux-wireless, linux-kernel, syzbot+15f88dfa580000, Arnav Rawat
In-Reply-To: <20260119194057.53339-1-kapoorarnav43@gmail.com>

On Tue, 2026-01-20 at 01:10 +0530, Arnav Kapoor wrote:
> This patch comprehensively addresses reviewer feedback on the debugfs TSF
> access protection:

Please go away until you understood how that contributing to the kernel
isn't dumping reviewer feedback into an LLM.

johannes

^ permalink raw reply

* Re: [PATCH wireless-next] wifi: cfg80211: don't apply HT flags to S1G channels
From: Johannes Berg @ 2026-01-20  7:50 UTC (permalink / raw)
  To: Lachlan Hodges; +Cc: linux-wireless, arien.judge
In-Reply-To: <tyreg7kb5ownpgrbcwo72rk5tevk6jpzbthtlqr5z5zykvhaji@wpybm6oversy>

On Tue, 2026-01-20 at 16:22 +1100, Lachlan Hodges wrote:
> > > Playing around with some 5 GHz configurations in OpenWRT it appears that only
> > > so much information can be conveyed and indeed there are configurations that
> > > will be rejected by cfg80211 (or maybe hostapd in some cases, not sure).
> > 
> > Really? Got an example, perhaps even with hostapd log to see how it's
> > rejected? That clearly contradicts what I wrote above which I really did
> > believe to be true until this moment ;-)
> 
> Sorry, it is indeed cfg80211 that is not accepting the chandef configuration.

Maybe I still know something ;-)

> channel=165
> 
> vht_oper_centr_freq_seg0_idx=171

> This is the result of selecting mode AX, channel 165 with a width of 80MHz.

Well, I guess that could be done in some countries, U-NII-4 goes up to
channel 177 inclusive? But not for Australia which only has up to 173
(according to Wikipedia, maybe it's changing, I didn't check better
sources now.)

> The GL-MT3000 using the mt76 driver with mt7915e driver (?)
> 
> cfg80211              299008  4 mt7915e,mt76_connac_lib,mt76,mac80211
> compat                 12288  3 mt76,mac80211,cfg80211
> mac80211              581632  3 mt7915e,mt76_connac_lib,mt76
> mt76                   73728  2 mt7915e,mt76_connac_lib
> mt76_connac_lib        45056  1 mt7915e
> 
> And for hostapd output (Sorry I don't have anything more verbose)
> 
> Tue Jan 20 04:10:43 2026 daemon.err hostapd: nl80211: kernel reports: (extension) channel is disabled

Right, makes sense, it's at least saying channel 177 doesn't exist or
cannot be used, perhaps already on 169/173 depending on what the device
knows about U-NII-4.

> Tue Jan 20 04:10:43 2026 daemon.err hostapd: Could not set channel for kernel driver
> Tue Jan 20 04:10:43 2026 daemon.err hostapd: Interface initialization failed
> 
> where _cfg80211_chandef_usable() is (rightfully) rejecting the configuration.
> This simply appears to be the logic to generate the config doesn't cover this
> case properly. 

Right.

> Anyways, I don't think this is all that useful too be honest,
> it just comes down to ensuring the static configurations are known especially
> since this range is specified as AUTO-BW anyway as per the regdom.

Yeah, or checking against the regulatory, or something ... but basically
channel 165 + 80 MHz isn't _really_ possible then, since the
channelization plan says then you should use 171 as it does now.

> > > I
> > > suppose what I'm asking is - how much do we need to protect usermode from
> > > this? Do you feel it's worth including a flag that somewhat emulates
> > > NOHT40+/-? I know much of that logic is quite old and S1G is "modern" ... ?
> > 
> > It's always nice to have userspace be aware of things, but I guess if
> > it's a configuration that's statically known to be invalid, it wouldn't
> > matter so much? 

But I think in the case you describe above, userspace should well have
been able to, at least in theory, predict that this would happen, based
on the channel flags.

> > But I don't think I've understood the S1G angle - are
> > you talking about primary 1 MHz? Primary 2 MHz?
> 
> Putting primaries aside and just thinking of operating - eventually we would
> need a NO_2MHZ flag as India for example only has 1MHz channels.. however
> this is well into the future .. we first need to add support for other
> countries :').

:)

> But yes as you said since these configurations are static it's not really a
> big deal and more up to userspace to be aware of it. I think what we have in
> cfg80211 now is good enough and seems to be working well when integrating
> into an actual userspace stack.

OK, sounds good.

> > We just posted a lot of hwsim changes for NAN - better send yours sooner
> > rather than later ;-)
> 
> Yes I have seen, lots of Wi-Fi 8 documents aswell so I assume that will get busy
> soon too. Well see .. Lots to do!!

I don't think hwsim will get too many wifi8 changes, but yeah there are
a lot of things brewing now. Seems you (mostly?) got just ahead of it
though, so it shouldn't be much of an issue, I'd think.

johannes

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox