Skip to content

Instantly share code, notes, and snippets.

@lostmsu
Created February 7, 2026 01:54
Show Gist options
  • Select an option

  • Save lostmsu/a0cdd213676223fc7669726b3a24d50a to your computer and use it in GitHub Desktop.

Select an option

Save lostmsu/a0cdd213676223fc7669726b3a24d50a to your computer and use it in GitHub Desktop.
MediaTek WiFi 7 MT7925E (PCIe) kernel driver deadlock patch by GPT-5.2
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -449,12 +449,16 @@
{
struct mt792x_phy *phy = &dev->phy;
+ WRITE_ONCE(phy->roc_abort, true);
timer_delete_sync(&phy->roc_timer);
cancel_work_sync(&phy->roc_work);
+ /* make sure roc_work didn't rearm the timer on lock contention */
+ timer_delete_sync(&phy->roc_timer);
if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
ieee80211_iterate_interfaces(mt76_hw(dev),
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7925_roc_iter, (void *)phy);
+ WRITE_ONCE(phy->roc_abort, false);
}
EXPORT_SYMBOL_GPL(mt7925_roc_abort_sync);
@@ -465,14 +469,40 @@
phy = (struct mt792x_phy *)container_of(work, struct mt792x_phy,
roc_work);
- if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
+ if (READ_ONCE(phy->roc_abort))
return;
- mt792x_mutex_acquire(phy->dev);
+ if (!test_bit(MT76_STATE_ROC, &phy->mt76->state))
+ return;
+
+ /*
+ * Avoid deadlocking with callers that cancel roc_work while holding
+ * dev->mt76.mutex (e.g. during station removal). If the mutex is held,
+ * reschedule via roc_timer for a short backoff instead of blocking.
+ */
+ if (!mutex_trylock(&phy->dev->mt76.mutex)) {
+ if (!READ_ONCE(phy->roc_abort)) {
+ unsigned long delay = max_t(unsigned long, 1, HZ / 50);
+
+ mod_timer(&phy->roc_timer, jiffies + delay);
+ }
+ return;
+ }
+
+ mt76_connac_pm_wake(&phy->dev->mt76.phy, &phy->dev->pm);
+
+ if (READ_ONCE(phy->roc_abort) ||
+ !test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state)) {
+ mt76_connac_power_save_sched(&phy->dev->mt76.phy, &phy->dev->pm);
+ mutex_unlock(&phy->dev->mt76.mutex);
+ return;
+ }
+
ieee80211_iterate_active_interfaces(phy->mt76->hw,
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7925_roc_iter, phy);
- mt792x_mutex_release(phy->dev);
+ mt76_connac_power_save_sched(&phy->dev->mt76.phy, &phy->dev->pm);
+ mutex_unlock(&phy->dev->mt76.mutex);
ieee80211_remain_on_channel_expired(phy->mt76->hw);
}
@@ -481,14 +511,17 @@
{
int err = 0;
+ WRITE_ONCE(phy->roc_abort, true);
timer_delete_sync(&phy->roc_timer);
cancel_work_sync(&phy->roc_work);
+ timer_delete_sync(&phy->roc_timer);
mt792x_mutex_acquire(phy->dev);
if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
err = mt7925_mcu_abort_roc(phy, mconf, phy->roc_token_id);
mt792x_mutex_release(phy->dev);
+ WRITE_ONCE(phy->roc_abort, false);
return err;
}
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -185,6 +185,7 @@
wait_queue_head_t roc_wait;
u8 roc_token_id;
bool roc_grant;
+ bool roc_abort;
};
struct mt792x_irq_map {
@lostmsu
Copy link
Author

lostmsu commented Feb 7, 2026

This is for kernel 6.18

On NixOS just add to your configuration.nix:

boot.kernelPatches = [
    {
      name = "mt7925-deadlock-fix";
      patch = ./mt7925-deadlock-fix.patch;
    }
  ];

Basically the problem was that the implementation of station disconnect took a lock on the device object and then tried to synchronously cancel pending work. But these work items might have already entered the path where they will need the same lock, creating a deadlock situation. The fix is to replace unconditional locking in the work items with try -> reschedule loop that stops when it sees the abort flag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment