public inbox for linux-usb@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/12] thunderbolt: Improvements to XDomain handling
@ 2026-04-27  8:10 Mika Westerberg
  2026-04-27  8:10 ` [PATCH 01/12] thunderbolt: Avoid reserved fields in path config space for USB4 routers Mika Westerberg
                   ` (11 more replies)
  0 siblings, 12 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:10 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

Hi all,

This series improves the Thunderbolt peer-to-peer (aka XDomain) handling in
various places in the driver and tries to make it follow the USB4 spec more
closely.

Alan Borzeszkowski (1):
  thunderbolt: Don't create multiple DMA tunnels on firmware connection manager

Gil Fine (1):
  thunderbolt: Avoid reserved fields in path config space for USB4 routers

Mika Westerberg (10):
  thunderbolt: Don't disable lane adapter if XDomain lane bonding isn't possible
  thunderbolt: Make XDomain lane bonding comply with the USB4 v2 spec
  thunderbolt: Keep the domain reference while processing hotplug
  thunderbolt: Release request if tb_cfg_request() fails in __tb_xdomain_response()
  thunderbolt: Set tb->root_switch to NULL when domain is stopped
  thunderbolt: Wait for tb_domain_release() to complete when driver is removed
  thunderbolt: Keep XDomain reference during the lifetime of a service
  thunderbolt: dma_test: No need to store debugfs directory pointer
  thunderbolt: Remove service debugfs entries during unregister
  thunderbolt: Remove XDomain from the bus without holding tb->lock

 drivers/thunderbolt/debugfs.c  |   2 +
 drivers/thunderbolt/dma_test.c |  20 ++--
 drivers/thunderbolt/domain.c   |  33 ++++++
 drivers/thunderbolt/icm.c      |  15 +++
 drivers/thunderbolt/nhi.c      |   4 +
 drivers/thunderbolt/path.c     |  31 ++++--
 drivers/thunderbolt/switch.c   |  14 +++
 drivers/thunderbolt/tb.c       |  65 ++++++------
 drivers/thunderbolt/tb.h       |   2 +
 drivers/thunderbolt/xdomain.c  | 181 +++++++++++++++++++++++----------
 include/linux/thunderbolt.h    |   4 +
 11 files changed, 269 insertions(+), 102 deletions(-)

-- 
2.50.1


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 01/12] thunderbolt: Avoid reserved fields in path config space for USB4 routers
  2026-04-27  8:10 [PATCH 00/12] thunderbolt: Improvements to XDomain handling Mika Westerberg
