mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Viktor Pashaiev <w.paszajew@gmail.com>
To: Felix Fietkau <nbd@nbd.name>, Lorenzo Bianconi <lorenzo@kernel.org>
Cc: linux-wireless@vger.kernel.org,
	Ryder Lee <ryder.lee@mediatek.com>,
	Shayne Chen <shayne.chen@mediatek.com>,
	Sean Wang <sean.wang@mediatek.com>,
	linux-kernel@vger.kernel.org,
	Piotr Kalinski <callynsky.linux@proton.me>,
	Viktor Pashaiev <w.paszajew@gmail.com>
Subject: [PATCH] wifi: mt76: harden mt792x USB disconnect and prevent abort_roc deadlock
Date: Sun, 27 Sep 2026 18:04:15 +0000	[thread overview]
Message-ID: <20260927180415.1498019-1-w.paszajew@gmail.com> (raw)

When a MediaTek MT7921/MT7925 USB wireless device encounters
communication stalls (such as -110 ETIMEDOUT vendor requests on
MT_MIB_SDR9 during MAC watchdog survey), the USB core detects an
unresponsive bus and schedules an asynchronous device reset
(__usb_queue_reset_device -> usb_reset_device). If the reset cannot
recover the endpoint, usbcore initiates a forced interface unbind,
invoking mt792xu_disconnect().

In mt792xu_disconnect(), mt76_unregister_device() was previously
called without quiescing asynchronous workers or asserting
MT76_REMOVED:
1. mt76_unregister_device() invokes ieee80211_unregister_hw() ->
   dev_close(), which acquires rtnl_lock.
2. Under rtnl_lock, ieee80211_set_disassoc() triggers
   drv_mgd_complete_tx() -> mt7921_abort_roc(), which attempts to
   acquire dev->mt76.mutex.
3. Concurrently, mt792x_mac_work is running on the workqueue holding
   dev->mt76.mutex while looping on 3-second USB timeout requests.

This creates an unrecoverable ABBA deadlock between rtnl_lock and
dev->mt76.mutex. NetworkManager, ip link, and shutdown/reboot scripts
hang indefinitely in D state (uninterruptible sleep), requiring a
hard power-off.

Furthermore, during unbind or rmmod teardown, in-progress reset_work
(mt7921_mac_reset_work / mt7925_mac_reset_work) could race with
disconnect, waking queues or iterating active interfaces on a
removed adapter.

Fix this by:
1. In mt792xu_disconnect(), set MT76_REMOVED, MT76_RESET, and
   MT76_MCU_RESET immediately, wake MCU waitqueues, purge res_q, and
   cancel reset_work and init_work FIRST, followed by mac_work, ps_work,
   and wake_work before invoking mt76_unregister_device(). Setting
   MT76_REMOVED causes all pending and future USB register requests to
   fail immediately with -EIO. Also drop redundant mt76_worker_disable()
   to prevent WARN_ON_ONCE in kthread_park().
2. In mt7921_mac_reset_work() and mt7925_mac_reset_work(), check
   MT76_REMOVED. If set, abort reset immediately, preventing queue
   waking and interface iterations on a removed device.
3. In mt7921_abort_roc() and mt7925_abort_roc(), check MT76_REMOVED.
   If set, clear MT76_STATE_ROC and return immediately without waiting
   on dev->mt76.mutex or sending MCU commands over a disconnected bus.
4. In mt7921_mcu_parse_response() and mt7925_mcu_parse_response(),
   suppress timeout error logging and mt792x_reset() when MT76_MCU_RESET
   or MT76_REMOVED is set. This shared response parsing covers PCIe,
   SDIO, and USB buses, eliminating spurious teardown noise and
   preventing reset_work from being re-queued onto the workqueue after
   disconnect has cancelled it and shortly before mt76_free_device()
   destroys it.

Closes: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2167595
Tested-by: Piotr Kalinski <callynsky.linux@proton.me>
Signed-off-by: Viktor Pashaiev <w.paszajew@gmail.com>
---
 drivers/net/wireless/mediatek/mt76/mt7921/mac.c  | 12 +++++++++++-
 drivers/net/wireless/mediatek/mt76/mt7921/main.c |  5 +++++
 drivers/net/wireless/mediatek/mt76/mt7921/mcu.c  | 11 ++++++++---
 drivers/net/wireless/mediatek/mt76/mt7925/mac.c  |  3 +++
 drivers/net/wireless/mediatek/mt76/mt7925/main.c |  5 +++++
 drivers/net/wireless/mediatek/mt76/mt7925/mcu.c  | 10 ++++++++--
 drivers/net/wireless/mediatek/mt76/mt792x_usb.c  | 10 +++++-----
 7 files changed, 45 insertions(+), 11 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
index e69978184f..7a43472db5 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
@@ -664,6 +664,9 @@ void mt7921_mac_reset_work(struct work_struct *work)
 	struct mt76_connac_pm *pm = &dev->pm;
 	int i, ret;
 
+	if (test_bit(MT76_REMOVED, &dev->mphy.state))
+		return;
+
 	dev_dbg(dev->mt76.dev, "chip reset\n");
 	set_bit(MT76_RESET, &dev->mphy.state);
 	dev->hw_full_reset = true;
@@ -674,6 +677,9 @@ void mt7921_mac_reset_work(struct work_struct *work)
 	cancel_work_sync(&pm->wake_work);
 
 	for (i = 0; i < 10; i++) {
+		if (test_bit(MT76_REMOVED, &dev->mphy.state))
+			goto out;
+
 		if (atomic_read(&dev->mt76.bus_hung))
 			return;
 
@@ -700,9 +706,13 @@ void mt7921_mac_reset_work(struct work_struct *work)
 		ieee80211_scan_completed(dev->mphy.hw, &info);
 	}
 
