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);
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;
sw = tb_to_switch(xd->dev.parent);
tb_port_at(xd->route, sw)->xdomain = NULL;
+ xd->is_unplugged = true;
tb_xdomain_remove(xd);
}
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,
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)
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;
+ }
}
}
}
out:
mutex_unlock(&tb->lock);
+ tb_domain_unregister_unplugged_xdomains(tb);
+
pm_runtime_mark_last_busy(&tb->dev);
pm_runtime_put_autosuspend(&tb->dev);
}
}
+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);
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);
/*
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);
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)
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)
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)
{
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);
}
/**
- * 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