* [PATCH] wifi: mt76: mt7925: fix NULL pointer dereference in vif iteration loops
@ 2025-12-31 5:29 Zac Bowling
2025-12-31 22:37 ` [PATCH] wifi: mt76: mt7925: fix missing mutex protection in reset and ROC abort paths Zac Bowling
0 siblings, 1 reply; 25+ messages in thread
From: Zac Bowling @ 2025-12-31 5:29 UTC (permalink / raw)
To: linux-wireless
Cc: lorenzo, nbd, ryder.lee, kvalo, sean.wang, deren.wu,
linux-mediatek, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 1219 bytes --]
I was getting a kernel panic on my new Framework Desktop running
Ubuntu 25.10 with this specific WIFI chipset.
mt792x_vif_to_bss_conf() can return NULL when iterating over valid_links
during HW reset or other state transitions, because the link configuration
in mac80211 may not be set up yet even though the driver's valid_links
bitmap has the link marked as valid.
This causes a NULL pointer dereference in mt76_connac_mcu_uni_add_dev()
when it tries to access bss_conf->vif->type, and similar crashes in other
functions that use bss_conf without checking.
The crash manifests as:
BUG: kernel NULL pointer dereference, address: 0000000000000000
RIP: 0010:mt76_connac_mcu_uni_add_dev+0xba/0x1f0 [mt76_connac_lib]
Call Trace:
mt7925_vif_connect_iter+0xcb/0x240 [mt7925_common]
__iterate_interfaces+0x92/0x130 [mac80211]
ieee80211_iterate_interfaces+0x3d/0x60 [mac80211]
mt7925_mac_reset_work+0x105/0x190 [mt7925_common]
Add NULL checks for bss_conf in all loops that iterate over valid_links
and call mt792x_vif_to_bss_conf(), skipping links where the mac80211
link configuration is not yet available.
Reported-by: Zac Bowling <zac@zacbowling.com>
Signed-off-by: Zac Bowling <zac@zacbowling.com>
[-- Attachment #2: 0001-wifi-mt76-mt7925-fix-NULL-pointer-dereference-in-vif.patch --]
[-- Type: application/octet-stream, Size: 3808 bytes --]
From 6790e656030fb23527aa5c0d6eaa28ce029335b1 Mon Sep 17 00:00:00 2001
From: Zac Bowling <zac@zacbowling.com>
Date: Tue, 30 Dec 2025 20:32:56 -0800
Subject: [PATCH] wifi: mt76: mt7925: fix NULL pointer dereference in vif
iteration loops
mt792x_vif_to_bss_conf() can return NULL when iterating over valid_links
during HW reset or other state transitions, because the link configuration
in mac80211 may not be set up yet even though the driver's valid_links
bitmap has the link marked as valid.
This causes a NULL pointer dereference in mt76_connac_mcu_uni_add_dev()
when it tries to access bss_conf->vif->type, and similar crashes in other
functions that use bss_conf without checking.
The crash manifests as:
BUG: kernel NULL pointer dereference, address: 0000000000000000
RIP: 0010:mt76_connac_mcu_uni_add_dev+0xba/0x1f0 [mt76_connac_lib]
Call Trace:
mt7925_vif_connect_iter+0xcb/0x240 [mt7925_common]
__iterate_interfaces+0x92/0x130 [mac80211]
ieee80211_iterate_interfaces+0x3d/0x60 [mac80211]
mt7925_mac_reset_work+0x105/0x190 [mt7925_common]
Add NULL checks for bss_conf in all loops that iterate over valid_links
and call mt792x_vif_to_bss_conf(), skipping links where the mac80211
link configuration is not yet available.
Reported-by: Zac Bowling <zac@zacbowling.com>
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 6 ++++++
drivers/net/wireless/mediatek/mt76/mt7925/main.c | 8 ++++++++
2 files changed, 14 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index 871b67101..184efe8af 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 2d358a966..3001a62a8 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -1304,6 +1304,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);
@@ -1630,6 +1632,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);
}
}
@@ -1861,6 +1865,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);
}
}
@@ -1876,6 +1882,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);
}
}
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: fix missing mutex protection in reset and ROC abort paths
2025-12-31 5:29 [PATCH] wifi: mt76: mt7925: fix NULL pointer dereference in vif iteration loops Zac Bowling
@ 2025-12-31 22:37 ` Zac Bowling
2026-01-01 0:22 ` [PATCH 2/3] wifi: mt76: mt7925: fix missing mutex protection in reset and ROC abort Zac Bowling
2026-01-01 0:23 ` [PATCH 3/3] wifi: mt76: mt7925: fix missing mutex protection in runtime PM and MLO PM Zac Bowling
0 siblings, 2 replies; 25+ messages in thread
From: Zac Bowling @ 2025-12-31 22:37 UTC (permalink / raw)
To: zac
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
From: Zac Bowling <zac@zacbowling.com>
This patch is a follow-up to the NULL pointer dereference fix (commit
6790e656030fb23527aa5c0d6eaa28ce029335b1). While that patch prevented
kernel panics from NULL pointer dereferences, it did not address the
underlying system hangs and deadlocks that occur during firmware
recovery.
The issue manifests on Framework Desktop systems with MT7925 WiFi cards
when:
1. Switching between WiFi networks
2. Disconnecting/reconnecting ethernet while WiFi is active
3. Firmware message timeouts trigger hardware reset recovery
During these operations, MCU message timeouts can occur, triggering
mt792x_reset() which queues reset_work. The reset work and ROC abort
functions iterate over active interfaces and call MCU functions that
require the device mutex to be held, but the mutex was not acquired
before the iteration.
This causes system-wide hangs where:
- Network commands (ip, etc.) hang indefinitely
- Processes get stuck in uninterruptible sleep (D state)
- Tailscale and other network services timeout
- System becomes completely unresponsive requiring force reboot
The hang occurs because:
1. Firmware timeouts trigger hardware reset via mt792x_reset()
2. Reset work (mt7925_mac_reset_work) or ROC abort (mt7925_roc_abort_sync)
tries to iterate interfaces and call MCU functions
3. MCU operations block indefinitely waiting for mutex that's held
elsewhere, or deadlock occurs
4. Network stack becomes unresponsive
Add mutex protection around interface iteration in both:
- mt7925_mac_reset_work(): Called during firmware recovery after MCU
timeouts to reconnect all interfaces
- mt7925_roc_abort_sync(): Called during suspend/resume and when aborting
Remain On Channel operations
This matches the pattern used elsewhere in the driver (e.g., in
mt7925_roc_iter, mt7925_mcu_set_suspend_iter, etc.) where interface
iteration callbacks invoke MCU functions.
Note: The author does not have deep familiarity with this codebase, but
this fix has been tested and appears to resolve the panic and deadlock
issues observed on Framework Desktop hardware with MT7925 WiFi cards.
Reported-by: Zac Bowling <zac@zacbowling.com>
Tested-by: Zac Bowling <zac@zacbowling.com>
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 2 ++
drivers/net/wireless/mediatek/mt76/mt7925/main.c | 5 ++++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index 184efe8afa10..06420ac6ed55 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
@@ -1331,9 +1331,11 @@ void mt7925_mac_reset_work(struct work_struct *work)
dev->hw_full_reset = false;
pm->suspended = false;
ieee80211_wake_queues(hw);
+ mt792x_mutex_acquire(dev);
ieee80211_iterate_active_interfaces(hw,
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7925_vif_connect_iter, NULL);
+ mt792x_mutex_release(dev);
mt76_connac_power_save_sched(&dev->mt76.phy, pm);
mt7925_regd_change(&dev->phy, "00");
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 3001a62a8b67..1f7661175623 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -459,10 +459,13 @@ void mt7925_roc_abort_sync(struct mt792x_dev *dev)
timer_delete_sync(&phy->roc_timer);
cancel_work_sync(&phy->roc_work);
- if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
+ if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state)) {
+ mt792x_mutex_acquire(dev);
ieee80211_iterate_interfaces(mt76_hw(dev),
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7925_roc_iter, (void *)phy);
+ mt792x_mutex_release(dev);
+ }
}
EXPORT_SYMBOL_GPL(mt7925_roc_abort_sync);
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 2/3] wifi: mt76: mt7925: fix missing mutex protection in reset and ROC abort
2025-12-31 22:37 ` [PATCH] wifi: mt76: mt7925: fix missing mutex protection in reset and ROC abort paths Zac Bowling
@ 2026-01-01 0:22 ` Zac Bowling
2026-01-01 0:23 ` [PATCH 3/3] wifi: mt76: mt7925: fix missing mutex protection in runtime PM and MLO PM Zac Bowling
1 sibling, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-01 0:22 UTC (permalink / raw)
To: zbowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang, zac
From: Zac Bowling <zac@zacbowling.com>
During firmware recovery and ROC (Remain On Channel) abort operations,
the driver iterates over active interfaces and calls MCU functions that
require the device mutex to be held, but the mutex was not acquired.
This causes system-wide hangs where network commands hang indefinitely,
processes get stuck in uninterruptible sleep (D state), and the system
becomes completely unresponsive requiring force reboot.
Add mutex protection around interface iteration in:
- mt7925_mac_reset_work(): Called during firmware recovery after MCU
timeouts to reconnect all interfaces
- mt7925_roc_abort_sync(): Called during suspend/resume and when aborting
Remain On Channel operations
This matches the pattern used elsewhere in the driver where interface
iteration callbacks invoke MCU functions.
Reported-by: Zac Bowling <zac@zacbowling.com>
Tested-by: Zac Bowling <zac@zacbowling.com>
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 2 ++
drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 2 ++
2 files changed, 4 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index 184efe8afa10..06420ac6ed55 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
@@ -1331,9 +1331,11 @@ void mt7925_mac_reset_work(struct work_struct *work)
dev->hw_full_reset = false;
pm->suspended = false;
ieee80211_wake_queues(hw);
+ mt792x_mutex_acquire(dev);
ieee80211_iterate_active_interfaces(hw,
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7925_vif_connect_iter, NULL);
+ mt792x_mutex_release(dev);
mt76_connac_power_save_sched(&dev->mt76.phy, pm);
mt7925_regd_change(&dev->phy, "00");
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
index c4161754c01d..e9d62c6aee91 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
@@ -455,7 +455,9 @@ static int mt7925_pci_suspend(struct device *device)
cancel_delayed_work_sync(&pm->ps_work);
cancel_work_sync(&pm->wake_work);
+ mt792x_mutex_acquire(dev);
mt7925_roc_abort_sync(dev);
+ mt792x_mutex_release(dev);
err = mt792x_mcu_drv_pmctrl(dev);
if (err < 0)
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 3/3] wifi: mt76: mt7925: fix missing mutex protection in runtime PM and MLO PM
2025-12-31 22:37 ` [PATCH] wifi: mt76: mt7925: fix missing mutex protection in reset and ROC abort paths Zac Bowling
2026-01-01 0:22 ` [PATCH 2/3] wifi: mt76: mt7925: fix missing mutex protection in reset and ROC abort Zac Bowling
@ 2026-01-01 0:23 ` Zac Bowling
2026-01-01 0:41 ` Zac Bowling
1 sibling, 1 reply; 25+ messages in thread
From: Zac Bowling @ 2026-01-01 0:23 UTC (permalink / raw)
To: zbowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang, zac
From: Zac Bowling <zac@zacbowling.com>
Two additional code paths were identified that iterate over active
interfaces and call MCU functions without proper mutex protection:
1. mt7925_set_runtime_pm(): Called when runtime PM settings change.
The callback mt7925_pm_interface_iter() calls mt7925_mcu_set_beacon_filter()
which in turn calls mt7925_mcu_set_rxfilter(). These MCU functions require
the device mutex to be held.
2. mt7925_mlo_pm_work(): A workqueue function for MLO power management.
The callback mt7925_mlo_pm_iter() was acquiring mutex internally, which
is inconsistent with the rest of the driver where the caller holds the
mutex during interface iteration. Move the mutex to the caller for
consistency and to prevent potential race conditions.
The impact of these bugs:
- mt7925_set_runtime_pm(): Can cause deadlocks when power management
settings are changed while WiFi is active
- mt7925_mlo_pm_work(): Can cause race conditions during MLO power save
state transitions
Note: Similar bugs exist in the mt7921 driver and should be fixed in a
separate patch series.
Reported-by: Zac Bowling <zac@zacbowling.com>
Tested-by: Zac Bowling <zac@zacbowling.com>
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/main.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 3001a62a8b67..9f17b21aef1c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -751,9 +751,11 @@ void mt7925_set_runtime_pm(struct mt792x_dev *dev)
bool monitor = !!(hw->conf.flags & IEEE80211_CONF_MONITOR);
pm->enable = pm->enable_user && !monitor;
+ mt792x_mutex_acquire(dev);
ieee80211_iterate_active_interfaces(hw,
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7925_pm_interface_iter, dev);
+ mt792x_mutex_release(dev);
pm->ds_enable = pm->ds_enable_user && !monitor;
mt7925_mcu_set_deep_sleep(dev, pm->ds_enable);
}
@@ -1301,14 +1303,12 @@ mt7925_mlo_pm_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
if (mvif->mlo_pm_state != MT792x_MLO_CHANGED_PS)
return;
- 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);
}
void mt7925_mlo_pm_work(struct work_struct *work)
@@ -1317,9 +1317,11 @@ void mt7925_mlo_pm_work(struct work_struct *work)
mlo_pm_work.work);
struct ieee80211_hw *hw = mt76_hw(dev);
+ mt792x_mutex_acquire(dev);
ieee80211_iterate_active_interfaces(hw,
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7925_mlo_pm_iter, dev);
+ mt792x_mutex_release(dev);
}
void mt7925_scan_work(struct work_struct *work)
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [PATCH 3/3] wifi: mt76: mt7925: fix missing mutex protection in runtime PM and MLO PM
2026-01-01 0:23 ` [PATCH 3/3] wifi: mt76: mt7925: fix missing mutex protection in runtime PM and MLO PM Zac Bowling
@ 2026-01-01 0:41 ` Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add NULL checks in MCU STA TLV functions Zac Bowling
` (3 more replies)
0 siblings, 4 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-01 0:41 UTC (permalink / raw)
To: zbowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
Note, this is an update to the original patch I sent.
In this v2 patch, I moved mutex protection from inside
mt7925_roc_abort_sync() to the
call site in pci.c. The previous approach caused a self-deadlock when
roc_abort_sync was
called from the station remove path, which already holds the mutex.
I created a repo with all these patches if that makes it easier:
https://github.com/zbowling/mt7925
These bugs also exist in the mt7921 driver, which this mt7925 driver
seems to fork from.
These lock patterns match the much older mt7615 driver and other wifi drivers.
Zac Bowling
Zac Bowling
On Wed, Dec 31, 2025 at 4:23 PM Zac Bowling <zbowling@gmail.com> wrote:
>
> From: Zac Bowling <zac@zacbowling.com>
>
> Two additional code paths were identified that iterate over active
> interfaces and call MCU functions without proper mutex protection:
>
> 1. mt7925_set_runtime_pm(): Called when runtime PM settings change.
> The callback mt7925_pm_interface_iter() calls mt7925_mcu_set_beacon_filter()
> which in turn calls mt7925_mcu_set_rxfilter(). These MCU functions require
> the device mutex to be held.
>
> 2. mt7925_mlo_pm_work(): A workqueue function for MLO power management.
> The callback mt7925_mlo_pm_iter() was acquiring mutex internally, which
> is inconsistent with the rest of the driver where the caller holds the
> mutex during interface iteration. Move the mutex to the caller for
> consistency and to prevent potential race conditions.
>
> The impact of these bugs:
> - mt7925_set_runtime_pm(): Can cause deadlocks when power management
> settings are changed while WiFi is active
> - mt7925_mlo_pm_work(): Can cause race conditions during MLO power save
> state transitions
>
> Note: Similar bugs exist in the mt7921 driver and should be fixed in a
> separate patch series.
>
> Reported-by: Zac Bowling <zac@zacbowling.com>
> Tested-by: Zac Bowling <zac@zacbowling.com>
> Signed-off-by: Zac Bowling <zac@zacbowling.com>
> ---
> drivers/net/wireless/mediatek/mt76/mt7925/main.c | 6 ++++--
> 1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> index 3001a62a8b67..9f17b21aef1c 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> @@ -751,9 +751,11 @@ void mt7925_set_runtime_pm(struct mt792x_dev *dev)
> bool monitor = !!(hw->conf.flags & IEEE80211_CONF_MONITOR);
>
> pm->enable = pm->enable_user && !monitor;
> + mt792x_mutex_acquire(dev);
> ieee80211_iterate_active_interfaces(hw,
> IEEE80211_IFACE_ITER_RESUME_ALL,
> mt7925_pm_interface_iter, dev);
> + mt792x_mutex_release(dev);
> pm->ds_enable = pm->ds_enable_user && !monitor;
> mt7925_mcu_set_deep_sleep(dev, pm->ds_enable);
> }
> @@ -1301,14 +1303,12 @@ mt7925_mlo_pm_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
> if (mvif->mlo_pm_state != MT792x_MLO_CHANGED_PS)
> return;
>
> - 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);
> }
>
> void mt7925_mlo_pm_work(struct work_struct *work)
> @@ -1317,9 +1317,11 @@ void mt7925_mlo_pm_work(struct work_struct *work)
> mlo_pm_work.work);
> struct ieee80211_hw *hw = mt76_hw(dev);
>
> + mt792x_mutex_acquire(dev);
> ieee80211_iterate_active_interfaces(hw,
> IEEE80211_IFACE_ITER_RESUME_ALL,
> mt7925_mlo_pm_iter, dev);
> + mt792x_mutex_release(dev);
> }
>
> void mt7925_scan_work(struct work_struct *work)
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: add NULL checks in MCU STA TLV functions
2026-01-01 0:41 ` Zac Bowling
@ 2026-01-01 6:25 ` Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add NULL checks for link_conf and mlink in main.c Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add NULL checks in MLO link and chanctx functions Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add error handling for AMPDU MCU commands Zac Bowling
` (2 subsequent siblings)
3 siblings, 2 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-01 6:25 UTC (permalink / raw)
To: linux-wireless
Cc: linux-mediatek, linux-kernel, kvalo, lorenzo, nbd, sean.wang,
deren.wu, ryder.lee
From: Zac Bowling <zac@zacbowling.com>
Add NULL pointer checks for link_conf and mconf in:
- mt7925_mcu_sta_phy_tlv(): builds PHY capability TLV for station record
- mt7925_mcu_sta_rate_ctrl_tlv(): builds rate control TLV for station record
Both functions call mt792x_vif_to_bss_conf() and mt792x_vif_to_link()
which can return NULL during MLO link state transitions when the link
configuration in mac80211 is not yet synchronized with the driver's
link tracking.
Without these checks, the driver will crash with a NULL pointer
dereference when accessing link_conf->chanreq.oper or link_conf->basic_rates.
Reported-by: Zac Bowling <zac@zacbowling.com>
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index cf0fdea45cf7..d61a7fbda745 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -1773,6 +1773,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 +1855,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;
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: add NULL checks for link_conf and mlink in main.c
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add NULL checks in MCU STA TLV functions Zac Bowling
@ 2026-01-01 6:25 ` Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add NULL checks in MLO link and chanctx functions Zac Bowling
1 sibling, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-01 6:25 UTC (permalink / raw)
To: linux-wireless
Cc: linux-mediatek, linux-kernel, kvalo, lorenzo, nbd, sean.wang,
deren.wu, ryder.lee
From: Zac Bowling <zac@zacbowling.com>
Add NULL pointer checks throughout main.c for functions that call
mt792x_vif_to_bss_conf(), mt792x_vif_to_link(), and mt792x_sta_to_link()
without verifying the return value before dereferencing.
Functions fixed:
- mt7925_set_key(): Check link_conf, mconf, and mlink before use
- mt7925_mac_link_sta_add(): Check link_conf before BSS info update
- mt7925_mac_link_sta_assoc(): Check mlink and link_conf before use
- mt7925_mac_link_sta_remove(): Check mlink and link_conf, add goto
label for proper cleanup path
- mt7925_change_vif_links(): Check link_conf before adding BSS
These functions can receive NULL when the link configuration in mac80211
is not yet synchronized with the driver's link tracking during MLO
operations or state transitions.
Without these checks, the driver will crash with NULL pointer
dereferences during station add/remove/association operations.
Reported-by: Zac Bowling <zac@zacbowling.com>
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
.../net/wireless/mediatek/mt76/mt7925/main.c | 27 ++++++++++++++++---
1 file changed, 23 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 9f17b21aef1c..7d3322461bcf 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -604,6 +604,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;
@@ -889,6 +893,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) {
@@ -1034,6 +1040,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);
@@ -1043,12 +1051,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);
@@ -1095,6 +1104,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);
@@ -1108,10 +1119,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);
@@ -1119,6 +1132,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))
@@ -2031,6 +2045,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);
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: add NULL checks in MLO link and chanctx functions
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add NULL checks in MCU STA TLV functions Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add NULL checks for link_conf and mlink in main.c Zac Bowling
@ 2026-01-01 6:25 ` Zac Bowling
1 sibling, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-01 6:25 UTC (permalink / raw)
To: linux-wireless
Cc: linux-mediatek, linux-kernel, kvalo, lorenzo, nbd, sean.wang,
deren.wu, ryder.lee
From: Zac Bowling <zac@zacbowling.com>
Add NULL pointer checks for mconf and link_conf in several functions
that were missing validation after calling mt792x_vif_to_link() and
mt792x_vif_to_bss_conf().
Functions fixed:
- mt7925_mac_set_links(): Check both primary and secondary link_conf
before dereferencing chanreq.oper for band selection
- mt7925_link_info_changed(): Check mconf before using it to get
link_conf, prevents NULL dereference chain
- mt7925_assign_vif_chanctx(): Check mconf before use, return -EINVAL
if NULL; check pri_link_conf before passing to MCU function
- mt7925_unassign_vif_chanctx(): Check mconf before dereferencing,
return early if NULL during MLO cleanup
These functions handle MLO (Multi-Link Operation) scenarios where link
configurations may not be fully set up when called, particularly during
rapid link state transitions or error recovery paths.
Reported-by: Zac Bowling <zac@zacbowling.com>
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
.../net/wireless/mediatek/mt76/mt7925/main.c | 39 +++++++++++++++----
1 file changed, 32 insertions(+), 7 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 058394b2e067..852cf8ff842f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -1006,18 +1006,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;
+
+ link_conf = mt792x_vif_to_bss_conf(vif, mvif->deflink_id);
+ if (!link_conf)
+ return;
- u16 sel_links = mt76_select_links(vif, 2);
- u8 secondary_link_id = __ffs(~BIT(mvif->deflink_id) & sel_links);
+ 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 ||
@@ -1927,7 +1938,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);
@@ -2136,9 +2152,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);
@@ -2167,6 +2188,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)
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: add error handling for AMPDU MCU commands
2026-01-01 0:41 ` Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add NULL checks in MCU STA TLV functions Zac Bowling
@ 2026-01-01 6:25 ` Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add error handling for BSS info MCU command in sta_add Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add error handling for BSS info in key setup Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7921: fix missing mutex protection in multiple paths Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add lockdep assertions for mutex verification Zac Bowling
3 siblings, 2 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-01 6:25 UTC (permalink / raw)
To: linux-wireless
Cc: linux-mediatek, linux-kernel, kvalo, lorenzo, nbd, sean.wang,
deren.wu, ryder.lee
From: Zac Bowling <zac@zacbowling.com>
Check return values of mt7925_mcu_uni_rx_ba() and mt7925_mcu_uni_tx_ba()
in mt7925_ampdu_action() and propagate errors to the caller.
Previously, failures in these MCU commands were silently ignored, which
could leave block aggregation in an inconsistent state between the driver
and firmware.
For IEEE80211_AMPDU_TX_STOP_CONT, only call the completion callback
ieee80211_stop_tx_ba_cb_irqsafe() if the MCU command succeeded, to avoid
signaling completion when the firmware operation failed.
Reported-by: Zac Bowling <zac@zacbowling.com>
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/main.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 7d3322461bcf..d966e5ab50ff 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -1271,22 +1271,22 @@ mt7925_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
case IEEE80211_AMPDU_RX_START:
mt76_rx_aggr_start(&dev->mt76, &msta->deflink.wcid, tid, ssn,
params->buf_size);
- mt7925_mcu_uni_rx_ba(dev, params, true);
+ ret = mt7925_mcu_uni_rx_ba(dev, params, true);
break;
case IEEE80211_AMPDU_RX_STOP:
mt76_rx_aggr_stop(&dev->mt76, &msta->deflink.wcid, tid);
- mt7925_mcu_uni_rx_ba(dev, params, false);
+ ret = mt7925_mcu_uni_rx_ba(dev, params, false);
break;
case IEEE80211_AMPDU_TX_OPERATIONAL:
mtxq->aggr = true;
mtxq->send_bar = false;
- mt7925_mcu_uni_tx_ba(dev, params, true);
+ ret = mt7925_mcu_uni_tx_ba(dev, params, true);
break;
case IEEE80211_AMPDU_TX_STOP_FLUSH:
case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
mtxq->aggr = false;
clear_bit(tid, &msta->deflink.wcid.ampdu_state);
- mt7925_mcu_uni_tx_ba(dev, params, false);
+ ret = mt7925_mcu_uni_tx_ba(dev, params, false);
break;
case IEEE80211_AMPDU_TX_START:
set_bit(tid, &msta->deflink.wcid.ampdu_state);
@@ -1295,8 +1295,9 @@ mt7925_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
case IEEE80211_AMPDU_TX_STOP_CONT:
mtxq->aggr = false;
clear_bit(tid, &msta->deflink.wcid.ampdu_state);
- mt7925_mcu_uni_tx_ba(dev, params, false);
- ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
+ ret = mt7925_mcu_uni_tx_ba(dev, params, false);
+ if (!ret)
+ ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
break;
}
mt792x_mutex_release(dev);
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: add error handling for BSS info MCU command in sta_add
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add error handling for AMPDU MCU commands Zac Bowling
@ 2026-01-01 6:25 ` Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add error handling for BSS info in key setup Zac Bowling
1 sibling, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-01 6:25 UTC (permalink / raw)
To: linux-wireless
Cc: linux-mediatek, linux-kernel, kvalo, lorenzo, nbd, sean.wang,
deren.wu, ryder.lee
From: Zac Bowling <zac@zacbowling.com>
Check return value of mt7925_mcu_add_bss_info() in mt7925_mac_link_sta_add()
and propagate errors to the caller.
BSS info must be set up before adding a station record. If this MCU
command fails, continuing with station add would leave the firmware in
an inconsistent state with a station but no BSS configuration.
Reported-by: Zac Bowling <zac@zacbowling.com>
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/main.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index d966e5ab50ff..a7e1e673c4bc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -899,11 +899,14 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
/* should update bss info before STA add */
if (vif->type == NL80211_IFTYPE_STATION && !link_sta->sta->tdls) {
if (ieee80211_vif_is_mld(vif))
- mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
- link_conf, link_sta, link_sta != mlink->pri_link);
+ ret = mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
+ link_conf, link_sta,
+ link_sta != mlink->pri_link);
else
- mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
- link_conf, link_sta, false);
+ ret = mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
+ link_conf, link_sta, false);
+ if (ret)
+ return ret;
}
if (ieee80211_vif_is_mld(vif) &&
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: add error handling for BSS info in key setup
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add error handling for AMPDU MCU commands Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add error handling for BSS info MCU command in sta_add Zac Bowling
@ 2026-01-01 6:25 ` Zac Bowling
1 sibling, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-01 6:25 UTC (permalink / raw)
To: linux-wireless
Cc: linux-mediatek, linux-kernel, kvalo, lorenzo, nbd, sean.wang,
deren.wu, ryder.lee
From: Zac Bowling <zac@zacbowling.com>
Check return value of mt7925_mcu_add_bss_info() in mt7925_set_key_link()
when setting up cipher for the first time and propagate errors.
The BSS info update with cipher information must succeed before key
programming can proceed. If this MCU command fails, continuing with
key setup would program keys into the firmware for a BSS that doesn't
have the correct cipher configuration.
Reported-by: Zac Bowling <zac@zacbowling.com>
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/main.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index a7e1e673c4bc..058394b2e067 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -637,8 +637,10 @@ static int mt7925_set_link_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
struct mt792x_phy *phy = mt792x_hw_phy(hw);
mconf->mt76.cipher = mt7925_mcu_get_cipher(key->cipher);
- mt7925_mcu_add_bss_info(phy, mconf->mt76.ctx, link_conf,
- link_sta, true);
+ err = mt7925_mcu_add_bss_info(phy, mconf->mt76.ctx, link_conf,
+ link_sta, true);
+ if (err)
+ goto out;
}
if (cmd == SET_KEY)
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7921: fix missing mutex protection in multiple paths
2026-01-01 0:41 ` Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add NULL checks in MCU STA TLV functions Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add error handling for AMPDU MCU commands Zac Bowling
@ 2026-01-01 6:25 ` Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add lockdep assertions for mutex verification Zac Bowling
3 siblings, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-01 6:25 UTC (permalink / raw)
To: linux-wireless
Cc: linux-mediatek, linux-kernel, kvalo, lorenzo, nbd, sean.wang,
deren.wu, ryder.lee
From: Zac Bowling <zac@zacbowling.com>
The MT7921 driver has the same mutex protection bugs as MT7925 - they were
inherited when MT7925 was forked from MT7921. Several code paths iterate
over active interfaces and call MCU functions without proper mutex protection.
Add mutex protection in the following locations:
1. mt7921_set_runtime_pm() in main.c:
Called when runtime PM settings change. The callback
mt7921_pm_interface_iter() calls MCU functions that require
the device mutex to be held.
2. mt7921_regd_set_6ghz_power_type() in main.c:
Called during VIF add/remove for 6GHz power type determination.
Uses ieee80211_iterate_active_interfaces() without mutex.
3. mt7921_mac_reset_work() in mac.c:
After firmware recovery, iterates interfaces to reconnect them.
The mt7921_vif_connect_iter() callback calls MCU functions.
4. PCI/SDIO suspend paths (pci.c, sdio.c):
The mt7921_roc_abort_sync() call iterates interfaces without
mutex protection.
These bugs can cause system hangs during:
- Power management state transitions
- WiFi reset/recovery
- Suspend/resume cycles
- 6GHz regulatory power type changes
The fix follows the same pattern used in the MT7925 patches.
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 | 4 ++++
drivers/net/wireless/mediatek/mt76/mt7921/pci.c | 2 ++
drivers/net/wireless/mediatek/mt76/mt7921/sdio.c | 2 ++
4 files changed, 10 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..05793a786644 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -619,9 +619,11 @@ void mt7921_set_runtime_pm(struct mt792x_dev *dev)
bool monitor = !!(hw->conf.flags & IEEE80211_CONF_MONITOR);
pm->enable = pm->enable_user && !monitor;
+ mt792x_mutex_acquire(dev);
ieee80211_iterate_active_interfaces(hw,
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7921_pm_interface_iter, dev);
+ mt792x_mutex_release(dev);
pm->ds_enable = pm->ds_enable_user && !monitor;
mt76_connac_mcu_set_deep_sleep(&dev->mt76, pm->ds_enable);
}
@@ -765,9 +767,11 @@ mt7921_regd_set_6ghz_power_type(struct ieee80211_vif *vif, bool is_add)
struct mt792x_dev *dev = phy->dev;
u32 valid_vif_num = 0;
+ mt792x_mutex_acquire(dev);
ieee80211_iterate_active_interfaces(mt76_hw(dev),
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7921_calc_vif_num, &valid_vif_num);
+ mt792x_mutex_release(dev);
if (valid_vif_num > 1) {
phy->power_type = MT_AP_DEFAULT;
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.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: add lockdep assertions for mutex verification
2026-01-01 0:41 ` Zac Bowling
` (2 preceding siblings ...)
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7921: fix missing mutex protection in multiple paths Zac Bowling
@ 2026-01-01 6:25 ` Zac Bowling
2026-01-02 20:03 ` [PATCH v2 0/6] wifi: mt76: mt7925/mt792x: additional stability fixes Zac Bowling
3 siblings, 1 reply; 25+ messages in thread
From: Zac Bowling @ 2026-01-01 6:25 UTC (permalink / raw)
To: linux-wireless
Cc: linux-mediatek, linux-kernel, kvalo, lorenzo, nbd, sean.wang,
deren.wu, ryder.lee
From: Zac Bowling <zac@zacbowling.com>
Add lockdep_assert_held() calls to critical MCU functions to help catch
mutex violations during development and debugging. This follows the
pattern used in other mt76 drivers (mt7996, mt7915, mt7615).
Functions with new assertions:
- mt7925_mcu_add_bss_info(): Core BSS configuration MCU command
- mt7925_mcu_sta_update(): Station record update MCU command
- mt7925_mcu_uni_bss_ps(): Power save state MCU command
These functions modify firmware state and must be called with the
device mutex held to prevent race conditions.
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index d61a7fbda745..958ff9da9f01 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -1527,6 +1527,8 @@ int mt7925_mcu_uni_bss_ps(struct mt792x_dev *dev,
},
};
+ lockdep_assert_held(&dev->mt76.mutex);
+
if (link_conf->vif->type != NL80211_IFTYPE_STATION)
return -EOPNOTSUPP;
@@ -2037,6 +2039,8 @@ int mt7925_mcu_sta_update(struct mt792x_dev *dev,
struct mt792x_sta *msta;
struct mt792x_link_sta *mlink;
+ lockdep_assert_held(&dev->mt76.mutex);
+
if (link_sta) {
msta = (struct mt792x_sta *)link_sta->sta->drv_priv;
mlink = mt792x_sta_to_link(msta, link_sta->link_id);
@@ -2843,6 +2847,8 @@ int mt7925_mcu_add_bss_info(struct mt792x_phy *phy,
struct mt792x_link_sta *mlink_bc;
struct sk_buff *skb;
+ lockdep_assert_held(&dev->mt76.mutex);
+
skb = __mt7925_mcu_alloc_bss_req(&dev->mt76, &mconf->mt76,
MT7925_BSS_UPDATE_MAX_SIZE);
if (IS_ERR(skb))
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 0/6] wifi: mt76: mt7925/mt792x: additional stability fixes
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add lockdep assertions for mutex verification Zac Bowling
@ 2026-01-02 20:03 ` Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: fix key removal failure during MLO roaming Zac Bowling
` (6 more replies)
0 siblings, 7 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-02 20:03 UTC (permalink / raw)
To: zbowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
From: Zac Bowling <zac@zacbowling.com>
This series contains additional fixes for the MT7925 WiFi driver that
address issues discovered through further testing and static analysis.
These patches build on my previous series [1] and address the remaining
stability issues I've encountered on the Framework Desktop.
Changes since v1:
- 6 new patches addressing MLO roaming, firmware recovery, and resume path
Summary of fixes:
Patch 12: Fix key removal failure during MLO roaming
During MLO link teardown, mac80211 may request key removal after
driver state is already cleaned up. Return success instead of -EINVAL
when the link is already gone, as the key is effectively removed.
Patch 13: Fix kernel warning in MLO ROC setup
Replace WARN_ON_ONCE() with proper NULL checks in mt7925_mcu_set_mlo_roc().
During MLO AP setup, the channel may not be configured yet when this
function is called. Return -ENOLINK instead of triggering a warning.
Patch 14: Add NULL checks for MLO link pointers in MCU functions
Several MCU functions dereference mt792x_sta_to_link() and
mt792x_vif_to_link() without checking for NULL. Add defensive checks
in sta_hdr_trans_tlv, wtbl_update_hdr_trans, sta_amsdu_tlv,
sta_mld_tlv, and sta_update.
Patch 15: Fix firmware reload after previous load crash (mt792x)
Backport the MT7915 fix (commit 79dd14f) to MT792x. If firmware loading
crashes after acquiring the patch semaphore, subsequent loads fail with
"Failed to get patch semaphore". Release the semaphore and restart MCU
before loading to ensure clean state.
Patch 16: Add mutex protection in resume path
The resume path was missing mutex protection around mt7925_mcu_set_deep_sleep()
and mt7925_regd_update() calls. Found by static analysis (sparse/coccinelle).
Patch 17: Add NULL checks and error handling
Add NULL checks in mt7925_mac_link_sta_add() and mt7925_conf_tx().
Add error logging for MCU calls in mt7925_regd_update() to help
diagnose regulatory domain update failures.
These fixes have been tested on a Framework Desktop (AMD Ryzen AI Max 300)
with the MT7925 (RZ717) WiFi card. The system is now stable through
suspend/resume cycles, MLO roaming, and firmware recovery scenarios that
previously caused crashes or hangs.
[1] https://lore.kernel.org/all/20260101062543.186499-1-zbowling@gmail.com/
Zac Bowling (6):
wifi: mt76: mt7925: fix key removal failure during MLO roaming
wifi: mt76: mt7925: fix kernel warning in MLO ROC setup
wifi: mt76: mt7925: add NULL checks for MLO link pointers in MCU
wifi: mt76: mt792x: fix firmware reload after failed load
wifi: mt76: mt7925: add mutex protection in resume path
wifi: mt76: mt7925: add NULL checks and error handling
drivers/net/wireless/mediatek/mt76/mt7925/init.c | 13 ++-
drivers/net/wireless/mediatek/mt76/mt7925/main.c | 19 +++-
drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 45 +++++---
drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 2 +
drivers/net/wireless/mediatek/mt76/mt792x_core.c | 14 +++
5 files changed, 75 insertions(+), 18 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: fix key removal failure during MLO roaming
2026-01-02 20:03 ` [PATCH v2 0/6] wifi: mt76: mt7925/mt792x: additional stability fixes Zac Bowling
@ 2026-01-02 20:03 ` Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: fix kernel warning in MLO ROC setup when channel not configured Zac Bowling
` (5 subsequent siblings)
6 siblings, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-02 20:03 UTC (permalink / raw)
To: zbowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
From: Zac Bowling <zac@zacbowling.com>
During MLO roaming, mac80211 may request key removal after the link state
has already been torn down. The current code returns -EINVAL when
link_conf, mconf, or mlink is NULL, causing 'failed to remove key from
hardware (-22)' errors in the kernel log.
This is a race condition where:
1. MLO link teardown begins, cleaning up driver state
2. mac80211 requests group key removal for the old link
3. mt792x_vif_to_bss_conf() or related functions return NULL
4. Driver returns -EINVAL, confusing upper layers
The fix: When removing a key (cmd != SET_KEY), if the link state is
already gone, return success (0) instead of error. The key is effectively
removed when the link was torn down.
This prevents the following errors during roaming:
wlp192s0: failed to remove key (1, ff:ff:ff:ff:ff:ff) from hardware (-22)
wlp192s0: failed to remove key (4, ff:ff:ff:ff:ff:ff) from hardware (-22)
And the associated wpa_supplicant warnings:
nl80211: kernel reports: link ID must for MLO group key
Reported-by: Zac Bowling <zac@zacbowling.com>
Tested-by: Zac Bowling <zac@zacbowling.com>
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/main.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 13156333431d..11c0197c7426 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -597,8 +597,15 @@ static int mt7925_set_link_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
mconf = mt792x_vif_to_link(mvif, link_id);
mlink = mt792x_sta_to_link(msta, link_id);
- if (!link_conf || !mconf || !mlink)
+ if (!link_conf || !mconf || !mlink) {
+ /* During MLO roaming, link state may be torn down before
+ * mac80211 requests key removal. If removing a key and
+ * the link is already gone, consider it successfully removed.
+ */
+ if (cmd != SET_KEY)
+ return 0;
return -EINVAL;
+ }
wcid = &mlink->wcid;
wcid_keyidx = &wcid->hw_key_idx;
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: fix kernel warning in MLO ROC setup when channel not configured
2026-01-02 20:03 ` [PATCH v2 0/6] wifi: mt76: mt7925/mt792x: additional stability fixes Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: fix key removal failure during MLO roaming Zac Bowling
@ 2026-01-02 20:03 ` Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: add NULL checks for MLO link pointers in MCU functions Zac Bowling
` (4 subsequent siblings)
6 siblings, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-02 20:03 UTC (permalink / raw)
To: zbowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
mt7925_mcu_set_mlo_roc() uses WARN_ON_ONCE() to check if link_conf or
channel is NULL. However, during MLO AP setup, it's normal for the
channel to not be configured yet when this function is called. The
WARN_ON_ONCE triggers a kernel warning/oops that makes the system
appear to have crashed, even though it's just a timing issue.
Replace WARN_ON_ONCE with regular NULL checks and return -ENOLINK to
indicate the link isn't fully configured yet. This allows the upper
layers to retry when the link is ready, without spamming the kernel
log with warnings.
Also add a check for mconf in the first loop to match the pattern
used in the second loop, preventing potential NULL dereference.
This fixes kernel oops reported during MLO AP setup on OpenWrt with
MT7925E hardware.
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
mt7925/mcu.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/mt7925/mcu.c b/mt7925/mcu.c
index bd38807e..b0bbeb5a 100644
--- a/mt7925/mcu.c
+++ b/mt7925/mcu.c
@@ -1337,15 +1337,23 @@ int mt7925_mcu_set_mlo_roc(struct mt792x_bss_conf *mconf, u16 sel_links,
for (i = 0; i < ARRAY_SIZE(links); i++) {
links[i].id = i ? __ffs(~BIT(mconf->link_id) & sel_links) :
mconf->link_id;
+
link_conf = mt792x_vif_to_bss_conf(vif, links[i].id);
- if (WARN_ON_ONCE(!link_conf))
- return -EPERM;
+ if (!link_conf)
+ return -ENOLINK;
links[i].chan = link_conf->chanreq.oper.chan;
- if (WARN_ON_ONCE(!links[i].chan))
- return -EPERM;
+ if (!links[i].chan)
+ /* Channel not configured yet - this can happen during
+ * MLO AP setup when links are being added sequentially.
+ * Return -ENOLINK to indicate link not ready.
+ */
+ return -ENOLINK;
links[i].mconf = mt792x_vif_to_link(mvif, links[i].id);
+ if (!links[i].mconf)
+ return -ENOLINK;
+
links[i].tag = links[i].id == mconf->link_id ?
UNI_ROC_ACQUIRE : UNI_ROC_SUB_LINK;
@@ -1359,8 +1367,8 @@ int mt7925_mcu_set_mlo_roc(struct mt792x_bss_conf *mconf, u16 sel_links,
type = MT7925_ROC_REQ_JOIN;
for (i = 0; i < ARRAY_SIZE(links) && i < hweight16(vif->active_links); i++) {
- if (WARN_ON_ONCE(!links[i].mconf || !links[i].chan))
- continue;
+ if (!links[i].mconf || !links[i].chan)
+ return -ENOLINK;
chan = links[i].chan;
center_ch = ieee80211_frequency_to_channel(chan->center_freq);
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: add NULL checks for MLO link pointers in MCU functions
2026-01-02 20:03 ` [PATCH v2 0/6] wifi: mt76: mt7925/mt792x: additional stability fixes Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: fix key removal failure during MLO roaming Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: fix kernel warning in MLO ROC setup when channel not configured Zac Bowling
@ 2026-01-02 20:03 ` Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt792x: fix firmware reload failure after previous load crash Zac Bowling
` (3 subsequent siblings)
6 siblings, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-02 20:03 UTC (permalink / raw)
To: zbowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
Several MCU functions dereference pointers returned by mt792x_sta_to_link()
and mt792x_vif_to_link() without checking for NULL. During MLO state
transitions, these functions can return NULL when link state is being
set up or torn down, causing kernel NULL pointer dereferences.
Add NULL checks in the following functions:
- mt7925_mcu_sta_hdr_trans_tlv(): Check mlink before dereferencing wcid
- mt7925_mcu_wtbl_update_hdr_trans(): Check mlink and mconf before use
- mt7925_mcu_sta_amsdu_tlv(): Check mlink before setting amsdu flag
- mt7925_mcu_sta_mld_tlv(): Check mconf and mlink in link iteration loop
- mt7925_mcu_sta_update(): Initialize mlink to NULL and check both
link_sta and mlink in the ternary condition
These race conditions can occur during:
- MLO link setup/teardown
- Station add/remove operations
- Firmware command generation during state transitions
The fixes follow the pattern used in mt7996 and ath12k drivers for
similar MLO link state handling.
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
mt7925/mcu.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/mt7925/mcu.c b/mt7925/mcu.c
index bd38807e..b9c4b99d 100644
--- a/mt7925/mcu.c
+++ b/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) {
@@ -1935,6 +1942,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 +2037,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.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt792x: fix firmware reload failure after previous load crash
2026-01-02 20:03 ` [PATCH v2 0/6] wifi: mt76: mt7925/mt792x: additional stability fixes Zac Bowling
` (2 preceding siblings ...)
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: add NULL checks for MLO link pointers in MCU functions Zac Bowling
@ 2026-01-02 20:03 ` Zac Bowling
2026-01-03 6:46 ` Sean Wang
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: add mutex protection in resume path Zac Bowling
` (2 subsequent siblings)
6 siblings, 1 reply; 25+ messages in thread
From: Zac Bowling @ 2026-01-02 20:03 UTC (permalink / raw)
To: zbowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
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'
because the semaphore is still held.
This issue manifests as devices becoming unusable after suspend/resume
failures or firmware crashes, requiring a full hardware reboot to
recover. This has been widely reported on MT7921 and MT7925 devices.
Apply the same fix that was applied to MT7915 in commit 79dd14f:
1. Release the patch semaphore before starting firmware load (in case
it was held by a previous failed attempt)
2. Restart MCU firmware to ensure clean state
3. Wait briefly for MCU to be ready
This fix applies to both MT7921 and MT7925 drivers which share the
mt792x_load_firmware() function.
Fixes: 'Failed to get patch semaphore' errors after firmware crash
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
mt792x_core.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/mt792x_core.c b/mt792x_core.c
index cc488ee9..b82e4470 100644
--- a/mt792x_core.c
+++ b/mt792x_core.c
@@ -927,6 +927,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.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: add mutex protection in resume path
2026-01-02 20:03 ` [PATCH v2 0/6] wifi: mt76: mt7925/mt792x: additional stability fixes Zac Bowling
` (3 preceding siblings ...)
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt792x: fix firmware reload failure after previous load crash Zac Bowling
@ 2026-01-02 20:03 ` Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: add NULL checks and error handling for MCU calls Zac Bowling
2026-01-02 20:05 ` [PATCH] wifi: mt76: mt7925: comprehensive stability fixes Zac Bowling
6 siblings, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-02 20:03 UTC (permalink / raw)
To: zbowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
From: Zac Bowling <zac@zacbowling.com>
Add mutex protection around mt7925_mcu_set_deep_sleep() and
mt7925_regd_update() calls in the resume path to prevent
potential race conditions during resume operations.
These MCU operations require serialization, and the resume
path was the only call site missing mutex protection.
Found by static analysis (sparse/coccinelle).
---
drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
index ca868619e1b7..b6c90c5f7e91 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
@@ -583,10 +583,12 @@ static int _mt7925_pci_resume(struct device *device, bool restore)
}
/* restore previous ds setting */
+ mt792x_mutex_acquire(dev);
if (!pm->ds_enable)
mt7925_mcu_set_deep_sleep(dev, false);
mt7925_regd_update(dev);
+ mt792x_mutex_release(dev);
failed:
pm->suspended = false;
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: add NULL checks and error handling for MCU calls
2026-01-02 20:03 ` [PATCH v2 0/6] wifi: mt76: mt7925/mt792x: additional stability fixes Zac Bowling
` (4 preceding siblings ...)
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: add mutex protection in resume path Zac Bowling
@ 2026-01-02 20:03 ` Zac Bowling
2026-01-02 20:05 ` [PATCH] wifi: mt76: mt7925: comprehensive stability fixes Zac Bowling
6 siblings, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-02 20:03 UTC (permalink / raw)
To: zbowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
From: Zac Bowling <zac@zacbowling.com>
Add NULL pointer checks for mt792x_sta_to_link() and mt792x_vif_to_link()
results in critical paths to prevent kernel crashes during MLO operations.
Add error logging for MCU return values in mt7925_regd_update() to help
diagnose regulatory domain update failures.
Found by static analysis review.
---
drivers/net/wireless/mediatek/mt76/mt7925/init.c | 13 ++++++++++---
drivers/net/wireless/mediatek/mt76/mt7925/main.c | 8 ++++++++
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
index d7d5afe365ed..f800112ccaf7 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
@@ -162,10 +162,17 @@ void mt7925_regd_update(struct mt792x_dev *dev)
if (!dev->regd_change)
return;
- mt7925_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env);
+ if (mt7925_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env) < 0)
+ dev_warn(dev->mt76.dev, "Failed to set CLC\n");
+
mt7925_regd_channel_update(wiphy, dev);
- mt7925_mcu_set_channel_domain(hw->priv);
- mt7925_set_tx_sar_pwr(hw, NULL);
+
+ if (mt7925_mcu_set_channel_domain(hw->priv) < 0)
+ dev_warn(dev->mt76.dev, "Failed to set channel domain\n");
+
+ if (mt7925_set_tx_sar_pwr(hw, NULL) < 0)
+ dev_warn(dev->mt76.dev, "Failed to set TX SAR power\n");
+
dev->regd_change = false;
}
EXPORT_SYMBOL_GPL(mt7925_regd_update);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 11c0197c7426..b6e3002faf41 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -863,12 +863,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;
@@ -1750,6 +1755,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;
--
2.51.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH] wifi: mt76: mt7925: comprehensive stability fixes
2026-01-02 20:03 ` [PATCH v2 0/6] wifi: mt76: mt7925/mt792x: additional stability fixes Zac Bowling
` (5 preceding siblings ...)
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: add NULL checks and error handling for MCU calls Zac Bowling
@ 2026-01-02 20:05 ` Zac Bowling
2026-01-03 6:25 ` Sean Wang
6 siblings, 1 reply; 25+ messages in thread
From: Zac Bowling @ 2026-01-02 20:05 UTC (permalink / raw)
To: zbowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
From: Zac Bowling <zac@zacbowling.com>
This unified patch combines all MT7925 driver fixes for kernel stability:
1. NULL pointer dereference fixes in vif iteration, TX path, and MCU functions
2. Missing mutex protection in reset, ROC, PM, and resume paths
3. Error handling for MCU commands (AMPDU, BSS info, key setup)
4. lockdep assertions for debugging
5. MLO (Multi-Link Operation) improvements for roaming and AP mode
6. Firmware reload recovery after crashes
These fixes address kernel panics and system hangs that occur during:
- WiFi network switching and BSSID roaming
- Suspend/resume cycles
- MLO link state transitions
- Firmware recovery after crashes
Tested on Framework Desktop (AMD Ryzen AI Max 300) with MT7925 (RZ717).
Individual patches and detailed analysis available at:
https://github.com/zbowling/mt7925
Signed-off-by: Zac Bowling <zac@zacbowling.com>
---
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
index d7d5afe365ed..f800112ccaf7 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
@@ -162,10 +162,17 @@ void mt7925_regd_update(struct mt792x_dev *dev)
if (!dev->regd_change)
return;
- mt7925_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env);
+ if (mt7925_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env) < 0)
+ dev_warn(dev->mt76.dev, "Failed to set CLC\n");
+
mt7925_regd_channel_update(wiphy, dev);
- mt7925_mcu_set_channel_domain(hw->priv);
- mt7925_set_tx_sar_pwr(hw, NULL);
+
+ if (mt7925_mcu_set_channel_domain(hw->priv) < 0)
+ dev_warn(dev->mt76.dev, "Failed to set channel domain\n");
+
+ if (mt7925_set_tx_sar_pwr(hw, NULL) < 0)
+ dev_warn(dev->mt76.dev, "Failed to set TX SAR power\n");
+
dev->regd_change = false;
}
EXPORT_SYMBOL_GPL(mt7925_regd_update);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index 1e44e96f034e..a4109dc72163 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
@@ -1270,6 +1270,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);
@@ -1324,9 +1330,11 @@ void mt7925_mac_reset_work(struct work_struct *work)
dev->hw_full_reset = false;
pm->suspended = false;
ieee80211_wake_queues(hw);
+ mt792x_mutex_acquire(dev);
ieee80211_iterate_active_interfaces(hw,
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7925_vif_connect_iter, NULL);
+ mt792x_mutex_release(dev);
mt76_connac_power_save_sched(&dev->mt76.phy, pm);
mt792x_mutex_acquire(dev);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index ac3d485a2f78..b6e3002faf41 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -596,6 +596,17 @@ 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) {
+ /* During MLO roaming, link state may be torn down before
+ * mac80211 requests key removal. If removing a key and
+ * the link is already gone, consider it successfully removed.
+ */
+ if (cmd != SET_KEY)
+ return 0;
+ return -EINVAL;
+ }
+
wcid = &mlink->wcid;
wcid_keyidx = &wcid->hw_key_idx;
@@ -625,8 +636,10 @@ static int mt7925_set_link_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
struct mt792x_phy *phy = mt792x_hw_phy(hw);
mconf->mt76.cipher = mt7925_mcu_get_cipher(key->cipher);
- mt7925_mcu_add_bss_info(phy, mconf->mt76.ctx, link_conf,
- link_sta, true);
+ err = mt7925_mcu_add_bss_info(phy, mconf->mt76.ctx, link_conf,
+ link_sta, true);
+ if (err)
+ goto out;
}
if (cmd == SET_KEY)
@@ -743,9 +756,11 @@ void mt7925_set_runtime_pm(struct mt792x_dev *dev)
bool monitor = !!(hw->conf.flags & IEEE80211_CONF_MONITOR);
pm->enable = pm->enable_user && !monitor;
+ mt792x_mutex_acquire(dev);
ieee80211_iterate_active_interfaces(hw,
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7925_pm_interface_iter, dev);
+ mt792x_mutex_release(dev);
pm->ds_enable = pm->ds_enable_user && !monitor;
mt7925_mcu_set_deep_sleep(dev, pm->ds_enable);
}
@@ -848,12 +863,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;
@@ -879,15 +899,20 @@ 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) {
if (ieee80211_vif_is_mld(vif))
- mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
- link_conf, link_sta, link_sta != mlink->pri_link);
+ ret = mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
+ link_conf, link_sta,
+ link_sta != mlink->pri_link);
else
- mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
- link_conf, link_sta, false);
+ ret = mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
+ link_conf, link_sta, false);
+ if (ret)
+ return ret;
}
if (ieee80211_vif_is_mld(vif) &&
@@ -985,18 +1010,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;
+
+ link_conf = mt792x_vif_to_bss_conf(vif, mvif->deflink_id);
+ if (!link_conf)
+ return;
+
+ chandef = &link_conf->chanreq.oper;
+ band = chandef->chan->band;
- u16 sel_links = mt76_select_links(vif, 2);
- u8 secondary_link_id = __ffs(~BIT(mvif->deflink_id) & sel_links);
+ 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 ||
@@ -1024,6 +1060,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);
@@ -1033,12 +1071,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);
@@ -1085,6 +1124,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);
@@ -1098,10 +1139,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);
@@ -1109,6 +1152,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))
@@ -1247,22 +1291,22 @@ mt7925_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
case IEEE80211_AMPDU_RX_START:
mt76_rx_aggr_start(&dev->mt76, &msta->deflink.wcid, tid, ssn,
params->buf_size);
- mt7925_mcu_uni_rx_ba(dev, params, true);
+ ret = mt7925_mcu_uni_rx_ba(dev, params, true);
break;
case IEEE80211_AMPDU_RX_STOP:
mt76_rx_aggr_stop(&dev->mt76, &msta->deflink.wcid, tid);
- mt7925_mcu_uni_rx_ba(dev, params, false);
+ ret = mt7925_mcu_uni_rx_ba(dev, params, false);
break;
case IEEE80211_AMPDU_TX_OPERATIONAL:
mtxq->aggr = true;
mtxq->send_bar = false;
- mt7925_mcu_uni_tx_ba(dev, params, true);
+ ret = mt7925_mcu_uni_tx_ba(dev, params, true);
break;
case IEEE80211_AMPDU_TX_STOP_FLUSH:
case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
mtxq->aggr = false;
clear_bit(tid, &msta->deflink.wcid.ampdu_state);
- mt7925_mcu_uni_tx_ba(dev, params, false);
+ ret = mt7925_mcu_uni_tx_ba(dev, params, false);
break;
case IEEE80211_AMPDU_TX_START:
set_bit(tid, &msta->deflink.wcid.ampdu_state);
@@ -1271,8 +1315,9 @@ mt7925_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
case IEEE80211_AMPDU_TX_STOP_CONT:
mtxq->aggr = false;
clear_bit(tid, &msta->deflink.wcid.ampdu_state);
- mt7925_mcu_uni_tx_ba(dev, params, false);
- ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
+ ret = mt7925_mcu_uni_tx_ba(dev, params, false);
+ if (!ret)
+ ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
break;
}
mt792x_mutex_release(dev);
@@ -1293,12 +1338,12 @@ mt7925_mlo_pm_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
if (mvif->mlo_pm_state != MT792x_MLO_CHANGED_PS)
return;
- 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);
}
void mt7925_mlo_pm_work(struct work_struct *work)
@@ -1307,9 +1352,11 @@ void mt7925_mlo_pm_work(struct work_struct *work)
mlo_pm_work.work);
struct ieee80211_hw *hw = mt76_hw(dev);
+ mt792x_mutex_acquire(dev);
ieee80211_iterate_active_interfaces(hw,
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7925_mlo_pm_iter, dev);
+ mt792x_mutex_release(dev);
}
static bool is_valid_alpha2(const char *alpha2)
@@ -1645,6 +1692,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);
}
}
@@ -1706,6 +1755,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;
@@ -1876,6 +1928,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);
}
}
@@ -1891,6 +1945,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);
}
}
@@ -1912,7 +1968,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);
@@ -2033,6 +2094,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);
@@ -2113,9 +2179,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);
@@ -2144,6 +2215,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 8eda407e4135..cf38e36790e7 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -1722,6 +1722,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;
@@ -1800,6 +1804,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;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
index 8eb1fe1082d1..b6c90c5f7e91 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
@@ -454,7 +454,9 @@ static int mt7925_pci_suspend(struct device *device)
cancel_delayed_work_sync(&pm->ps_work);
cancel_work_sync(&pm->wake_work);
+ mt792x_mutex_acquire(dev);
mt7925_roc_abort_sync(dev);
+ mt792x_mutex_release(dev);
err = mt792x_mcu_drv_pmctrl(dev);
if (err < 0)
@@ -581,10 +583,12 @@ static int _mt7925_pci_resume(struct device *device, bool restore)
}
/* restore previous ds setting */
+ mt792x_mutex_acquire(dev);
if (!pm->ds_enable)
mt7925_mcu_set_deep_sleep(dev, false);
mt7925_regd_update(dev);
+ mt792x_mutex_release(dev);
failed:
pm->suspended = false;
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
index 9cad572c34a3..0170a23b0529 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);
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [PATCH] wifi: mt76: mt7925: comprehensive stability fixes
2026-01-02 20:05 ` [PATCH] wifi: mt76: mt7925: comprehensive stability fixes Zac Bowling
@ 2026-01-03 6:25 ` Sean Wang
2026-01-03 19:11 ` Zac Bowling
0 siblings, 1 reply; 25+ messages in thread
From: Sean Wang @ 2026-01-03 6:25 UTC (permalink / raw)
To: Zac Bowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
Hi Zac,
Thanks for the extensive work digging into the MT7925 stability issues
the problems you’re addressing are real and definitely worth fixing.
For upstream review, it would help a lot to align with a few common practices:
1) One patch should handle one issue. Splitting this into smaller,
self-contained patches makes review easier and allows safe reverts.
2) For fixes of runtime failures (panic, NULL deref, hangs), please include
the relevant dmesg or crash log in the commit message so reviewers and
downstream users can clearly see the failure being addressed and
determine whether they are hitting the same issue.
3) If a fix comes from static analysis (e.g. clang static analyzer), that’s
perfectly fine, just mention it in the commit message and briefly explain
why the state or pointer can be invalid at runtime.
4) For review, it would also be helpful to aggregate the fixes from v1, v2,
and this one into a clean v3 series based on the current wireless
tree (https://github.com/nbd168/wireless.git).
Sean
On Fri, Jan 2, 2026 at 2:05 PM Zac Bowling <zbowling@gmail.com> wrote:
>
> From: Zac Bowling <zac@zacbowling.com>
>
> This unified patch combines all MT7925 driver fixes for kernel stability:
>
> 1. NULL pointer dereference fixes in vif iteration, TX path, and MCU functions
> 2. Missing mutex protection in reset, ROC, PM, and resume paths
> 3. Error handling for MCU commands (AMPDU, BSS info, key setup)
> 4. lockdep assertions for debugging
> 5. MLO (Multi-Link Operation) improvements for roaming and AP mode
> 6. Firmware reload recovery after crashes
>
> These fixes address kernel panics and system hangs that occur during:
> - WiFi network switching and BSSID roaming
> - Suspend/resume cycles
> - MLO link state transitions
> - Firmware recovery after crashes
>
> Tested on Framework Desktop (AMD Ryzen AI Max 300) with MT7925 (RZ717).
>
> Individual patches and detailed analysis available at:
> https://github.com/zbowling/mt7925
>
> Signed-off-by: Zac Bowling <zac@zacbowling.com>
> ---
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
> index d7d5afe365ed..f800112ccaf7 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
> @@ -162,10 +162,17 @@ void mt7925_regd_update(struct mt792x_dev *dev)
> if (!dev->regd_change)
> return;
>
> - mt7925_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env);
> + if (mt7925_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env) < 0)
> + dev_warn(dev->mt76.dev, "Failed to set CLC\n");
> +
> mt7925_regd_channel_update(wiphy, dev);
> - mt7925_mcu_set_channel_domain(hw->priv);
> - mt7925_set_tx_sar_pwr(hw, NULL);
> +
> + if (mt7925_mcu_set_channel_domain(hw->priv) < 0)
> + dev_warn(dev->mt76.dev, "Failed to set channel domain\n");
> +
> + if (mt7925_set_tx_sar_pwr(hw, NULL) < 0)
> + dev_warn(dev->mt76.dev, "Failed to set TX SAR power\n");
> +
> dev->regd_change = false;
> }
> EXPORT_SYMBOL_GPL(mt7925_regd_update);
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
> index 1e44e96f034e..a4109dc72163 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
> @@ -1270,6 +1270,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);
> @@ -1324,9 +1330,11 @@ void mt7925_mac_reset_work(struct work_struct *work)
> dev->hw_full_reset = false;
> pm->suspended = false;
> ieee80211_wake_queues(hw);
> + mt792x_mutex_acquire(dev);
> ieee80211_iterate_active_interfaces(hw,
> IEEE80211_IFACE_ITER_RESUME_ALL,
> mt7925_vif_connect_iter, NULL);
> + mt792x_mutex_release(dev);
> mt76_connac_power_save_sched(&dev->mt76.phy, pm);
>
> mt792x_mutex_acquire(dev);
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> index ac3d485a2f78..b6e3002faf41 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> @@ -596,6 +596,17 @@ 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) {
> + /* During MLO roaming, link state may be torn down before
> + * mac80211 requests key removal. If removing a key and
> + * the link is already gone, consider it successfully removed.
> + */
> + if (cmd != SET_KEY)
> + return 0;
> + return -EINVAL;
> + }
> +
> wcid = &mlink->wcid;
> wcid_keyidx = &wcid->hw_key_idx;
>
> @@ -625,8 +636,10 @@ static int mt7925_set_link_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
> struct mt792x_phy *phy = mt792x_hw_phy(hw);
>
> mconf->mt76.cipher = mt7925_mcu_get_cipher(key->cipher);
> - mt7925_mcu_add_bss_info(phy, mconf->mt76.ctx, link_conf,
> - link_sta, true);
> + err = mt7925_mcu_add_bss_info(phy, mconf->mt76.ctx, link_conf,
> + link_sta, true);
> + if (err)
> + goto out;
> }
>
> if (cmd == SET_KEY)
> @@ -743,9 +756,11 @@ void mt7925_set_runtime_pm(struct mt792x_dev *dev)
> bool monitor = !!(hw->conf.flags & IEEE80211_CONF_MONITOR);
>
> pm->enable = pm->enable_user && !monitor;
> + mt792x_mutex_acquire(dev);
> ieee80211_iterate_active_interfaces(hw,
> IEEE80211_IFACE_ITER_RESUME_ALL,
> mt7925_pm_interface_iter, dev);
> + mt792x_mutex_release(dev);
> pm->ds_enable = pm->ds_enable_user && !monitor;
> mt7925_mcu_set_deep_sleep(dev, pm->ds_enable);
> }
> @@ -848,12 +863,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;
> @@ -879,15 +899,20 @@ 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) {
> if (ieee80211_vif_is_mld(vif))
> - mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
> - link_conf, link_sta, link_sta != mlink->pri_link);
> + ret = mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
> + link_conf, link_sta,
> + link_sta != mlink->pri_link);
> else
> - mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
> - link_conf, link_sta, false);
> + ret = mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
> + link_conf, link_sta, false);
> + if (ret)
> + return ret;
> }
>
> if (ieee80211_vif_is_mld(vif) &&
> @@ -985,18 +1010,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;
> +
> + link_conf = mt792x_vif_to_bss_conf(vif, mvif->deflink_id);
> + if (!link_conf)
> + return;
> +
> + chandef = &link_conf->chanreq.oper;
> + band = chandef->chan->band;
>
> - u16 sel_links = mt76_select_links(vif, 2);
> - u8 secondary_link_id = __ffs(~BIT(mvif->deflink_id) & sel_links);
> + 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 ||
> @@ -1024,6 +1060,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);
>
> @@ -1033,12 +1071,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);
> @@ -1085,6 +1124,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);
>
> @@ -1098,10 +1139,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);
> @@ -1109,6 +1152,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))
> @@ -1247,22 +1291,22 @@ mt7925_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
> case IEEE80211_AMPDU_RX_START:
> mt76_rx_aggr_start(&dev->mt76, &msta->deflink.wcid, tid, ssn,
> params->buf_size);
> - mt7925_mcu_uni_rx_ba(dev, params, true);
> + ret = mt7925_mcu_uni_rx_ba(dev, params, true);
> break;
> case IEEE80211_AMPDU_RX_STOP:
> mt76_rx_aggr_stop(&dev->mt76, &msta->deflink.wcid, tid);
> - mt7925_mcu_uni_rx_ba(dev, params, false);
> + ret = mt7925_mcu_uni_rx_ba(dev, params, false);
> break;
> case IEEE80211_AMPDU_TX_OPERATIONAL:
> mtxq->aggr = true;
> mtxq->send_bar = false;
> - mt7925_mcu_uni_tx_ba(dev, params, true);
> + ret = mt7925_mcu_uni_tx_ba(dev, params, true);
> break;
> case IEEE80211_AMPDU_TX_STOP_FLUSH:
> case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
> mtxq->aggr = false;
> clear_bit(tid, &msta->deflink.wcid.ampdu_state);
> - mt7925_mcu_uni_tx_ba(dev, params, false);
> + ret = mt7925_mcu_uni_tx_ba(dev, params, false);
> break;
> case IEEE80211_AMPDU_TX_START:
> set_bit(tid, &msta->deflink.wcid.ampdu_state);
> @@ -1271,8 +1315,9 @@ mt7925_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
> case IEEE80211_AMPDU_TX_STOP_CONT:
> mtxq->aggr = false;
> clear_bit(tid, &msta->deflink.wcid.ampdu_state);
> - mt7925_mcu_uni_tx_ba(dev, params, false);
> - ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
> + ret = mt7925_mcu_uni_tx_ba(dev, params, false);
> + if (!ret)
> + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
> break;
> }
> mt792x_mutex_release(dev);
> @@ -1293,12 +1338,12 @@ mt7925_mlo_pm_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
> if (mvif->mlo_pm_state != MT792x_MLO_CHANGED_PS)
> return;
>
> - 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);
> }
>
> void mt7925_mlo_pm_work(struct work_struct *work)
> @@ -1307,9 +1352,11 @@ void mt7925_mlo_pm_work(struct work_struct *work)
> mlo_pm_work.work);
> struct ieee80211_hw *hw = mt76_hw(dev);
>
> + mt792x_mutex_acquire(dev);
> ieee80211_iterate_active_interfaces(hw,
> IEEE80211_IFACE_ITER_RESUME_ALL,
> mt7925_mlo_pm_iter, dev);
> + mt792x_mutex_release(dev);
> }
>
> static bool is_valid_alpha2(const char *alpha2)
> @@ -1645,6 +1692,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);
> }
> }
> @@ -1706,6 +1755,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;
>
> @@ -1876,6 +1928,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);
> }
> }
> @@ -1891,6 +1945,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);
> }
> }
> @@ -1912,7 +1968,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);
>
> @@ -2033,6 +2094,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);
>
> @@ -2113,9 +2179,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);
> @@ -2144,6 +2215,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 8eda407e4135..cf38e36790e7 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
> @@ -1722,6 +1722,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;
>
> @@ -1800,6 +1804,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;
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> index 8eb1fe1082d1..b6c90c5f7e91 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> @@ -454,7 +454,9 @@ static int mt7925_pci_suspend(struct device *device)
> cancel_delayed_work_sync(&pm->ps_work);
> cancel_work_sync(&pm->wake_work);
>
> + mt792x_mutex_acquire(dev);
> mt7925_roc_abort_sync(dev);
> + mt792x_mutex_release(dev);
>
> err = mt792x_mcu_drv_pmctrl(dev);
> if (err < 0)
> @@ -581,10 +583,12 @@ static int _mt7925_pci_resume(struct device *device, bool restore)
> }
>
> /* restore previous ds setting */
> + mt792x_mutex_acquire(dev);
> if (!pm->ds_enable)
> mt7925_mcu_set_deep_sleep(dev, false);
>
> mt7925_regd_update(dev);
> + mt792x_mutex_release(dev);
> failed:
> pm->suspended = false;
>
> diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
> index 9cad572c34a3..0170a23b0529 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);
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH] wifi: mt76: mt792x: fix firmware reload failure after previous load crash
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt792x: fix firmware reload failure after previous load crash Zac Bowling
@ 2026-01-03 6:46 ` Sean Wang
2026-01-03 18:42 ` Zac Bowling
0 siblings, 1 reply; 25+ messages in thread
From: Sean Wang @ 2026-01-03 6:46 UTC (permalink / raw)
To: Zac Bowling
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
On Fri, Jan 2, 2026 at 2:03 PM Zac Bowling <zbowling@gmail.com> wrote:
>
> 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'
> because the semaphore is still held.
>
> This issue manifests as devices becoming unusable after suspend/resume
> failures or firmware crashes, requiring a full hardware reboot to
> recover. This has been widely reported on MT7921 and MT7925 devices.
>
> Apply the same fix that was applied to MT7915 in commit 79dd14f:
> 1. Release the patch semaphore before starting firmware load (in case
> it was held by a previous failed attempt)
> 2. Restart MCU firmware to ensure clean state
> 3. Wait briefly for MCU to be ready
>
> This fix applies to both MT7921 and MT7925 drivers which share the
> mt792x_load_firmware() function.
>
> Fixes: 'Failed to get patch semaphore' errors after firmware crash
> Signed-off-by: Zac Bowling <zac@zacbowling.com>
> ---
> mt792x_core.c | 14 ++++++++++++++
> 1 file changed, 14 insertions(+)
>
> diff --git a/mt792x_core.c b/mt792x_core.c
> index cc488ee9..b82e4470 100644
> --- a/mt792x_core.c
> +++ b/mt792x_core.c
> @@ -927,6 +927,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);
> +
Hi Zac,
This is a good finding. Since this is a common mt792x code path, have you
also had a chance to test it on MT7921?
One small nit: the Fixes tag should reference the actual commit being
fixed, e.g.
Fixes: <commit-sha> ("mt76: mt792x: ...")
instead of the error string.
Sean
> ret = mt76_connac2_load_patch(&dev->mt76, mt792x_patch_name(dev));
> if (ret)
> return ret;
> --
> 2.51.0
>
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH] wifi: mt76: mt792x: fix firmware reload failure after previous load crash
2026-01-03 6:46 ` Sean Wang
@ 2026-01-03 18:42 ` Zac Bowling
0 siblings, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-03 18:42 UTC (permalink / raw)
To: Sean Wang
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
Hi Sean,
Thanks! I don't have a MT7921, only a MT7925, so no unfortunately. I
ordered off Amazon and should be here in a week or two.
Zac Bowling
Zac Bowling
On Fri, Jan 2, 2026 at 10:46 PM Sean Wang <sean.wang@kernel.org> wrote:
>
> On Fri, Jan 2, 2026 at 2:03 PM Zac Bowling <zbowling@gmail.com> wrote:
> >
> > 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'
> > because the semaphore is still held.
> >
> > This issue manifests as devices becoming unusable after suspend/resume
> > failures or firmware crashes, requiring a full hardware reboot to
> > recover. This has been widely reported on MT7921 and MT7925 devices.
> >
> > Apply the same fix that was applied to MT7915 in commit 79dd14f:
> > 1. Release the patch semaphore before starting firmware load (in case
> > it was held by a previous failed attempt)
> > 2. Restart MCU firmware to ensure clean state
> > 3. Wait briefly for MCU to be ready
> >
> > This fix applies to both MT7921 and MT7925 drivers which share the
> > mt792x_load_firmware() function.
> >
> > Fixes: 'Failed to get patch semaphore' errors after firmware crash
> > Signed-off-by: Zac Bowling <zac@zacbowling.com>
> > ---
> > mt792x_core.c | 14 ++++++++++++++
> > 1 file changed, 14 insertions(+)
> >
> > diff --git a/mt792x_core.c b/mt792x_core.c
> > index cc488ee9..b82e4470 100644
> > --- a/mt792x_core.c
> > +++ b/mt792x_core.c
> > @@ -927,6 +927,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);
> > +
>
> Hi Zac,
>
> This is a good finding. Since this is a common mt792x code path, have you
> also had a chance to test it on MT7921?
>
> One small nit: the Fixes tag should reference the actual commit being
> fixed, e.g.
>
> Fixes: <commit-sha> ("mt76: mt792x: ...")
>
> instead of the error string.
>
> Sean
>
> > ret = mt76_connac2_load_patch(&dev->mt76, mt792x_patch_name(dev));
> > if (ret)
> > return ret;
> > --
> > 2.51.0
> >
> >
> >
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH] wifi: mt76: mt7925: comprehensive stability fixes
2026-01-03 6:25 ` Sean Wang
@ 2026-01-03 19:11 ` Zac Bowling
0 siblings, 0 replies; 25+ messages in thread
From: Zac Bowling @ 2026-01-03 19:11 UTC (permalink / raw)
To: Sean Wang
Cc: deren.wu, kvalo, linux-kernel, linux-mediatek, linux-wireless,
lorenzo, nbd, ryder.lee, sean.wang
Hi Sean,
1) I have some 17 smaller patches that got into more detail
but didn't want to spam. When sending here to LKMS I squashed that
down into about 7 initially that I have already sent in the last 3 days.
I wasn't sure if a fully squashed patch would make it easier to review,
since it's all related to a similar class of bug, which this patch is.
Most of this stemmed from null deref crash I ran into in a few places,
either during re-auth attempts that failed from a bad key or from other
races in state changes. When I was investigating searching google I
found a half a dozen other similar crashes posted on forums from folks
that hit similar things but no correct solution. Just folks doing hacks.
That's when I wrote a little stress test tool that if you have the right AP
environment like two APs with the same SSID with Wifi 7 and MLO
enabled you can trigger different races with some hammering and get a
consistent repro case. I'm using 3 Ubiquiti 7 Pro APs and it's been panic
city over here the last month which got me motivated enough to
investigate over my holiday break.
Some of these null and error return checks are purely defensive
additions to prevent future regressions.
2) I have a folder full of dumps after my stress tests to repro :)
They all look similar to this but the exact null defref is not always in
the same place. All from after ieee80211_iterate_interfaces so my patches
mostly work to check locks in or around that call. The deadlocks I have
some dmesg logs but that aren't too interesting.
[ 655.737302] [ T12] BUG: kernel NULL pointer dereference, address:
0000000000000000
[ 655.737320] [ T12] #PF: supervisor read access in kernel mode
[ 655.737324] [ T12] #PF: error_code(0x0000) - not-present page
[ 655.737328] [ T12] PGD 0 P4D 0
[ 655.737334] [ T12] Oops: Oops: 0000 [#1] SMP NOPTI
[ 655.737342] [ T12] CPU: 20 UID: 0 PID: 12 Comm: kworker/u128:0
Kdump: loaded Tainted: G OE 6.17.0-8-generic #8-Ubuntu
PREEMPT(voluntary)
[ 655.737350] [ T12] Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE
[ 655.737351] [ T12] Hardware name: Framework Desktop (AMD Ryzen AI
Max 300 Series)/FRANMFCP06, BIOS 03.04 11/19/2025
[ 655.737354] [ T12] Workqueue: mt76 mt7925_mac_reset_work [mt7925_common]
[ 655.737370] [ T12] RIP: 0010:mt76_connac_mcu_uni_add_dev+0xba/0x1f0
[mt76_connac_lib]
[ 655.737385] [ T12] Code: cc 66 44 89 5d d2 44 88 45 d4 44 88 4d d5
88 65 d7 c6 45 dc 01 88 55 dd 0f b7 97 b8 00 00 00 88 4d ef 66 89 55
e4 66 89 55 ea <48> 8b 16 8b 12 83 fa 03 0f 84 0c 01 00 00 77 1b 83 fa
01 0f 84 f5
[ 655.737388] [ T12] RSP: 0018:ffffd07fc018fcb0 EFLAGS: 00010282
[ 655.737392] [ T12] RAX: 000000000000ff00 RBX: ffff8a4449442040 RCX:
0000000000000000
[ 655.737394] [ T12] RDX: 0000000000000013 RSI: 0000000000000000 RDI:
ffff8a44c7d7a4b0
[ 655.737396] [ T12] RBP: ffffd07fc018fcf8 R08: 0000000000000001 R09:
0000000000000000
[ 655.737397] [ T12] R10: 0000000000000000 R11: 0000000000000020 R12:
ffff8a4449442040
[ 655.737399] [ T12] R13: ffff8a44c7d79f08 R14: 0000000000000000 R15:
ffff8a44c7d78a80
[ 655.737401] [ T12] FS: 0000000000000000(0000)
GS:ffff8a53ca47f000(0000) knlGS:0000000000000000
[ 655.737403] [ T12] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 655.737404] [ T12] CR2: 0000000000000000 CR3: 00000009e2a40000 CR4:
0000000000f50ef0
[ 655.737406] [ T12] PKRU: 55555554
[ 655.737408] [ T12] Call Trace:
[ 655.737411] [ T12]
[ 655.737416] [ T12] mt7925_vif_connect_iter+0xcb/0x240 [mt7925_common]
[ 655.737423] [ T12] __iterate_interfaces+0x92/0x130 [mac80211]
[ 655.737500] [ T12] ? __pfx_mt7925_vif_connect_iter+0x10/0x10 [mt7925_common]
[ 655.737506] [ T12] ieee80211_iterate_interfaces+0x3d/0x60 [mac80211]
[ 655.737549] [ T12] ? __pfx_mt7925_vif_connect_iter+0x10/0x10 [mt7925_common]
[ 655.737553] [ T12] mt7925_mac_reset_work+0x105/0x190 [mt7925_common]
[ 655.737559] [ T12] process_one_work+0x18b/0x370
[ 655.737567] [ T12] worker_thread+0x317/0x450
[ 655.737570] [ T12] ? __pfx_worker_thread+0x10/0x10
[ 655.737573] [ T12] kthread+0x108/0x220
[ 655.737577] [ T12] ? __pfx_kthread+0x10/0x10
[ 655.737579] [ T12] ret_from_fork+0x131/0x150
[ 655.737585] [ T12] ? __pfx_kthread+0x10/0x10
[ 655.737587] [ T12] ret_from_fork_asm+0x1a/0x30
3) Yeah, some of my later patches came from static analysis
(clang-tidy, etc) but
also AI, mostly looking for additional cases I missed since I'm new to
this code. Some
of the patches were actually porting over changes made for other MT chipsets
not applied to this one that seemed relevant. I'll try to include more
details. This is all
just an attempt to get my personal machine stable overnight so I don't
have to run
ethernet and just blacklist the driver :)
4) Sounds good! Will do!
Zac Bowling
On Fri, Jan 2, 2026 at 10:26 PM Sean Wang <sean.wang@kernel.org> wrote:
>
> Hi Zac,
>
> Thanks for the extensive work digging into the MT7925 stability issues
> the problems you’re addressing are real and definitely worth fixing.
>
> For upstream review, it would help a lot to align with a few common practices:
>
> 1) One patch should handle one issue. Splitting this into smaller,
> self-contained patches makes review easier and allows safe reverts.
>
> 2) For fixes of runtime failures (panic, NULL deref, hangs), please include
> the relevant dmesg or crash log in the commit message so reviewers and
> downstream users can clearly see the failure being addressed and
> determine whether they are hitting the same issue.
>
> 3) If a fix comes from static analysis (e.g. clang static analyzer), that’s
> perfectly fine, just mention it in the commit message and briefly explain
> why the state or pointer can be invalid at runtime.
>
> 4) For review, it would also be helpful to aggregate the fixes from v1, v2,
> and this one into a clean v3 series based on the current wireless
> tree (https://github.com/nbd168/wireless.git).
>
> Sean
>
> On Fri, Jan 2, 2026 at 2:05 PM Zac Bowling <zbowling@gmail.com> wrote:
> >
> > From: Zac Bowling <zac@zacbowling.com>
> >
> > This unified patch combines all MT7925 driver fixes for kernel stability:
> >
> > 1. NULL pointer dereference fixes in vif iteration, TX path, and MCU functions
> > 2. Missing mutex protection in reset, ROC, PM, and resume paths
> > 3. Error handling for MCU commands (AMPDU, BSS info, key setup)
> > 4. lockdep assertions for debugging
> > 5. MLO (Multi-Link Operation) improvements for roaming and AP mode
> > 6. Firmware reload recovery after crashes
> >
> > These fixes address kernel panics and system hangs that occur during:
> > - WiFi network switching and BSSID roaming
> > - Suspend/resume cycles
> > - MLO link state transitions
> > - Firmware recovery after crashes
> >
> > Tested on Framework Desktop (AMD Ryzen AI Max 300) with MT7925 (RZ717).
> >
> > Individual patches and detailed analysis available at:
> > https://github.com/zbowling/mt7925
> >
> > Signed-off-by: Zac Bowling <zac@zacbowling.com>
> > ---
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
> > index d7d5afe365ed..f800112ccaf7 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
> > @@ -162,10 +162,17 @@ void mt7925_regd_update(struct mt792x_dev *dev)
> > if (!dev->regd_change)
> > return;
> >
> > - mt7925_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env);
> > + if (mt7925_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env) < 0)
> > + dev_warn(dev->mt76.dev, "Failed to set CLC\n");
> > +
> > mt7925_regd_channel_update(wiphy, dev);
> > - mt7925_mcu_set_channel_domain(hw->priv);
> > - mt7925_set_tx_sar_pwr(hw, NULL);
> > +
> > + if (mt7925_mcu_set_channel_domain(hw->priv) < 0)
> > + dev_warn(dev->mt76.dev, "Failed to set channel domain\n");
> > +
> > + if (mt7925_set_tx_sar_pwr(hw, NULL) < 0)
> > + dev_warn(dev->mt76.dev, "Failed to set TX SAR power\n");
> > +
> > dev->regd_change = false;
> > }
> > EXPORT_SYMBOL_GPL(mt7925_regd_update);
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
> > index 1e44e96f034e..a4109dc72163 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
> > @@ -1270,6 +1270,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);
> > @@ -1324,9 +1330,11 @@ void mt7925_mac_reset_work(struct work_struct *work)
> > dev->hw_full_reset = false;
> > pm->suspended = false;
> > ieee80211_wake_queues(hw);
> > + mt792x_mutex_acquire(dev);
> > ieee80211_iterate_active_interfaces(hw,
> > IEEE80211_IFACE_ITER_RESUME_ALL,
> > mt7925_vif_connect_iter, NULL);
> > + mt792x_mutex_release(dev);
> > mt76_connac_power_save_sched(&dev->mt76.phy, pm);
> >
> > mt792x_mutex_acquire(dev);
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> > index ac3d485a2f78..b6e3002faf41 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> > @@ -596,6 +596,17 @@ 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) {
> > + /* During MLO roaming, link state may be torn down before
> > + * mac80211 requests key removal. If removing a key and
> > + * the link is already gone, consider it successfully removed.
> > + */
> > + if (cmd != SET_KEY)
> > + return 0;
> > + return -EINVAL;
> > + }
> > +
> > wcid = &mlink->wcid;
> > wcid_keyidx = &wcid->hw_key_idx;
> >
> > @@ -625,8 +636,10 @@ static int mt7925_set_link_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
> > struct mt792x_phy *phy = mt792x_hw_phy(hw);
> >
> > mconf->mt76.cipher = mt7925_mcu_get_cipher(key->cipher);
> > - mt7925_mcu_add_bss_info(phy, mconf->mt76.ctx, link_conf,
> > - link_sta, true);
> > + err = mt7925_mcu_add_bss_info(phy, mconf->mt76.ctx, link_conf,
> > + link_sta, true);
> > + if (err)
> > + goto out;
> > }
> >
> > if (cmd == SET_KEY)
> > @@ -743,9 +756,11 @@ void mt7925_set_runtime_pm(struct mt792x_dev *dev)
> > bool monitor = !!(hw->conf.flags & IEEE80211_CONF_MONITOR);
> >
> > pm->enable = pm->enable_user && !monitor;
> > + mt792x_mutex_acquire(dev);
> > ieee80211_iterate_active_interfaces(hw,
> > IEEE80211_IFACE_ITER_RESUME_ALL,
> > mt7925_pm_interface_iter, dev);
> > + mt792x_mutex_release(dev);
> > pm->ds_enable = pm->ds_enable_user && !monitor;
> > mt7925_mcu_set_deep_sleep(dev, pm->ds_enable);
> > }
> > @@ -848,12 +863,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;
> > @@ -879,15 +899,20 @@ 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) {
> > if (ieee80211_vif_is_mld(vif))
> > - mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
> > - link_conf, link_sta, link_sta != mlink->pri_link);
> > + ret = mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
> > + link_conf, link_sta,
> > + link_sta != mlink->pri_link);
> > else
> > - mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
> > - link_conf, link_sta, false);
> > + ret = mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
> > + link_conf, link_sta, false);
> > + if (ret)
> > + return ret;
> > }
> >
> > if (ieee80211_vif_is_mld(vif) &&
> > @@ -985,18 +1010,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;
> > +
> > + link_conf = mt792x_vif_to_bss_conf(vif, mvif->deflink_id);
> > + if (!link_conf)
> > + return;
> > +
> > + chandef = &link_conf->chanreq.oper;
> > + band = chandef->chan->band;
> >
> > - u16 sel_links = mt76_select_links(vif, 2);
> > - u8 secondary_link_id = __ffs(~BIT(mvif->deflink_id) & sel_links);
> > + 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 ||
> > @@ -1024,6 +1060,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);
> >
> > @@ -1033,12 +1071,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);
> > @@ -1085,6 +1124,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);
> >
> > @@ -1098,10 +1139,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);
> > @@ -1109,6 +1152,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))
> > @@ -1247,22 +1291,22 @@ mt7925_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
> > case IEEE80211_AMPDU_RX_START:
> > mt76_rx_aggr_start(&dev->mt76, &msta->deflink.wcid, tid, ssn,
> > params->buf_size);
> > - mt7925_mcu_uni_rx_ba(dev, params, true);
> > + ret = mt7925_mcu_uni_rx_ba(dev, params, true);
> > break;
> > case IEEE80211_AMPDU_RX_STOP:
> > mt76_rx_aggr_stop(&dev->mt76, &msta->deflink.wcid, tid);
> > - mt7925_mcu_uni_rx_ba(dev, params, false);
> > + ret = mt7925_mcu_uni_rx_ba(dev, params, false);
> > break;
> > case IEEE80211_AMPDU_TX_OPERATIONAL:
> > mtxq->aggr = true;
> > mtxq->send_bar = false;
> > - mt7925_mcu_uni_tx_ba(dev, params, true);
> > + ret = mt7925_mcu_uni_tx_ba(dev, params, true);
> > break;
> > case IEEE80211_AMPDU_TX_STOP_FLUSH:
> > case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
> > mtxq->aggr = false;
> > clear_bit(tid, &msta->deflink.wcid.ampdu_state);
> > - mt7925_mcu_uni_tx_ba(dev, params, false);
> > + ret = mt7925_mcu_uni_tx_ba(dev, params, false);
> > break;
> > case IEEE80211_AMPDU_TX_START:
> > set_bit(tid, &msta->deflink.wcid.ampdu_state);
> > @@ -1271,8 +1315,9 @@ mt7925_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
> > case IEEE80211_AMPDU_TX_STOP_CONT:
> > mtxq->aggr = false;
> > clear_bit(tid, &msta->deflink.wcid.ampdu_state);
> > - mt7925_mcu_uni_tx_ba(dev, params, false);
> > - ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
> > + ret = mt7925_mcu_uni_tx_ba(dev, params, false);
> > + if (!ret)
> > + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
> > break;
> > }
> > mt792x_mutex_release(dev);
> > @@ -1293,12 +1338,12 @@ mt7925_mlo_pm_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
> > if (mvif->mlo_pm_state != MT792x_MLO_CHANGED_PS)
> > return;
> >
> > - 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);
> > }
> >
> > void mt7925_mlo_pm_work(struct work_struct *work)
> > @@ -1307,9 +1352,11 @@ void mt7925_mlo_pm_work(struct work_struct *work)
> > mlo_pm_work.work);
> > struct ieee80211_hw *hw = mt76_hw(dev);
> >
> > + mt792x_mutex_acquire(dev);
> > ieee80211_iterate_active_interfaces(hw,
> > IEEE80211_IFACE_ITER_RESUME_ALL,
> > mt7925_mlo_pm_iter, dev);
> > + mt792x_mutex_release(dev);
> > }
> >
> > static bool is_valid_alpha2(const char *alpha2)
> > @@ -1645,6 +1692,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);
> > }
> > }
> > @@ -1706,6 +1755,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;
> >
> > @@ -1876,6 +1928,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);
> > }
> > }
> > @@ -1891,6 +1945,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);
> > }
> > }
> > @@ -1912,7 +1968,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);
> >
> > @@ -2033,6 +2094,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);
> >
> > @@ -2113,9 +2179,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);
> > @@ -2144,6 +2215,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 8eda407e4135..cf38e36790e7 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
> > @@ -1722,6 +1722,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;
> >
> > @@ -1800,6 +1804,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;
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> > index 8eb1fe1082d1..b6c90c5f7e91 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> > @@ -454,7 +454,9 @@ static int mt7925_pci_suspend(struct device *device)
> > cancel_delayed_work_sync(&pm->ps_work);
> > cancel_work_sync(&pm->wake_work);
> >
> > + mt792x_mutex_acquire(dev);
> > mt7925_roc_abort_sync(dev);
> > + mt792x_mutex_release(dev);
> >
> > err = mt792x_mcu_drv_pmctrl(dev);
> > if (err < 0)
> > @@ -581,10 +583,12 @@ static int _mt7925_pci_resume(struct device *device, bool restore)
> > }
> >
> > /* restore previous ds setting */
> > + mt792x_mutex_acquire(dev);
> > if (!pm->ds_enable)
> > mt7925_mcu_set_deep_sleep(dev, false);
> >
> > mt7925_regd_update(dev);
> > + mt792x_mutex_release(dev);
> > failed:
> > pm->suspended = false;
> >
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
> > index 9cad572c34a3..0170a23b0529 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);
> >
> >
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2026-01-03 19:11 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-31 5:29 [PATCH] wifi: mt76: mt7925: fix NULL pointer dereference in vif iteration loops Zac Bowling
2025-12-31 22:37 ` [PATCH] wifi: mt76: mt7925: fix missing mutex protection in reset and ROC abort paths Zac Bowling
2026-01-01 0:22 ` [PATCH 2/3] wifi: mt76: mt7925: fix missing mutex protection in reset and ROC abort Zac Bowling
2026-01-01 0:23 ` [PATCH 3/3] wifi: mt76: mt7925: fix missing mutex protection in runtime PM and MLO PM Zac Bowling
2026-01-01 0:41 ` Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add NULL checks in MCU STA TLV functions Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add NULL checks for link_conf and mlink in main.c Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add NULL checks in MLO link and chanctx functions Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add error handling for AMPDU MCU commands Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add error handling for BSS info MCU command in sta_add Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add error handling for BSS info in key setup Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7921: fix missing mutex protection in multiple paths Zac Bowling
2026-01-01 6:25 ` [PATCH] wifi: mt76: mt7925: add lockdep assertions for mutex verification Zac Bowling
2026-01-02 20:03 ` [PATCH v2 0/6] wifi: mt76: mt7925/mt792x: additional stability fixes Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: fix key removal failure during MLO roaming Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: fix kernel warning in MLO ROC setup when channel not configured Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: add NULL checks for MLO link pointers in MCU functions Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt792x: fix firmware reload failure after previous load crash Zac Bowling
2026-01-03 6:46 ` Sean Wang
2026-01-03 18:42 ` Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: add mutex protection in resume path Zac Bowling
2026-01-02 20:03 ` [PATCH] wifi: mt76: mt7925: add NULL checks and error handling for MCU calls Zac Bowling
2026-01-02 20:05 ` [PATCH] wifi: mt76: mt7925: comprehensive stability fixes Zac Bowling
2026-01-03 6:25 ` Sean Wang
2026-01-03 19:11 ` Zac Bowling
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).