+out:
 	dev->hw_full_reset = false;
-	clear_bit(MT76_RESET, &dev->mphy.state);
 	pm->suspended = false;
+	if (test_bit(MT76_REMOVED, &dev->mphy.state))
+		return;
+
+	clear_bit(MT76_RESET, &dev->mphy.state);
 	ieee80211_wake_queues(hw);
 	ieee80211_iterate_active_interfaces(hw,
 					    IEEE80211_IFACE_ITER_RESUME_ALL,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
index 68a059504e..f189a8a13b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -410,6 +410,11 @@ static int mt7921_abort_roc(struct mt792x_phy *phy, struct mt792x_vif *vif)
 	timer_delete_sync(&phy->roc_timer);
 	cancel_work_sync(&phy->roc_work);
 
+	if (test_bit(MT76_REMOVED, &phy->mt76->state)) {
+		clear_bit(MT76_STATE_ROC, &phy->mt76->state);
+		return 0;
+	}
+
 	mt792x_mutex_acquire(phy->dev);
 	if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
 		err = mt7921_mcu_abort_roc(phy, vif, phy->roc_token_id);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
index 40546005c7..a1dd9d83fb 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
@@ -20,9 +20,14 @@ int mt7921_mcu_parse_response(struct mt76_dev *mdev, int cmd,
 	int ret = 0;
 
 	if (!skb) {
-		dev_err(mdev->dev, "Message %08x (seq %d) timeout\n",
-			cmd, seq);
-		mt792x_reset(mdev);
+		struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
+
+		if (!test_bit(MT76_MCU_RESET, &dev->mphy.state) &&
+		    !test_bit(MT76_REMOVED, &dev->mphy.state)) {
+			dev_err(mdev->dev, "Message %08x (seq %d) timeout\n",
+				cmd, seq);
+			mt792x_reset(mdev);
+		}
 
 		return -ETIMEDOUT;
 	}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index 101f571b02..a48082ac09 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
@@ -1516,6 +1516,9 @@ void mt7925_mac_reset_work(struct work_struct *work)
 	struct mt76_connac_pm *pm = &dev->pm;
 	int i, ret;
 
+	if (test_bit(MT76_REMOVED, &dev->mphy.state))
+		return;
+
 	if (atomic_read(&dev->mt76.bus_hung))
 		return;
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 5993b31e1a..c88219dd0a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -556,6 +556,11 @@ static int mt7925_abort_roc(struct mt792x_phy *phy,
 	timer_delete_sync(&phy->roc_timer);
 	cancel_work_sync(&phy->roc_work);
 
+	if (test_bit(MT76_REMOVED, &phy->mt76->state)) {
+		clear_bit(MT76_STATE_ROC, &phy->mt76->state);
+		return 0;
+	}
+
 	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);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index fa29c486a4..211118f416 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -27,8 +27,14 @@ int mt7925_mcu_parse_response(struct mt76_dev *mdev, int cmd,
 	int ret = 0;
 
 	if (!skb) {
-		dev_err(mdev->dev, "Message %08x (seq %d) timeout\n", cmd, seq);
-		mt792x_reset(mdev);
+		struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
+
+		if (!test_bit(MT76_MCU_RESET, &dev->mphy.state) &&
+		    !test_bit(MT76_REMOVED, &dev->mphy.state)) {
+			dev_err(mdev->dev, "Message %08x (seq %d) timeout\n",
+				cmd, seq);
+			mt792x_reset(mdev);
+		}
 
 		return -ETIMEDOUT;
 	}
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
index 850f6b2cfc..b41903cfec 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
@@ -501,6 +501,7 @@ void mt792xu_disconnect(struct usb_interface *usb_intf)
 	if (!dev)
 		return;
 
+	set_bit(MT76_REMOVED, &dev->mphy.state);
 	set_bit(MT76_RESET, &dev->mphy.state);
 	set_bit(MT76_MCU_RESET, &dev->mphy.state);
 	clear_bit(MT76_STATE_RUNNING, &dev->mphy.state);
@@ -509,16 +510,15 @@ void mt792xu_disconnect(struct usb_interface *usb_intf)
 
 	cancel_work_sync(&dev->reset_work);
 	cancel_work_sync(&dev->init_work);
-	mt76_worker_disable(&dev->mt76.tx_worker);
+	cancel_delayed_work_sync(&dev->mphy.mac_work);
+	cancel_delayed_work_sync(&dev->pm.ps_work);
+	cancel_work_sync(&dev->pm.wake_work);
 	mt792xu_reset_work_cleanup(dev);
-	if (!test_bit(MT76_STATE_INITIALIZED, &dev->mphy.state)) {
-		set_bit(MT76_REMOVED, &dev->mphy.state);
+	if (!test_bit(MT76_STATE_INITIALIZED, &dev->mphy.state))
 		return;
-	}
 
 	mt76_unregister_device(&dev->mt76);
 	mt792xu_cleanup(dev);
-	set_bit(MT76_REMOVED, &dev->mphy.state);
 
 	usb_set_intfdata(usb_intf, NULL);
 
-- 
2.43.0


                 reply	other threads:[~2026-09-27 18:04 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260927180415.1498019-1-w.paszajew@gmail.com \
    --to=w.paszajew@gmail.com \
    --cc=callynsky.linux@proton.me \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-wireless@vger.kernel.org \
    --cc=lorenzo@kernel.org \
    --cc=nbd@nbd.name \
    --cc=ryder.lee@mediatek.com \
    --cc=sean.wang@mediatek.com \
    --cc=shayne.chen@mediatek.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®