From 053fd6171b7ba396f72e3e5ee39c1bcbf3c89854 Mon Sep 17 00:00:00 2001 From: Zac Bowling Date: Wed, 31 Dec 2025 16:51:17 -0800 Subject: [PATCH 1/3] wifi: mt76: mt7925: fix NULL pointer dereference in vif iteration Add NULL checks for bss_conf in all loops that iterate over valid_links and call mt792x_vif_to_bss_conf(). This prevents kernel panics when the link configuration in mac80211 is not yet set up even though the driver's valid_links bitmap has the link marked as valid. This can happen during HW reset when link state is inconsistent, or during MLO operations where the driver's link tracking is ahead of mac80211's BSS configuration. Reported-by: Zac Bowling Tested-by: Zac Bowling Signed-off-by: Zac Bowling --- mt7925/mac.c | 6 ++++++ mt7925/main.c | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/mt7925/mac.c b/mt7925/mac.c index fa75fb587..ed9b54036 100644 --- a/mt7925/mac.c +++ b/mt7925/mac.c @@ -1274,6 +1274,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/mt7925/main.c b/mt7925/main.c index 307850a58..519e0c00e 100644 --- a/mt7925/main.c +++ b/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); } } From 64e43636792ed0ef35db76d640e301590512fe93 Mon Sep 17 00:00:00 2001 From: Zac Bowling Date: Wed, 31 Dec 2025 16:51:29 -0800 Subject: [PATCH 2/3] wifi: mt76: mt7925: fix missing mutex protection in reset and ROC abort 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 - PCI suspend path: Wrap mt7925_roc_abort_sync() call with mutex Note: The mutex is added at the call site in pci.c rather than inside mt7925_roc_abort_sync() because this function is also called from the station remove path which already holds the mutex. Reported-by: Zac Bowling Tested-by: Zac Bowling Signed-off-by: Zac Bowling --- mt7925/mac.c | 2 ++ mt7925/pci.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/mt7925/mac.c b/mt7925/mac.c index ed9b54036..77e8c7861 100644 --- a/mt7925/mac.c +++ b/mt7925/mac.c @@ -1334,9 +1334,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/mt7925/pci.c b/mt7925/pci.c index c4161754c..e9d62c6ae 100644 --- a/mt7925/pci.c +++ b/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) From d597c2523d6e1ba861ae7bc7522a0c53d84209d7 Mon Sep 17 00:00:00 2001 From: Zac Bowling Date: Wed, 31 Dec 2025 16:51:37 -0800 Subject: [PATCH 3/3] wifi: mt76: mt7925: fix missing mutex protection in runtime PM and MLO PM Two additional code paths iterate over active interfaces and call MCU functions without proper mutex protection: 1. mt7925_set_runtime_pm(): Called when runtime PM settings change. The ieee80211_iterate_active_interfaces() call invokes mt7925_pm_interface_iter() which calls mt7925_mcu_set_beacon_filter(). 2. mt7925_mlo_pm_work(): Workqueue function for MLO power management. The iterator callback mt7925_mlo_pm_iter() calls mt7925_mcu_uni_bss_ps(). Add mutex protection around the iterate calls in both functions. For mt7925_mlo_pm_iter(), move the mutex from inside the callback to the caller (mt7925_mlo_pm_work) for consistency with other patterns. This matches the pattern used in the older mt7615 driver and other wireless drivers like iwlwifi. Reported-by: Zac Bowling Tested-by: Zac Bowling Signed-off-by: Zac Bowling --- mt7925/main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mt7925/main.c b/mt7925/main.c index 519e0c00e..ed4a57fab 100644 --- a/mt7925/main.c +++ b/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)