@ 2026-04-27  8:10 ` 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
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:10 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

From: Gil Fine <gil.fine@linux.intel.com>

According to USB4 spec, USB4 Connection Manager shall not change value
of any fields that are defined as "RsvdZ" or "VD".  Specifically fields:
Path Credits Allocated, IFC, ISE fields in path config space shall not
be written by CM. To handle this, CM shall first read current path
config space from the hardware, change only the fields that can be
changed, and then write back the path config space.

Signed-off-by: Gil Fine <gil.fine@linux.intel.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/path.c | 31 ++++++++++++++++++++++---------
 1 file changed, 22 insertions(+), 9 deletions(-)

diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c
index 8713ea0f47c1..0092b2ec7873 100644
--- a/drivers/thunderbolt/path.c
+++ b/drivers/thunderbolt/path.c
@@ -412,7 +412,8 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index,
 				 * in the USB4 spec so we clear them
 				 * only for pre-USB4 adapters.
 				 */
-				if (!tb_switch_is_usb4(port->sw)) {
+				if (tb_port_is_null(port) ||
+				    !tb_switch_is_usb4(port->sw)) {
 					hop.ingress_fc = 0;
 					hop.ingress_shared_buffer = 0;
 				}
@@ -532,15 +533,18 @@ int tb_path_activate(struct tb_path *path)
 		__tb_path_deactivate_hop(path->hops[i].in_port,
 				path->hops[i].in_hop_index, path->clear_fc);
 
-		/* dword 0 */
+		/* Needed for USB4 routers, read path config space before write */
+		res = tb_port_read(path->hops[i].in_port, &hop, TB_CFG_HOPS,
+				   2 * path->hops[i].in_hop_index, 2);
+		if (res)
+			goto err;
+
 		hop.next_hop = path->hops[i].next_hop_index;
 		hop.out_port = path->hops[i].out_port->port;
-		hop.initial_credits = path->hops[i].initial_credits;
 		hop.pmps = path->hops[i].pm_support;
 		hop.unknown1 = 0;
 		hop.enable = 1;
 
-		/* dword 1 */
 		out_mask = (i == path->path_length - 1) ?
 				TB_PATH_DESTINATION : TB_PATH_INTERNAL;
 		in_mask = (i == 0) ? TB_PATH_SOURCE : TB_PATH_INTERNAL;
@@ -550,12 +554,21 @@ int tb_path_activate(struct tb_path *path)
 		hop.drop_packages = path->drop_packages;
 		hop.counter = path->hops[i].in_counter_index;
 		hop.counter_enable = path->hops[i].in_counter_index != -1;
-		hop.ingress_fc = path->ingress_fc_enable & in_mask;
 		hop.egress_fc = path->egress_fc_enable & out_mask;
-		hop.ingress_shared_buffer = path->ingress_shared_buffer
-					    & in_mask;
-		hop.egress_shared_buffer = path->egress_shared_buffer
-					    & out_mask;
+		hop.egress_shared_buffer = path->egress_shared_buffer & out_mask;
+		/*
+		 * Protocol adapters IFC and ISE bits, and Path Credits
+		 * Allocated are vendor defined in the USB4 spec so we
+		 * program them only for pre-USB4 and lane adapters.
+		 */
+		if (tb_port_is_null(path->hops[i].in_port) ||
+		    !tb_switch_is_usb4(path->hops[i].in_port->sw)) {
+			hop.initial_credits = path->hops[i].initial_credits;
+			hop.ingress_fc = path->ingress_fc_enable & in_mask;
+			hop.ingress_shared_buffer =
+				path->ingress_shared_buffer & in_mask;
+		}
+
 		hop.unknown3 = 0;
 
 		tb_port_dbg(path->hops[i].in_port, "Writing hop %d\n", i);
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 02/12] thunderbolt: Don't disable lane adapter if XDomain lane bonding isn't possible
  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 ` Mika Westerberg
  2026-04-27  8:11 ` [PATCH 03/12] thunderbolt: Make XDomain lane bonding comply with the USB4 v2 spec Mika Westerberg
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:10 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

This happens when firmware connection manager is being used. It will
deal with disabling the lane 1 adapter after the tunnel has been
established and re-enabling it afterwards. For this reason only do this
when we know that lane bonding is possible (e.g running software
connection manager).

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/xdomain.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index 754808c43f00..57367e18733a 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -1931,7 +1931,13 @@ static void tb_xdomain_link_exit(struct tb_xdomain *xd)
 	if (tb_port_get_link_generation(down) >= 4) {
 		down->bonded = false;
 		down->dual_link_port->bonded = false;
-	} else if (xd->link_width > TB_LINK_WIDTH_SINGLE) {
+		return;
+	}
+
+	if (!xd->bonding_possible)
+		return;
+
+	if (xd->link_width > TB_LINK_WIDTH_SINGLE) {
 		/*
 		 * Just return port structures back to way they were and
 		 * update credits. No need to update userspace because
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 03/12] thunderbolt: Make XDomain lane bonding comply with the USB4 v2 spec
  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 ` Mika Westerberg
  2026-04-27  8:11 ` [PATCH 04/12] thunderbolt: Keep the domain reference while processing hotplug Mika Westerberg
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:11 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

The USB4 v2 Inter-Domain spec "unified" the lane bonding flow so that
when the other end (with higher UUID) is not yet set the target link
width accordingly it is expected to reply with ERROR_NOT_READY.
Implement this for Linux.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/xdomain.c | 65 +++++++++++++++++++++++++----------
 1 file changed, 47 insertions(+), 18 deletions(-)

diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index 57367e18733a..680b2204875a 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -535,29 +535,19 @@ static int tb_xdp_link_state_status_request(struct tb_ctl *ctl, u64 route,
 }
 
 static int tb_xdp_link_state_status_response(struct tb *tb, struct tb_ctl *ctl,
-					     struct tb_xdomain *xd, u8 sequence)
+					     struct tb_xdomain *xd, u8 sequence,
+					     u8 slw, u8 sls, u8 tls, u8 tlw)
 {
 	struct tb_xdp_link_state_status_response res;
-	struct tb_port *port = tb_xdomain_downstream_port(xd);
-	u32 val[2];
-	int ret;
 
 	memset(&res, 0, sizeof(res));
 	tb_xdp_fill_header(&res.hdr, xd->route, sequence,
 			   LINK_STATE_STATUS_RESPONSE, sizeof(res));
 
-	ret = tb_port_read(port, val, TB_CFG_PORT,
-			   port->cap_phy + LANE_ADP_CS_0, ARRAY_SIZE(val));
-	if (ret)
-		return ret;
-
-	res.slw = (val[0] & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >>
-			LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT;
-	res.sls = (val[0] & LANE_ADP_CS_0_SUPPORTED_SPEED_MASK) >>
-			LANE_ADP_CS_0_SUPPORTED_SPEED_SHIFT;
-	res.tls = val[1] & LANE_ADP_CS_1_TARGET_SPEED_MASK;
-	res.tlw = (val[1] & LANE_ADP_CS_1_TARGET_WIDTH_MASK) >>
-			LANE_ADP_CS_1_TARGET_WIDTH_SHIFT;
+	res.slw = slw;
+	res.sls = sls;
+	res.tls = tls;
+	res.tlw = tlw;
 
 	return __tb_xdomain_response(ctl, &res, sizeof(res),
 				     TB_CFG_PKG_XDOMAIN_RESP);
@@ -804,8 +794,47 @@ static void tb_xdp_handle_request(struct work_struct *work)
 		       route);
 
 		if (xd) {
-			ret = tb_xdp_link_state_status_response(tb, ctl, xd,
-								sequence);
+			struct tb_port *port = tb_xdomain_downstream_port(xd);
+			u8 slw, sls, tls, tlw;
+			u32 val[2];
+
+			/*
+			 * Read the adapter supported and target widths
+			 * and speeds.
+			 */
+			ret = tb_port_read(port, val, TB_CFG_PORT,
+					   port->cap_phy + LANE_ADP_CS_0,
+					   ARRAY_SIZE(val));
+			if (ret)
+				break;
+
+			slw = (val[0] & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >>
+				LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT;
+			sls = (val[0] & LANE_ADP_CS_0_SUPPORTED_SPEED_MASK) >>
+				LANE_ADP_CS_0_SUPPORTED_SPEED_SHIFT;
+			tls = val[1] & LANE_ADP_CS_1_TARGET_SPEED_MASK;
+			tlw = (val[1] & LANE_ADP_CS_1_TARGET_WIDTH_MASK) >>
+				LANE_ADP_CS_1_TARGET_WIDTH_SHIFT;
+
+			/*
+			 * When we have higher UUID, we are supposed to
+			 * return ERROR_NOT_READY if the tlw is not yet
+			 * set according to the Inter-Domain spec for
+			 * USB4 v2.
+			 */
+			if (xd->state == XDOMAIN_STATE_BONDING_UUID_HIGH &&
+			    xd->target_link_width &&
+			    xd->target_link_width != tlw) {
+				tb_dbg(tb, "%llx: target link width not yet set %#x != %#x\n",
+				       route, tlw, xd->target_link_width);
+				tb_xdp_error_response(ctl, route, sequence,
+						      ERROR_NOT_READY);
+			} else {
+				tb_dbg(tb, "%llx: replying with target link width set to %#x\n",
+				       route, tlw);
+				ret = tb_xdp_link_state_status_response(tb, ctl,
+					xd, sequence, slw, sls, tls, tlw);
+			}
 		} else {
 			tb_xdp_error_response(ctl, route, sequence,
 					      ERROR_NOT_READY);
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 04/12] thunderbolt: Keep the domain reference while processing hotplug
  2026-04-27  8:10 [PATCH 00/12] thunderbolt: Improvements to XDomain handling Mika Westerberg
                   ` (2 preceding siblings ...)
  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 ` Mika Westerberg
  2026-04-27  8:11 ` [PATCH 05/12] thunderbolt: Release request if tb_cfg_request() fails in __tb_xdomain_response() Mika Westerberg
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:11 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

