[PATCH] wifi: mt76: mt792x: Fix a potential deadlock in high-load situations

Leon Yen leon.yen at mediatek.com
Thu Dec 11 04:43:44 PST 2025


A deadlock may occur between two works, ps_work and mac_work, if their work
functions run simultaneously as they attempt to cancel each other by
calling cancel_delayed_work_sync().

mt792x_mac_work() ->   ...  -> cancel_delayed_work_sync(&pm->ps_work);
mt792x_pm_power_save_work() -> cancel_delayed_work_sync(&mphy->mac_work);

In high-load situations, they are queued but may not have chance to be
executed until the CPUs are released. Once the CPUs are available, there
is a high possibility that the ps_work function and mac_work function will
be executed simultaneously, resulting in a deadlock.

This patch ensures that the ps_work function and mac_work function can run
exclusively by adding two flags to indicate their running status.The work
function will reschedule itself if the opposite is currently running.

Signed-off-by: Leon Yen <leon.yen at mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt76.h        |  1 +
 drivers/net/wireless/mediatek/mt76/mt76_connac.h |  1 +
 drivers/net/wireless/mediatek/mt76/mt792x_mac.c  | 13 +++++++++++++
 3 files changed, 15 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index d05e83ea1cac..0414a4898d80 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -892,6 +892,7 @@ struct mt76_phy {
 #endif
 
 	struct delayed_work mac_work;
+	atomic_t mac_work_running;
 	u8 mac_work_count;
 
 	struct {
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
index 813d61bffc2c..eefa0147f883 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
@@ -107,6 +107,7 @@ struct mt76_connac_pm {
 	struct mutex mutex;
 
 	struct delayed_work ps_work;
+	atomic_t ps_work_running;
 	unsigned long last_activity;
 	unsigned long idle_timeout;
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_mac.c b/drivers/net/wireless/mediatek/mt76/mt792x_mac.c
index 71dec93094eb..22345031e262 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_mac.c
@@ -15,6 +15,10 @@ void mt792x_mac_work(struct work_struct *work)
 					       mac_work.work);
 	phy = mphy->priv;
 
+	if (atomic_read(&phy->dev->pm.ps_work_running))
+		goto out;
+	atomic_set(&mphy->mac_work_running, 1);
+
 	mt792x_mutex_acquire(phy->dev);
 
 	mt76_update_survey(mphy);
@@ -27,8 +31,10 @@ void mt792x_mac_work(struct work_struct *work)
 	mt792x_mutex_release(phy->dev);
 
 	mt76_tx_status_check(mphy->dev, false);
+out:
 	ieee80211_queue_delayed_work(phy->mt76->hw, &mphy->mac_work,
 				     MT792x_WATCHDOG_TIME);
+	atomic_set(&mphy->mac_work_running, 0);
 }
 EXPORT_SYMBOL_GPL(mt792x_mac_work);
 
@@ -356,6 +362,11 @@ void mt792x_pm_power_save_work(struct work_struct *work)
 	mphy = dev->phy.mt76;
 
 	delta = dev->pm.idle_timeout;
+
+	if (atomic_read(&mphy->mac_work_running))
+		goto out;
+	atomic_set(&dev->pm.ps_work_running, 1);
+
 	if (test_bit(MT76_HW_SCANNING, &mphy->state) ||
 	    test_bit(MT76_HW_SCHED_SCANNING, &mphy->state) ||
 	    dev->fw_assert)
@@ -376,9 +387,11 @@ void mt792x_pm_power_save_work(struct work_struct *work)
 
 	if (!mt792x_mcu_fw_pmctrl(dev)) {
 		cancel_delayed_work_sync(&mphy->mac_work);
+		atomic_set(&dev->pm.ps_work_running, 0);
 		return;
 	}
 out:
 	queue_delayed_work(dev->mt76.wq, &dev->pm.ps_work, delta);
+	atomic_set(&dev->pm.ps_work_running, 0);
 }
 EXPORT_SYMBOL_GPL(mt792x_pm_power_save_work);
-- 
2.45.2




More information about the Linux-mediatek mailing list