git: 009d92b25f7c - main - mt76: mt7921: prevent PM from scheduling another delayed work on detach

From: Bjoern A. Zeeb <bz_at_FreeBSD.org>
Date: Sun, 14 Jun 2026 22:32:28 UTC
The branch main has been updated by bz:

URL: https://cgit.FreeBSD.org/src/commit/?id=009d92b25f7c2d6ddf3fb4202d0a6a4612a716f1

commit 009d92b25f7c2d6ddf3fb4202d0a6a4612a716f1
Author:     Bjoern A. Zeeb <bz@FreeBSD.org>
AuthorDate: 2026-06-14 16:37:10 +0000
Commit:     Bjoern A. Zeeb <bz@FreeBSD.org>
CommitDate: 2026-06-14 22:31:38 +0000

    mt76: mt7921: prevent PM from scheduling another delayed work on detach
    
    Amongst others mt76_connac_pm_unref() is calling mt76_connac_power_save_sched()
    which will (normaly) re-schedule the pm_work.
    In various parts we also cancel that work, also during PCI detach ("shutdown",
    "remove" in LinuxKPI terms).
    However we also keep calling mt76_connac_pm_unref() in the detach path and thus
    we get to a point where we re-scheduled the work but then the device goes away.
    At that point LinuxKPI delayed work has a callput pending which is embedded in
    the work structure (pm_work).  The moment we free the device that structure
    and callout is gone but the callout is still on the list and once that list
    is walked we panic.
    
    Simply prevent mt76_connac_power_save_sched() from getting to the point of
    possibly re-scheduling the pm_work by setting pm->enable to false in the
    beginning of the detach path.
    
    The are likely more paths which will need the same treatment as the code
    is by far anything from "symmetric" (that is the attach path is highly
    bus independent while the detach path is implemented per-bus).  Also
    other chipsets share the same "logical paths" with their own names, so
    they will need this too once we get to them.
    
    Sponsored by:   The FreeBSD Foundation
    MFC after:      3 days
---
 sys/contrib/dev/mediatek/mt76/mt7921/pci.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/sys/contrib/dev/mediatek/mt76/mt7921/pci.c b/sys/contrib/dev/mediatek/mt76/mt7921/pci.c
index 46b59c4d0390..b70335aba6bf 100644
--- a/sys/contrib/dev/mediatek/mt76/mt7921/pci.c
+++ b/sys/contrib/dev/mediatek/mt76/mt7921/pci.c
@@ -51,6 +51,17 @@ static void mt7921e_unregister_device(struct mt792x_dev *dev)
 	if (dev->phy.chip_cap & MT792x_CHIP_CAP_WF_RF_PIN_CTRL_EVT_EN)
 		wiphy_rfkill_stop_polling(hw->wiphy);
 
+#if defined(__FreeBSD__)
+	/*
+	 * Prevent scheduling ps_work again in mt76_connac_power_save_sched().
+	 * Otherwise upon shutdown we may have delayed work pending, which on
+	 * FreeBSD means there is a callout running, but the ps_work will be
+	 * freed along with the mt792x_dev and so there is a callout on a list
+	 * which no longer exists.
+	 */
+	pm->enable = false;
+#endif
+
 	cancel_work_sync(&dev->init_work);
 	mt76_unregister_device(&dev->mt76);
 	mt76_for_each_q_rx(&dev->mt76, i)