We process hotplug events in a workqueue that may run after the domain
has been removed by tb_domain_remove(). For example if user unloads the
driver while at the same time plugging  a device router we may have
scheduled tb_handle_hotplug() to run. Avoid possible UAF in this case by
taking the domain reference before scheduling the hotplug handler in
tb_queue_hotplug().

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/tb.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index c69c323e6952..34b7d18cce56 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -98,7 +98,7 @@ static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug)
 	if (!ev)
 		return;
 
-	ev->tb = tb;
+	ev->tb = tb_domain_get(tb);
 	ev->route = route;
 	ev->port = port;
 	ev->unplug = unplug;
@@ -2527,6 +2527,9 @@ static void tb_handle_hotplug(struct work_struct *work)
 	pm_runtime_mark_last_busy(&tb->dev);
 	pm_runtime_put_autosuspend(&tb->dev);
 
+	/* Undo the refcount increased in tb_queue_hotplug() */
+	tb_domain_put(tb);
+
 	kfree(ev);
 }
 
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 05/12] thunderbolt: Release request if tb_cfg_request() fails in __tb_xdomain_response()
  2026-04-27  8:10 [PATCH 00/12] thunderbolt: Improvements to XDomain handling Mika Westerberg
                   ` (3 preceding siblings ...)
  2026-04-27  8:11 ` [PATCH 04/12] thunderbolt: Keep the domain reference while processing hotplug Mika Westerberg
