From: <juan.martinez@amd.com>
To: <mika.westerberg@linux.intel.com>
Cc: <mario.limonciello@amd.com>, <juan.martinez@amd.com>,
<westeri@kernel.org>, <andreas.noever@gmail.com>,
<YehezkelShB@gmail.com>, <Basavaraj.Natikar@amd.com>,
<Sanath.S@amd.com>, <linux-usb@vger.kernel.org>,
<linux-kernel@vger.kernel.org>
Subject: [PATCH v5] thunderbolt: Fix tb->lock deadlock during hot-unplug on AMD USB4 routers
Date: Mon, 31 Aug 2026 11:16:10 -0500 [thread overview]
Message-ID: <20260831161610.1322731-1-juan.martinez@amd.com> (raw)
In-Reply-To: <20260831130638.GK124825@black.igk.intel.com>
From: Juan Martinez <juan.martinez@amd.com>
Commit f1de1fc5f632 ("thunderbolt: Add quirk to reset host interface on
DMA path teardown for AMD USB4 routers") introduced a deadlock when
physically unplugging a Thunderbolt cable on AMD systems.
The problem occurs because tb_handle_hotplug() holds tb->lock while
processing the unplug event. When it removes the XDomain services,
tbnet_remove() calls tb_xdomain_disable_paths() which eventually calls
tb_domain_reset_interface(). That function tries to acquire tb->lock
via guard(mutex), but the hotplug worker already holds it, causing a
self-deadlock.
The deadlock manifests as a complete network hang because
tb_handle_hotplug() holds RTNL while waiting on its own mutex, blocking
all network operations system-wide.
The existing code already handles this scenario partially: when
xd->is_unplugged is true, tb_disconnect_xdomain_paths() intentionally
skips the DMA teardown because the hotplug handler tears down the DMA
tunnels itself. However, the reset was still being called
unconditionally.
Fix this by splitting tb_domain_reset_interface() into a locked inner
function and a locking wrapper. Skip the reset from
tb_domain_disconnect_xdomain_paths() when xd->is_unplugged is true, and
instead reset the interface after the hotplug handler tears down the DMA
tunnel while already holding tb->lock.
Handle both unplug topologies: reset after the direct XDomain teardown,
and after invalid DMA tunnels are removed when an upstream router and its
downstream XDomain are unplugged together.
This preserves the reset behavior for normal shutdown paths while
avoiding the deadlock during physical cable unplug.
Fixes: f1de1fc5f632 ("thunderbolt: Add quirk to reset host interface on DMA path teardown for AMD USB4 routers")
Signed-off-by: Juan Martinez <juan.martinez@amd.com>
---
Notes (v5):
Changes in v5:
- Rebase on the current thunderbolt/next branch.
- Reset the host interface after invalid DMA tunnels are removed, covering
XDomains below an unplugged router.
drivers/thunderbolt/domain.c | 20 +++++++++++++++++---
drivers/thunderbolt/tb.c | 10 +++++++++-
drivers/thunderbolt/tb.h | 1 +
3 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c
index 12c88509a54f..4cef9f4de523 100644
--- a/drivers/thunderbolt/domain.c
+++ b/drivers/thunderbolt/domain.c
@@ -788,14 +788,22 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
transmit_ring, receive_path, receive_ring);
}
-static void tb_domain_reset_interface(struct tb *tb)
+/*
+ * __tb_domain_reset_interface_locked - Reset host interface (lock held)
+ *
+ * Caller must hold tb->lock. Used by hotplug path where lock is already held.
+ */
+void __tb_domain_reset_interface_locked(struct tb *tb)
{
struct tb_nhi *nhi = tb->nhi;
+ lockdep_assert_held(&tb->lock);
+
if (!nhi->ops->reset_interface)
return;
- guard(mutex)(&tb->lock);
+ if (!(nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN))
+ return;
/* The reset clears the ring state so stop the control channel */
tb_ctl_stop(tb->ctl);
@@ -803,6 +811,12 @@ static void tb_domain_reset_interface(struct tb *tb)
tb_ctl_start(tb->ctl);
}
+static void tb_domain_reset_interface(struct tb *tb)
+{
+ guard(mutex)(&tb->lock);
+ __tb_domain_reset_interface_locked(tb);
+}
+
/**
* tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain
* @tb: Domain disabling the DMA paths
@@ -835,7 +849,7 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
if (ret)
return ret;
- if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN)
+ if (!xd->is_unplugged)
tb_domain_reset_interface(tb);
return 0;
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 47753a5c0f2e..9300cdae10b1 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -1776,12 +1776,19 @@ static void tb_free_invalid_tunnels(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
struct tb_tunnel *tunnel;
+ bool reset = false;
struct tb_tunnel *n;
list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) {
- if (tb_tunnel_is_invalid(tunnel))
+ if (tb_tunnel_is_invalid(tunnel)) {
+ if (tb_tunnel_is_dma(tunnel))
+ reset = true;
tb_deactivate_and_free_tunnel(tunnel);
+ }
}
+
+ if (reset)
+ __tb_domain_reset_interface_locked(tb);
}
/*
@@ -2489,6 +2496,7 @@ static void tb_handle_hotplug(struct work_struct *work)
tb_xdomain_remove(xd);
port->xdomain = NULL;
__tb_disconnect_xdomain_paths(tb, xd, -1, -1, -1, -1);
+ __tb_domain_reset_interface_locked(tb);
tb_xdomain_put(xd);
tb_port_unconfigure_xdomain(port);
} else if (tb_port_is_dpout(port) || tb_port_is_dpin(port)) {
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index c112954ce3fd..5bb448a71407 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -792,6 +792,7 @@ int tb_domain_disconnect_pcie_paths(struct tb *tb);
int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
int transmit_path, int transmit_ring,
int receive_path, int receive_ring);
+void __tb_domain_reset_interface_locked(struct tb *tb);
int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
int transmit_path, int transmit_ring,
int receive_path, int receive_ring);
base-commit: 48e989e33b715611438ce4b8d6ff712d4becd84f
--
2.43.0
next prev parent reply other threads:[~2026-08-31 16:16 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-25 21:42 [PATCH v2] thunderbolt: Fix tb->lock deadlock during hot-unplug on AMD USB4 routers juan.martinez
2026-08-26 3:02 ` Mario Limonciello
2026-08-27 21:57 ` [PATCH v3] " Juan Martinez
2026-08-28 4:43 ` Mario Limonciello
2026-08-28 5:19 ` [PATCH v4] " Juan Martinez
2026-08-28 14:58 ` Mario Limonciello
2026-08-31 11:11 ` Mika Westerberg
2026-08-31 12:55 ` Mario Limonciello
2026-08-31 13:06 ` Mika Westerberg
2026-08-31 13:07 ` Mario Limonciello
2026-08-31 16:16 ` juan.martinez [this message]
2026-09-01 22:16 ` [PATCH v5] " Mario Limonciello
2026-09-02 5:48 ` Mika Westerberg
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=20260831161610.1322731-1-juan.martinez@amd.com \
--to=juan.martinez@amd.com \
--cc=Basavaraj.Natikar@amd.com \
--cc=Sanath.S@amd.com \
--cc=YehezkelShB@gmail.com \
--cc=andreas.noever@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=mario.limonciello@amd.com \
--cc=mika.westerberg@linux.intel.com \
--cc=westeri@kernel.org \
/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