diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index d089d2decedd..569163a26afa 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,38 +2105,51 @@ 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); + tb_xdomain_link_exit(xd); + + 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); + } +} + +/** + * 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); - 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)) { - put_device(&xd->dev); - } else { - dev_info(&xd->dev, "host disconnected\n"); - device_unregister(&xd->dev); - } + dev_info(&xd->dev, "host disconnected\n"); + device_unregister(&xd->dev); } /**