@ 2026-04-27  8:11 ` Mika Westerberg
  2026-04-27  8:11 ` [PATCH 06/12] thunderbolt: Set tb->root_switch to NULL when domain is stopped Mika Westerberg
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:11 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

If tb_cfg_request() fails setting up the request (for example the
control channel is shut down already) it returns an error without
calling the callback. To avoid leaking that memory, call
tb_cfg_request_put() if tb_cfg_request() fails.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/xdomain.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index 680b2204875a..4fe19cf6387d 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -136,6 +136,7 @@ static int __tb_xdomain_response(struct tb_ctl *ctl, const void *response,
 				 size_t size, enum tb_cfg_pkg_type type)
 {
 	struct tb_cfg_request *req;
+	int ret;
 
 	req = tb_cfg_request_alloc();
 	if (!req)
@@ -147,7 +148,11 @@ static int __tb_xdomain_response(struct tb_ctl *ctl, const void *response,
 	req->request_size = size;
 	req->request_type = type;
 
-	return tb_cfg_request(ctl, req, response_ready, req);
+	ret = tb_cfg_request(ctl, req, response_ready, req);
+	if (ret)
+		tb_cfg_request_put(req);
+
+	return ret;
 }
 
 /**
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 06/12] thunderbolt: Set tb->root_switch to NULL when domain is stopped
  2026-04-27  8:10 [PATCH 00/12] thunderbolt: Improvements to XDomain handling Mika Westerberg
                   ` (4 preceding siblings ...)
  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 ` Mika Westerberg
  2026-04-27  8:11 ` [PATCH 07/12] thunderbolt: Wait for tb_domain_release() to complete when driver is removed Mika Westerberg
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:11 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

Similarly what we do with the firmware connection manager. This makes
tb_xdp_handle_request() return error to the remote host. However, we
need to make sure we keep the uuid alive so that we can reply until the
whole domain is released.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/tb.c      | 1 +
 drivers/thunderbolt/xdomain.c | 6 +++++-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 34b7d18cce56..677877baae63 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -2952,6 +2952,7 @@ static void tb_stop(struct tb *tb)
 		tb_tunnel_put(tunnel);
 	}
 	tb_switch_remove(tb->root_switch);
+	tb->root_switch = NULL;
 	tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */
 }
 
diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index 4fe19cf6387d..a1887a15a284 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -740,7 +740,7 @@ static void tb_xdp_handle_request(struct work_struct *work)
 
 	mutex_lock(&tb->lock);
 	if (tb->root_switch)
-		uuid = tb->root_switch->uuid;
+		uuid = kmemdup(tb->root_switch->uuid, sizeof(*uuid), GFP_KERNEL);
 	else
 		uuid = NULL;
 	mutex_unlock(&tb->lock);
@@ -880,6 +880,7 @@ static void tb_xdp_handle_request(struct work_struct *work)
 	}
 
 out:
+	kfree(uuid);
 	kfree(xw->pkg);
 	kfree(xw);
 
