From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E3842175A6D; Thu, 30 Jul 2026 14:55:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785423303; cv=none; b=MkDEwjbsHTPVIJIj9gDdN8jW4Yp5qvgu/zed9nr6OBdzTwGW/PRw7DBf1fTB2VlhTVPble5+w2gc5W2FNhldfBDDGnAwt5j/GnrEOoVEPreNYor4inN/Kg8sFIQ2l7GxTdPU4lfgnrqoI1BCxdR2rBaY3yWo2+SaCRnoFwhsMxQ= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785423303; c=relaxed/simple; bh=TDw2D2wwWMqRZiqIP11tBXLgH4HmERxPFwGtHG0+BhY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=VPU+gO8yMiG2HJH2ASH+RXvnrpXR6yRGA3I2zdRkeeG7q1KBSCXDxw2zOX8le4hzuAjrFMrMPVYI7ZdztfLFyapXVekSZiqU2jik4rln/wMt0BMzCSfE/6FzsOY49DkYwA/tdvyKovkxit943N6yH3QYsibWpwkmUfffJonCR98= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=slstBizx; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="slstBizx" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 3F8211F000E9; Thu, 30 Jul 2026 14:55:01 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1785423301; bh=oyMWSgwxGoKSQvWTMnu8kSruFmc4Mh56SMBXYq2s+TA=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=slstBizxv6Icu/G8cq8FluUNiqEpAtUB9VJhwvzKMSNZ11/yjAXEvX/lOFQ+IPTJQ 49Uz3KfmQgRVWM7AaVB8SCfbgNhJAVDlrUqyrbkI9bwRPI2Xn8zfDr1Q5KjNzx47F6 PtI4xyPHJEuUFXLsA16T9uSU+OUW/tmYWZx55RyU= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Mika Westerberg , Sasha Levin Subject: [PATCH 7.1 735/744] thunderbolt: Remove XDomain from the bus without holding tb->lock Date: Thu, 30 Jul 2026 16:16:48 +0200 Message-ID: <20260730141459.932088973@linuxfoundation.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260730141444.267951807@linuxfoundation.org> References: <20260730141444.267951807@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: patches@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 7.1-stable review patch. If anyone has any objections, please let me know. ------------------ From: Mika Westerberg [ Upstream commit a8937f35cf39c39c64325aa84d0463d866850857 ] 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 Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- 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(-) --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -1786,6 +1786,8 @@ static void margining_port_remove(struct 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); --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -850,6 +850,36 @@ int tb_domain_disconnect_all_paths(struc 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; --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -738,6 +738,7 @@ static void remove_xdomain(struct tb_xdo 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(stru 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_ 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) --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -3625,6 +3625,20 @@ int tb_switch_resume(struct tb_switch *s 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; + } } } } --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -2524,6 +2524,8 @@ put_sw: out: mutex_unlock(&tb->lock); + tb_domain_unregister_unplugged_xdomains(tb); + pm_runtime_mark_last_busy(&tb->dev); pm_runtime_put_autosuspend(&tb->dev); @@ -3110,6 +3112,24 @@ static void tb_restore_children(struct t } } +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); @@ -3129,6 +3149,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); /* @@ -3171,28 +3192,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); @@ -3212,14 +3211,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) @@ -3246,11 +3245,11 @@ static void tb_remove_work(struct work_s 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) --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -793,6 +793,7 @@ int tb_domain_disconnect_xdomain_paths(s 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(stru 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); --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -2072,41 +2072,54 @@ static int unregister_service(struct dev } /** - * 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 *