From: Mika Westerberg <mika.westerberg@linux.intel.com>
To: linux-usb@vger.kernel.org
Cc: Yehezkel Bernat <YehezkelShB@gmail.com>,
Lukas Wunner <lukas@wunner.de>,
Andreas Noever <andreas.noever@gmail.com>,
Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>,
Gil Fine <gil.fine@linux.intel.com>,
Mika Westerberg <mika.westerberg@linux.intel.com>
Subject: [PATCH 11/12] thunderbolt: Remove XDomain from the bus without holding tb->lock
Date: Mon, 27 Apr 2026 10:11:08 +0200 [thread overview]
Message-ID: <20260427081109.2337731-12-mika.westerberg@linux.intel.com> (raw)
In-Reply-To: <20260427081109.2337731-1-mika.westerberg@linux.intel.com>
Currently we call device_unregister() for services and the XDomain
itself with tb->lock held. This prevents the service drivers from
calling any functions that may take it. For this reason separate
removing the XDomain from the topology data structures (where we need
the lock) from unregistering the device from the bus (where remove
callbacks of the drivers are being called).
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
drivers/thunderbolt/debugfs.c | 2 ++
drivers/thunderbolt/domain.c | 30 ++++++++++++++++++
drivers/thunderbolt/icm.c | 5 +++
drivers/thunderbolt/switch.c | 14 +++++++++
drivers/thunderbolt/tb.c | 59 +++++++++++++++++------------------
drivers/thunderbolt/tb.h | 2 ++
drivers/thunderbolt/xdomain.c | 53 +++++++++++++++++++------------
7 files changed, 115 insertions(+), 50 deletions(-)
diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c
index 042f6a0d0f7f..09f189d7015a 100644
--- a/drivers/thunderbolt/debugfs.c
+++ b/drivers/thunderbolt/debugfs.c
@@ -1780,6 +1780,8 @@ static void margining_port_remove(struct tb_port *port)
if (!port->usb4)
return;
+ if (!port->usb4->margining)
+ return;
snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
parent = debugfs_lookup(dir_name, port->sw->debugfs_dir);
diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c
index df4d7dd45adf..d83719a37b4c 100644
--- a/drivers/thunderbolt/domain.c
+++ b/drivers/thunderbolt/domain.c
@@ -853,6 +853,36 @@ int tb_domain_disconnect_all_paths(struct tb *tb)
return bus_for_each_dev(&tb_bus_type, NULL, tb, disconnect_xdomain);
}
+struct unregister_context {
+ const struct tb *tb;
+ int n;
+};
+
+static int unregister_unplugged_xdomain(struct device *dev, void *data)
+{
+ struct unregister_context *ctx = data;
+ struct tb_xdomain *xd;
+
+ xd = tb_to_xdomain(dev);
+ if (xd && xd->tb == ctx->tb && xd->is_unplugged) {
+ tb_xdomain_unregister(xd);
+ ctx->n++;
+ }
+ return 0;
+}
+
+int tb_domain_unregister_unplugged_xdomains(struct tb *tb)
+{
+ struct unregister_context ctx;
+
+ ctx.tb = tb_domain_get(tb);
+ ctx.n = 0;
+ bus_for_each_dev(&tb_bus_type, NULL, &ctx, unregister_unplugged_xdomain);
+ tb_domain_put(tb);
+
+ return ctx.n;
+}
+
int tb_domain_init(void)
{
int ret;
diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c
index 9d95bf3ab44c..2f93a7bccad5 100644
--- a/drivers/thunderbolt/icm.c
+++ b/drivers/thunderbolt/icm.c
@@ -738,6 +738,7 @@ static void remove_xdomain(struct tb_xdomain *xd)
sw = tb_to_switch(xd->dev.parent);
tb_port_at(xd->route, sw)->xdomain = NULL;
+ xd->is_unplugged = true;
tb_xdomain_remove(xd);
}
@@ -1762,6 +1763,8 @@ static void icm_handle_notification(struct work_struct *work)
kfree(n->pkg);
kfree(n);
+
+ tb_domain_unregister_unplugged_xdomains(tb);
}
static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
@@ -2112,6 +2115,8 @@ static void icm_rescan_work(struct work_struct *work)
if (tb->root_switch)
icm_free_unplugged_children(tb->root_switch);
mutex_unlock(&tb->lock);
+
+ tb_domain_unregister_unplugged_xdomains(tb);
}
static void icm_complete(struct tb *tb)
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index c2ad58b19e7b..bfcab98faf4b 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -3625,6 +3625,20 @@ int tb_switch_resume(struct tb_switch *sw, bool runtime)
tb_port_warn(port,
"lost during suspend, disconnecting\n");
tb_sw_set_unplugged(port->remote->sw);
+ } else if (port->xdomain) {
+ /*
+ * If the user replaced the XDomain with
+ * another router, this will succeed in
+ * which case we must remove the XDomain
+ * before adding the new router.
+ */
+ err = tb_cfg_get_upstream_port(sw->tb->ctl,
+ port->xdomain->route);
+ if (err > 0) {
+ tb_port_warn(port,
+ "XDomain was disconnected\n");
+ port->xdomain->is_unplugged = true;
+ }
}
}
}
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 677877baae63..a9d26a2ec259 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -2524,6 +2524,8 @@ static void tb_handle_hotplug(struct work_struct *work)
out:
mutex_unlock(&tb->lock);
+ tb_domain_unregister_unplugged_xdomains(tb);
+
pm_runtime_mark_last_busy(&tb->dev);
pm_runtime_put_autosuspend(&tb->dev);
@@ -3114,6 +3116,24 @@ static void tb_restore_children(struct tb_switch *sw)
}
}
+static void tb_free_unplugged_xdomains(struct tb_switch *sw)
+{
+ struct tb_port *port;
+
+ tb_switch_for_each_port(sw, port) {
+ if (tb_is_upstream_port(port))
+ continue;
+ if (port->xdomain && port->xdomain->is_unplugged) {
+ tb_retimer_remove_all(port);
+ tb_xdomain_remove(port->xdomain);
+ tb_port_unconfigure_xdomain(port);
+ port->xdomain = NULL;
+ } else if (port->remote) {
+ tb_free_unplugged_xdomains(port->remote->sw);
+ }
+ }
+}
+
static int tb_resume_noirq(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
@@ -3133,6 +3153,7 @@ static int tb_resume_noirq(struct tb *tb)
tb_switch_resume(tb->root_switch, false);
tb_free_invalid_tunnels(tb);
tb_free_unplugged_children(tb->root_switch);
+ tb_free_unplugged_xdomains(tb->root_switch);
tb_restore_children(tb->root_switch);
/*
@@ -3175,28 +3196,6 @@ static int tb_resume_noirq(struct tb *tb)
return 0;
}
-static int tb_free_unplugged_xdomains(struct tb_switch *sw)
-{
- struct tb_port *port;
- int ret = 0;
-
- tb_switch_for_each_port(sw, port) {
- if (tb_is_upstream_port(port))
- continue;
- if (port->xdomain && port->xdomain->is_unplugged) {
- tb_retimer_remove_all(port);
- tb_xdomain_remove(port->xdomain);
- tb_port_unconfigure_xdomain(port);
- port->xdomain = NULL;
- ret++;
- } else if (port->remote) {
- ret += tb_free_unplugged_xdomains(port->remote->sw);
- }
- }
-
- return ret;
-}
-
static int tb_freeze_noirq(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
@@ -3216,14 +3215,14 @@ static int tb_thaw_noirq(struct tb *tb)
static void tb_complete(struct tb *tb)
{
/*
- * Release any unplugged XDomains and if there is a case where
+ * Unregister unplugged XDomains and if there is a case where
* another domain is swapped in place of unplugged XDomain we
* need to run another rescan.
*/
- mutex_lock(&tb->lock);
- if (tb_free_unplugged_xdomains(tb->root_switch))
- tb_scan_switch(tb->root_switch);
- mutex_unlock(&tb->lock);
+ if (tb_domain_unregister_unplugged_xdomains(tb)) {
+ scoped_guard(mutex, &tb->lock)
+ tb_scan_switch(tb->root_switch);
+ }
}
static int tb_runtime_suspend(struct tb *tb)
@@ -3250,11 +3249,11 @@ static void tb_remove_work(struct work_struct *work)
struct tb *tb = tcm_to_tb(tcm);
mutex_lock(&tb->lock);
- if (tb->root_switch) {
+ if (tb->root_switch)
tb_free_unplugged_children(tb->root_switch);
- tb_free_unplugged_xdomains(tb->root_switch);
- }
mutex_unlock(&tb->lock);
+
+ tb_free_unplugged_xdomains(tb->root_switch);
}
static int tb_runtime_resume(struct tb *tb)
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 217c3114bec8..229b9e7961fb 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -793,6 +793,7 @@ 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);
int tb_domain_disconnect_all_paths(struct tb *tb);
+int tb_domain_unregister_unplugged_xdomains(struct tb *tb);
static inline struct tb *tb_domain_get(struct tb *tb)
{
@@ -1263,6 +1264,7 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
const uuid_t *remote_uuid);
void tb_xdomain_add(struct tb_xdomain *xd);
void tb_xdomain_remove(struct tb_xdomain *xd);
+void tb_xdomain_unregister(struct tb_xdomain *xd);
struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
u8 depth);
diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index 76e1902d18f3..9a30fe36c4be 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -2105,40 +2105,53 @@ static int unregister_service(struct device *dev, void *data)
}
/**
- * tb_xdomain_remove() - Remove XDomain from the bus
+ * tb_xdomain_remove() - Remove XDomain
* @xd: XDomain to remove
*
- * This will stop all ongoing configuration work and remove the XDomain
- * along with any services from the bus. When the last reference to @xd
- * is released the object will be released as well.
+ * This will stop all ongoing configuration work. XDomain is not removed
+ * from the bus if it was added. That needs to be done separately by
+ * calling tb_xdomain_unregister().
+ *
+ * Called with @tb->lock held.
*/
void tb_xdomain_remove(struct tb_xdomain *xd)
{
tb_xdomain_debugfs_remove(xd);
-
stop_handshake(xd);
-
- device_for_each_child_reverse(&xd->dev, xd, unregister_service);
-
tb_xdomain_link_exit(xd);
- /*
- * Undo runtime PM here explicitly because it is possible that
- * the XDomain was never added to the bus and thus device_del()
- * is not called for it (device_del() would handle this otherwise).
- */
- pm_runtime_disable(&xd->dev);
- pm_runtime_put_noidle(&xd->dev);
- pm_runtime_set_suspended(&xd->dev);
-
if (!device_is_registered(&xd->dev)) {
+ /*
+ * Undo runtime PM here explicitly because it is
+ * possible that the XDomain was never added to the bus
+ * and thus device_del() is not called for it
+ * (device_del() would handle this otherwise).
+ */
+ pm_runtime_disable(&xd->dev);
+ pm_runtime_put_noidle(&xd->dev);
+ pm_runtime_set_suspended(&xd->dev);
put_device(&xd->dev);
- } else {
- dev_info(&xd->dev, "host disconnected\n");
- device_unregister(&xd->dev);
}
}
+/**
+ * tb_xdomain_unregister() - Unregister XDomain
+ * @xd: XDomain to unregister
+ *
+ * This will unregister the XDomain along with any services from the
+ * bus. When the last reference to @xd is released the object will be
+ * released as well.
+ */
+void tb_xdomain_unregister(struct tb_xdomain *xd)
+{
+ lockdep_assert_not_held(&xd->tb->lock);
+
+ device_for_each_child_reverse(&xd->dev, xd, unregister_service);
+
+ dev_info(&xd->dev, "host disconnected\n");
+ device_unregister(&xd->dev);
+}
+
/**
* tb_xdomain_lane_bonding_enable() - Enable lane bonding on XDomain
* @xd: XDomain connection
--
2.50.1
next prev parent reply other threads:[~2026-04-27 8:11 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-27 8:10 [PATCH 00/12] thunderbolt: Improvements to XDomain handling Mika Westerberg
2026-04-27 8:10 ` [PATCH 01/12] thunderbolt: Avoid reserved fields in path config space for USB4 routers Mika Westerberg
2026-04-27 8:10 ` [PATCH 02/12] thunderbolt: Don't disable lane adapter if XDomain lane bonding isn't possible Mika Westerberg
2026-04-27 8:11 ` [PATCH 03/12] thunderbolt: Make XDomain lane bonding comply with the USB4 v2 spec Mika Westerberg
2026-04-27 8:11 ` [PATCH 04/12] thunderbolt: Keep the domain reference while processing hotplug Mika Westerberg
2026-04-27 8:11 ` [PATCH 05/12] thunderbolt: Release request if tb_cfg_request() fails in __tb_xdomain_response() Mika Westerberg
2026-04-27 8:11 ` [PATCH 06/12] thunderbolt: Set tb->root_switch to NULL when domain is stopped Mika Westerberg
2026-04-27 8:11 ` [PATCH 07/12] thunderbolt: Wait for tb_domain_release() to complete when driver is removed Mika Westerberg
2026-04-27 8:11 ` [PATCH 08/12] thunderbolt: Keep XDomain reference during the lifetime of a service Mika Westerberg
2026-04-27 8:11 ` [PATCH 09/12] thunderbolt: dma_test: No need to store debugfs directory pointer Mika Westerberg
2026-04-27 8:11 ` [PATCH 10/12] thunderbolt: Remove service debugfs entries during unregister Mika Westerberg
2026-04-27 8:11 ` Mika Westerberg [this message]
2026-04-27 8:11 ` [PATCH 12/12] thunderbolt: Don't create multiple DMA tunnels on firmware connection manager 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=20260427081109.2337731-12-mika.westerberg@linux.intel.com \
--to=mika.westerberg@linux.intel.com \
--cc=YehezkelShB@gmail.com \
--cc=alan.borzeszkowski@linux.intel.com \
--cc=andreas.noever@gmail.com \
--cc=gil.fine@linux.intel.com \
--cc=linux-usb@vger.kernel.org \
--cc=lukas@wunner.de \
/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