@@ -2348,6 +2349,9 @@ static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw,
 {
 	struct tb_port *port;
 
+	if (!sw)
+		return NULL;
+
 	tb_switch_for_each_port(sw, port) {
 		struct tb_xdomain *xd;
 
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 07/12] thunderbolt: Wait for tb_domain_release() to complete when driver is removed
  2026-04-27  8:10 [PATCH 00/12] thunderbolt: Improvements to XDomain handling Mika Westerberg
                   ` (5 preceding siblings ...)
  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 ` Mika Westerberg
  2026-04-27  8:11 ` [PATCH 08/12] thunderbolt: Keep XDomain reference during the lifetime of a service Mika Westerberg
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:11 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

We should not call nhi_shutdown() before the domain structure and the
control channel rings are completely released. Otherwise we might
release resources like the nhi->msix_ida that are still referenced in
tb_domain_release(). For this reason wait for the tb_domain_release() to
be completed before continuing to nhi_shutdown() and eventually
releasing of the rest of the data structures.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/domain.c | 3 +++
 drivers/thunderbolt/nhi.c    | 4 ++++
 include/linux/thunderbolt.h  | 2 ++
 3 files changed, 9 insertions(+)

diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c
index 317780b99992..df4d7dd45adf 100644
--- a/drivers/thunderbolt/domain.c
+++ b/drivers/thunderbolt/domain.c
@@ -319,12 +319,15 @@ const struct bus_type tb_bus_type = {
 static void tb_domain_release(struct device *dev)
 {
 	struct tb *tb = container_of(dev, struct tb, dev);
+	struct tb_nhi *nhi = tb->nhi;
 
 	tb_ctl_free(tb->ctl);
 	destroy_workqueue(tb->wq);
 	ida_free(&tb_domain_ida, tb->index);
 	mutex_destroy(&tb->lock);
 	kfree(tb);
+
+	complete(&nhi->domain_released);
 }
 
 const struct device_type tb_domain_type = {
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 2bb2e79ca3cb..1a2051673067 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -1401,6 +1401,8 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 
 	dev_dbg(dev, "NHI initialized, starting thunderbolt\n");
 
+	init_completion(&nhi->domain_released);
+
 	res = tb_domain_add(tb, host_reset);
 	if (res) {
 		/*
@@ -1408,6 +1410,7 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 		 * activated. Do a proper shutdown.
 		 */
 		tb_domain_put(tb);
+		wait_for_completion(&nhi->domain_released);
 		nhi_shutdown(nhi);
 		return res;
 	}
@@ -1433,6 +1436,7 @@ static void nhi_remove(struct pci_dev *pdev)
 	pm_runtime_forbid(&pdev->dev);
 
 	tb_domain_remove(tb);
+	wait_for_completion(&nhi->domain_released);
 	nhi_shutdown(nhi);
 }
 
diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h
index 0ba112175bb3..a5ef7100a6d3 100644
--- a/include/linux/thunderbolt.h
+++ b/include/linux/thunderbolt.h
@@ -493,6 +493,7 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc)
  *		    MSI-X is used.
  * @hop_count: Number of rings (end point hops) supported by NHI.
  * @quirks: NHI specific quirks if any
+ * @domain_released: Completed when domain has been fully released
  */
 struct tb_nhi {
 	spinlock_t lock;
@@ -507,6 +508,7 @@ struct tb_nhi {
 	struct work_struct interrupt_work;
 	u32 hop_count;
 	unsigned long quirks;
+	struct completion domain_released;
 };
 
 /**
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 08/12] thunderbolt: Keep XDomain reference during the lifetime of a service
  2026-04-27  8:10 [PATCH 00/12] thunderbolt: Improvements to XDomain handling Mika Westerberg
                   ` (6 preceding siblings ...)
  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 ` Mika Westerberg
  2026-04-27  8:11 ` [PATCH 09/12] thunderbolt: dma_test: No need to store debugfs directory pointer Mika Westerberg
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:11 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

This is needed because we release the service ID in tb_service_release()
and the ID array is owned by the parent XDomain.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/xdomain.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index a1887a15a284..0e97d0ad7733 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -1039,6 +1039,7 @@ static void tb_service_release(struct device *dev)
 	ida_free(&xd->service_ids, svc->id);
 	kfree(svc->key);
 	kfree(svc);
+	tb_xdomain_put(xd);
 }
 
 const struct device_type tb_service_type = {
@@ -1147,7 +1148,7 @@ static void enumerate_services(struct tb_xdomain *xd)
 		svc->id = id;
 		svc->dev.bus = &tb_bus_type;
 		svc->dev.type = &tb_service_type;
-		svc->dev.parent = &xd->dev;
+		svc->dev.parent = get_device(&xd->dev);
 		dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), svc->id);
 
 		tb_service_debugfs_init(svc);
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 09/12] thunderbolt: dma_test: No need to store debugfs directory pointer
  2026-04-27  8:10 [PATCH 00/12] thunderbolt: Improvements to XDomain handling Mika Westerberg
                   ` (7 preceding siblings ...)
  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 ` Mika Westerberg
  2026-04-27  8:11 ` [PATCH 10/12] thunderbolt: Remove service debugfs entries during unregister Mika Westerberg
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:11 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

We don't actually need to store the debugfs directory pointer inside
struct dma_test. Instead we can use the debugfs_lookup_and_remove()
which also handles the case if the debugfs directory is already removed
by the core driver (for example when cable is disconnected).

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/dma_test.c | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)

diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c
index b4aa79d482a0..af1e6bc9c7cd 100644
--- a/drivers/thunderbolt/dma_test.c
+++ b/drivers/thunderbolt/dma_test.c
@@ -87,7 +87,6 @@ static const char * const dma_test_result_names[] = {
  * @error_code: Error code of the last run
  * @complete: Used to wait for the Rx to complete
  * @lock: Lock serializing access to this structure
- * @debugfs_dir: dentry of this dma_test
  */
 struct dma_test {
 	const struct tb_service *svc;
@@ -108,7 +107,6 @@ struct dma_test {
 	enum dma_test_test_error error_code;
 	struct completion complete;
 	struct mutex lock;
-	struct dentry *debugfs_dir;
 };
 
 /* DMA test property directory UUID: 3188cd10-6523-4a5a-a682-fdca07a248d8 */
@@ -619,18 +617,18 @@ DEFINE_SHOW_ATTRIBUTE(status);
 
 static void dma_test_debugfs_init(struct tb_service *svc)
 {
-	struct dma_test *dt = tb_service_get_drvdata(svc);
+	struct dentry *debugfs_dir;
 
-	dt->debugfs_dir = debugfs_create_dir("dma_test", svc->debugfs_dir);
+	debugfs_dir = debugfs_create_dir("dma_test", svc->debugfs_dir);
 
-	debugfs_create_file("lanes", 0600, dt->debugfs_dir, svc, &lanes_fops);
-	debugfs_create_file("speed", 0600, dt->debugfs_dir, svc, &speed_fops);
-	debugfs_create_file("packets_to_receive", 0600, dt->debugfs_dir, svc,
+	debugfs_create_file("lanes", 0600, debugfs_dir, svc, &lanes_fops);
+	debugfs_create_file("speed", 0600, debugfs_dir, svc, &speed_fops);
+	debugfs_create_file("packets_to_receive", 0600, debugfs_dir, svc,
 			    &packets_to_receive_fops);
-	debugfs_create_file("packets_to_send", 0600, dt->debugfs_dir, svc,
+	debugfs_create_file("packets_to_send", 0600, debugfs_dir, svc,
 			    &packets_to_send_fops);
-	debugfs_create_file("status", 0400, dt->debugfs_dir, svc, &status_fops);
-	debugfs_create_file("test", 0200, dt->debugfs_dir, svc, &test_fops);
+	debugfs_create_file("status", 0400, debugfs_dir, svc, &status_fops);
+	debugfs_create_file("test", 0200, debugfs_dir, svc, &test_fops);
 }
 
 static int dma_test_probe(struct tb_service *svc, const struct tb_service_id *id)
@@ -658,7 +656,7 @@ static void dma_test_remove(struct tb_service *svc)
 	struct dma_test *dt = tb_service_get_drvdata(svc);
 
 	mutex_lock(&dt->lock);
-	debugfs_remove_recursive(dt->debugfs_dir);
+	debugfs_lookup_and_remove("dma_test", svc->debugfs_dir);
 	mutex_unlock(&dt->lock);
 }
 
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 10/12] thunderbolt: Remove service debugfs entries during unregister
  2026-04-27  8:10 [PATCH 00/12] thunderbolt: Improvements to XDomain handling Mika Westerberg
                   ` (8 preceding siblings ...)
  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 ` Mika Westerberg
  2026-04-27  8:11 ` [PATCH 11/12] thunderbolt: Remove XDomain from the bus without holding tb->lock Mika Westerberg
  2026-04-27  8:11 ` [PATCH 12/12] thunderbolt: Don't create multiple DMA tunnels on firmware connection manager Mika Westerberg
  11 siblings, 0 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:11 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

We add them as part of the register path so to keep it symmetric remove
them as part of the unregister path. This also removes them even if the
service itself is not yet released (but is unregistered), thus allowing
new register with the same service name to happen.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/xdomain.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index 0e97d0ad7733..76e1902d18f3 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -1035,7 +1035,6 @@ static void tb_service_release(struct device *dev)
 	struct tb_service *svc = container_of(dev, struct tb_service, dev);
 	struct tb_xdomain *xd = tb_service_parent(svc);
 
-	tb_service_debugfs_remove(svc);
 	ida_free(&xd->service_ids, svc->id);
 	kfree(svc->key);
 	kfree(svc);
@@ -1050,6 +1049,14 @@ const struct device_type tb_service_type = {
 };
 EXPORT_SYMBOL_GPL(tb_service_type);
 
+static void __unregister_service(struct device *dev)
+{
+	struct tb_service *svc = tb_to_service(dev);
+
+	tb_service_debugfs_remove(svc);
+	device_unregister(&svc->dev);
+}
+
 static int remove_missing_service(struct device *dev, void *data)
 {
 	struct tb_xdomain *xd = data;
@@ -1061,7 +1068,7 @@ static int remove_missing_service(struct device *dev, void *data)
 
 	if (!tb_property_find(xd->remote_properties, svc->key,
 			      TB_PROPERTY_TYPE_DIRECTORY))
-		device_unregister(dev);
+		__unregister_service(dev);
 
 	return 0;
 }
@@ -1154,6 +1161,7 @@ static void enumerate_services(struct tb_xdomain *xd)
 		tb_service_debugfs_init(svc);
 
 		if (device_register(&svc->dev)) {
+			tb_service_debugfs_remove(svc);
 			put_device(&svc->dev);
 			break;
 		}
@@ -2092,7 +2100,7 @@ void tb_xdomain_add(struct tb_xdomain *xd)
 
 static int unregister_service(struct device *dev, void *data)
 {
-	device_unregister(dev);
+	__unregister_service(dev);
 	return 0;
 }
 
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 11/12] thunderbolt: Remove XDomain from the bus without holding tb->lock
  2026-04-27  8:10 [PATCH 00/12] thunderbolt: Improvements to XDomain handling Mika Westerberg
                   ` (9 preceding siblings ...)
  2026-04-27  8:11 ` [PATCH 10/12] thunderbolt: Remove service debugfs entries during unregister Mika Westerberg
@ 2026-04-27  8:11 ` Mika Westerberg
  2026-04-27  8:11 ` [PATCH 12/12] thunderbolt: Don't create multiple DMA tunnels on firmware connection manager Mika Westerberg
  11 siblings, 0 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:11 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

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


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 12/12] thunderbolt: Don't create multiple DMA tunnels on firmware connection manager
  2026-04-27  8:10 [PATCH 00/12] thunderbolt: Improvements to XDomain handling Mika Westerberg
                   ` (10 preceding siblings ...)
  2026-04-27  8:11 ` [PATCH 11/12] thunderbolt: Remove XDomain from the bus without holding tb->lock Mika Westerberg
@ 2026-04-27  8:11 ` Mika Westerberg
  11 siblings, 0 replies; 13+ messages in thread
