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 --- 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