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

Sean Wang sean.wang at kernel.org
Thu Dec 11 15:34:09 PST 2025


Hi Leon,

Nice catch! But I think this approach can still lead to deadlock.
If both workers fire at the same time, they may both see other == 0,
both set their own state to 1, both start running, and then both try
to call cancel_delayed_work_sync() on each other.
Then since atomic_set/atomic_read are weakly ordered, we don’t really
get proper exclusivity on SMP without something stronger.
And given that ps_work is a low-level piece used all over the place,
blocking on it isn’t ideal. A non-blocking cancel avoids the "both
wait on each other" situation.

So I’d suggest switching to:

@@ -375,7 +378,8 @@ void mt792x_pm_power_save_work(struct work_struct *work)
        }

        if (!mt792x_mcu_fw_pmctrl(dev)) {
-               cancel_delayed_work_sync(&mphy->mac_work);
+               cancel_delayed_work(&mphy->mac_work);
                return;
        }

to break the waiting loop.

This should make things safer without changing the overall flow, and
keeps the code easier to read and maintain.
Any pending mac work will get canceled, and if a mac work is already
running, it will schedule another ps_work once it completes.

                  Sean

On Thu, Dec 11, 2025 at 6:44 AM Leon Yen <leon.yen at mediatek.com> wrote:
>
> 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