From: Mika Westerberg @ 2026-04-27  8:11 UTC (permalink / raw)
  To: linux-usb
  Cc: Yehezkel Bernat, Lukas Wunner, Andreas Noever, Alan Borzeszkowski,
	Gil Fine, Mika Westerberg

From: Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>

Firmware connection manager supports only one DMA tunnel per XDomain
connection. Firmware prior Intel Titan Ridge failed the operation
directly but the same does not happen anymore on Titan Ridge and
forward. For this reason add an explicit check, and fail the operation
accordingly in the driver.

Signed-off-by: Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/icm.c     | 10 ++++++++++
 drivers/thunderbolt/xdomain.c | 25 +++++++++++++++++++------
 include/linux/thunderbolt.h   |  2 ++
 3 files changed, 31 insertions(+), 6 deletions(-)

diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c
index 2f93a7bccad5..c492995166f7 100644
--- a/drivers/thunderbolt/icm.c
+++ b/drivers/thunderbolt/icm.c
@@ -587,6 +587,11 @@ static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
 	struct icm_fr_pkg_approve_xdomain request;
 	int ret;
 
+	if (atomic_read(&xd->ntunnels) >= 1) {
+		tb_warn(tb, "only one tunnel is supported by the firmware\n");
+		return -EOPNOTSUPP;
+	}
+
 	memset(&request, 0, sizeof(request));
 	request.hdr.code = ICM_APPROVE_XDOMAIN;
 	request.link_info = xd->depth << ICM_LINK_INFO_DEPTH_SHIFT | xd->link;
@@ -1158,6 +1163,11 @@ static int icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
 	struct icm_tr_pkg_approve_xdomain request;
 	int ret;
 
+	if (atomic_read(&xd->ntunnels) >= 1) {
+		tb_warn(tb, "only one tunnel is supported by the firmware\n");
+		return -EOPNOTSUPP;
+	}
+
 	memset(&request, 0, sizeof(request));
 	request.hdr.code = ICM_APPROVE_XDOMAIN;
 	request.route_hi = upper_32_bits(xd->route);
diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index 9a30fe36c4be..6e83f93eee83 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -2038,6 +2038,7 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
 	INIT_DELAYED_WORK(&xd->state_work, tb_xdomain_state_work);
 	INIT_DELAYED_WORK(&xd->properties_changed_work,
 			  tb_xdomain_properties_changed);
+	atomic_set(&xd->ntunnels, 0);
 
 	xd->local_uuid = kmemdup(local_uuid, sizeof(uuid_t), GFP_KERNEL);
 	if (!xd->local_uuid)
@@ -2328,9 +2329,15 @@ int tb_xdomain_enable_paths(struct tb_xdomain *xd, int transmit_path,
 			    int transmit_ring, int receive_path,
 			    int receive_ring)
 {
-	return tb_domain_approve_xdomain_paths(xd->tb, xd, transmit_path,
-					       transmit_ring, receive_path,
-					       receive_ring);
+	int ret;
+
+	ret = tb_domain_approve_xdomain_paths(xd->tb, xd, transmit_path,
+					      transmit_ring, receive_path,
+					      receive_ring);
+	if (ret)
+		return ret;
+	atomic_inc(&xd->ntunnels);
+	return 0;
 }
 EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths);
 
@@ -2353,9 +2360,15 @@ int tb_xdomain_disable_paths(struct tb_xdomain *xd, int transmit_path,
 			     int transmit_ring, int receive_path,
 			     int receive_ring)
 {
-	return tb_domain_disconnect_xdomain_paths(xd->tb, xd, transmit_path,
-						  transmit_ring, receive_path,
-						  receive_ring);
+	int ret;
+
+	ret = tb_domain_disconnect_xdomain_paths(xd->tb, xd, transmit_path,
+						 transmit_ring, receive_path,
+						 receive_ring);
+	if (ret)
+		return ret;
+	atomic_dec(&xd->ntunnels);
+	return 0;
 }
 EXPORT_SYMBOL_GPL(tb_xdomain_disable_paths);
 
diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h
index a5ef7100a6d3..bbdbbc84c999 100644
--- a/include/linux/thunderbolt.h
+++ b/include/linux/thunderbolt.h
@@ -228,6 +228,7 @@ enum tb_link_width {
  *				changed notification
  * @bonding_possible: True if lane bonding is possible on local side
  * @target_link_width: Target link width from the remote host
+ * @ntunnels: Keeps track of how many tunnels go through this XDomain
  * @link: Root switch link the remote domain is connected (ICM only)
  * @depth: Depth in the chain the remote domain is connected (ICM only)
  *
@@ -273,6 +274,7 @@ struct tb_xdomain {
 	int properties_changed_retries;
 	bool bonding_possible;
 	u8 target_link_width;
+	atomic_t ntunnels;
 	u8 link;
 	u8 depth;
 };
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-04-27  8:11 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH 11/12] thunderbolt: Remove XDomain from the bus without holding tb->lock Mika Westerberg
2026-04-27  8:11 ` [PATCH 12/12] thunderbolt: Don't create multiple DMA tunnels on firmware connection manager Mika Westerberg

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox