diff options
Diffstat (limited to 'drivers/thunderbolt/tb.c')
-rw-r--r-- | drivers/thunderbolt/tb.c | 388 |
1 files changed, 305 insertions, 83 deletions
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 107cd232f486..f507815040eb 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -206,27 +206,197 @@ static struct tb_port *tb_find_unused_port(struct tb_switch *sw, } static struct tb_port *tb_find_usb3_down(struct tb_switch *sw, - const struct tb_port *port) + const struct tb_port *port) { struct tb_port *down; down = usb4_switch_map_usb3_down(sw, port); - if (down) { - if (WARN_ON(!tb_port_is_usb3_down(down))) - goto out; - if (WARN_ON(tb_usb3_port_is_enabled(down))) - goto out; - + if (down && !tb_usb3_port_is_enabled(down)) return down; + return NULL; +} + +static struct tb_tunnel *tb_find_tunnel(struct tb *tb, enum tb_tunnel_type type, + struct tb_port *src_port, + struct tb_port *dst_port) +{ + struct tb_cm *tcm = tb_priv(tb); + struct tb_tunnel *tunnel; + + list_for_each_entry(tunnel, &tcm->tunnel_list, list) { + if (tunnel->type == type && + ((src_port && src_port == tunnel->src_port) || + (dst_port && dst_port == tunnel->dst_port))) { + return tunnel; + } } -out: - return tb_find_unused_port(sw, TB_TYPE_USB3_DOWN); + return NULL; +} + +static struct tb_tunnel *tb_find_first_usb3_tunnel(struct tb *tb, + struct tb_port *src_port, + struct tb_port *dst_port) +{ + struct tb_port *port, *usb3_down; + struct tb_switch *sw; + + /* Pick the router that is deepest in the topology */ + if (dst_port->sw->config.depth > src_port->sw->config.depth) + sw = dst_port->sw; + else + sw = src_port->sw; + + /* Can't be the host router */ + if (sw == tb->root_switch) + return NULL; + + /* Find the downstream USB4 port that leads to this router */ + port = tb_port_at(tb_route(sw), tb->root_switch); + /* Find the corresponding host router USB3 downstream port */ + usb3_down = usb4_switch_map_usb3_down(tb->root_switch, port); + if (!usb3_down) + return NULL; + + return tb_find_tunnel(tb, TB_TUNNEL_USB3, usb3_down, NULL); +} + +static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port, + struct tb_port *dst_port, int *available_up, int *available_down) +{ + int usb3_consumed_up, usb3_consumed_down, ret; + struct tb_cm *tcm = tb_priv(tb); + struct tb_tunnel *tunnel; + struct tb_port *port; + + tb_port_dbg(dst_port, "calculating available bandwidth\n"); + + tunnel = tb_find_first_usb3_tunnel(tb, src_port, dst_port); + if (tunnel) { + ret = tb_tunnel_consumed_bandwidth(tunnel, &usb3_consumed_up, + &usb3_consumed_down); + if (ret) + return ret; + } else { + usb3_consumed_up = 0; + usb3_consumed_down = 0; + } + + *available_up = *available_down = 40000; + + /* Find the minimum available bandwidth over all links */ + tb_for_each_port_on_path(src_port, dst_port, port) { + int link_speed, link_width, up_bw, down_bw; + + if (!tb_port_is_null(port)) + continue; + + if (tb_is_upstream_port(port)) { + link_speed = port->sw->link_speed; + } else { + link_speed = tb_port_get_link_speed(port); + if (link_speed < 0) + return link_speed; + } + + link_width = port->bonded ? 2 : 1; + + up_bw = link_speed * link_width * 1000; /* Mb/s */ + /* Leave 10% guard band */ + up_bw -= up_bw / 10; + down_bw = up_bw; + + tb_port_dbg(port, "link total bandwidth %d Mb/s\n", up_bw); + + /* + * Find all DP tunnels that cross the port and reduce + * their consumed bandwidth from the available. + */ + list_for_each_entry(tunnel, &tcm->tunnel_list, list) { + int dp_consumed_up, dp_consumed_down; + + if (!tb_tunnel_is_dp(tunnel)) + continue; + + if (!tb_tunnel_port_on_path(tunnel, port)) + continue; + + ret = tb_tunnel_consumed_bandwidth(tunnel, + &dp_consumed_up, + &dp_consumed_down); + if (ret) + return ret; + + up_bw -= dp_consumed_up; + down_bw -= dp_consumed_down; + } + + /* + * If USB3 is tunneled from the host router down to the + * branch leading to port we need to take USB3 consumed + * bandwidth into account regardless whether it actually + * crosses the port. + */ + up_bw -= usb3_consumed_up; + down_bw -= usb3_consumed_down; + + if (up_bw < *available_up) + *available_up = up_bw; + if (down_bw < *available_down) + *available_down = down_bw; + } + + if (*available_up < 0) + *available_up = 0; + if (*available_down < 0) + *available_down = 0; + + return 0; +} + +static int tb_release_unused_usb3_bandwidth(struct tb *tb, + struct tb_port *src_port, + struct tb_port *dst_port) +{ + struct tb_tunnel *tunnel; + + tunnel = tb_find_first_usb3_tunnel(tb, src_port, dst_port); + return tunnel ? tb_tunnel_release_unused_bandwidth(tunnel) : 0; +} + +static void tb_reclaim_usb3_bandwidth(struct tb *tb, struct tb_port *src_port, + struct tb_port *dst_port) +{ + int ret, available_up, available_down; + struct tb_tunnel *tunnel; + + tunnel = tb_find_first_usb3_tunnel(tb, src_port, dst_port); + if (!tunnel) + return; + + tb_dbg(tb, "reclaiming unused bandwidth for USB3\n"); + + /* + * Calculate available bandwidth for the first hop USB3 tunnel. + * That determines the whole USB3 bandwidth for this branch. + */ + ret = tb_available_bandwidth(tb, tunnel->src_port, tunnel->dst_port, + &available_up, &available_down); + if (ret) { + tb_warn(tb, "failed to calculate available bandwidth\n"); + return; + } + + tb_dbg(tb, "available bandwidth for USB3 %d/%d Mb/s\n", + available_up, available_down); + + tb_tunnel_reclaim_available_bandwidth(tunnel, &available_up, &available_down); } static int tb_tunnel_usb3(struct tb *tb, struct tb_switch *sw) { struct tb_switch *parent = tb_switch_parent(sw); + int ret, available_up, available_down; struct tb_port *up, *down, *port; struct tb_cm *tcm = tb_priv(tb); struct tb_tunnel *tunnel; @@ -235,6 +405,9 @@ static int tb_tunnel_usb3(struct tb *tb, struct tb_switch *sw) if (!up) return 0; + if (!sw->link_usb4) + return 0; + /* * Look up available down port. Since we are chaining it should * be found right above this switch. @@ -254,21 +427,48 @@ static int tb_tunnel_usb3(struct tb *tb, struct tb_switch *sw) parent_up = tb_switch_find_port(parent, TB_TYPE_USB3_UP); if (!parent_up || !tb_port_is_enabled(parent_up)) return 0; + + /* Make all unused bandwidth available for the new tunnel */ + ret = tb_release_unused_usb3_bandwidth(tb, down, up); + if (ret) + return ret; } - tunnel = tb_tunnel_alloc_usb3(tb, up, down); - if (!tunnel) - return -ENOMEM; + ret = tb_available_bandwidth(tb, down, up, &available_up, + &available_down); + if (ret) + goto err_reclaim; + + tb_port_dbg(up, "available bandwidth for new USB3 tunnel %d/%d Mb/s\n", + available_up, available_down); + + tunnel = tb_tunnel_alloc_usb3(tb, up, down, available_up, + available_down); + if (!tunnel) { + ret = -ENOMEM; + goto err_reclaim; + } if (tb_tunnel_activate(tunnel)) { tb_port_info(up, "USB3 tunnel activation failed, aborting\n"); - tb_tunnel_free(tunnel); - return -EIO; + ret = -EIO; + goto err_free; } list_add_tail(&tunnel->list, &tcm->tunnel_list); + if (tb_route(parent)) + tb_reclaim_usb3_bandwidth(tb, down, up); + return 0; + +err_free: + tb_tunnel_free(tunnel); +err_reclaim: + if (tb_route(parent)) + tb_reclaim_usb3_bandwidth(tb, down, up); + + return ret; } static int tb_create_usb3_tunnels(struct tb_switch *sw) @@ -339,6 +539,9 @@ static void tb_scan_port(struct tb_port *port) tb_port_dbg(port, "port already has a remote\n"); return; } + + tb_retimer_scan(port); + sw = tb_switch_alloc(port->sw->tb, &port->sw->dev, tb_downstream_route(port)); if (IS_ERR(sw)) { @@ -395,6 +598,9 @@ static void tb_scan_port(struct tb_port *port) if (tb_enable_tmu(sw)) tb_sw_warn(sw, "failed to enable TMU\n"); + /* Scan upstream retimers */ + tb_retimer_scan(upstream_port); + /* * Create USB 3.x tunnels only when the switch is plugged to the * domain. This is because we scan the domain also during discovery @@ -404,43 +610,44 @@ static void tb_scan_port(struct tb_port *port) if (tcm->hotplug_active && tb_tunnel_usb3(sw->tb, sw)) tb_sw_warn(sw, "USB3 tunnel creation failed\n"); + tb_add_dp_resources(sw); tb_scan_switch(sw); } -static struct tb_tunnel *tb_find_tunnel(struct tb *tb, enum tb_tunnel_type type, - struct tb_port *src_port, - struct tb_port *dst_port) -{ - struct tb_cm *tcm = tb_priv(tb); - struct tb_tunnel *tunnel; - - list_for_each_entry(tunnel, &tcm->tunnel_list, list) { - if (tunnel->type == type && - ((src_port && src_port == tunnel->src_port) || - (dst_port && dst_port == tunnel->dst_port))) { - return tunnel; - } - } - - return NULL; -} - static void tb_deactivate_and_free_tunnel(struct tb_tunnel *tunnel) { + struct tb_port *src_port, *dst_port; + struct tb *tb; + if (!tunnel) return; tb_tunnel_deactivate(tunnel); list_del(&tunnel->list); - /* - * In case of DP tunnel make sure the DP IN resource is deallocated - * properly. - */ - if (tb_tunnel_is_dp(tunnel)) { - struct tb_port *in = tunnel->src_port; + tb = tunnel->tb; + src_port = tunnel->src_port; + dst_port = tunnel->dst_port; + + switch (tunnel->type) { + case TB_TUNNEL_DP: + /* + * In case of DP tunnel make sure the DP IN resource is + * deallocated properly. + */ + tb_switch_dealloc_dp_resource(src_port->sw, src_port); + fallthrough; - tb_switch_dealloc_dp_resource(in->sw, in); + case TB_TUNNEL_USB3: + tb_reclaim_usb3_bandwidth(tb, src_port, dst_port); + break; + + default: + /* + * PCIe and DMA tunnels do not consume guaranteed + * bandwidth. + */ + break; } tb_tunnel_free(tunnel); @@ -473,6 +680,7 @@ static void tb_free_unplugged_children(struct tb_switch *sw) continue; if (port->remote->sw->is_unplugged) { + tb_retimer_remove_all(port); tb_remove_dp_resources(port->remote->sw); tb_switch_lane_bonding_disable(port->remote->sw); tb_switch_remove(port->remote->sw); @@ -524,7 +732,7 @@ static struct tb_port *tb_find_pcie_down(struct tb_switch *sw, if (down) { if (WARN_ON(!tb_port_is_pcie_down(down))) goto out; - if (WARN_ON(tb_pci_port_is_enabled(down))) + if (tb_pci_port_is_enabled(down)) goto out; return down; @@ -534,51 +742,49 @@ out: return tb_find_unused_port(sw, TB_TYPE_PCIE_DOWN); } -static int tb_available_bw(struct tb_cm *tcm, struct tb_port *in, - struct tb_port *out) +static struct tb_port *tb_find_dp_out(struct tb *tb, struct tb_port *in) { - struct tb_switch *sw = out->sw; - struct tb_tunnel *tunnel; - int bw, available_bw = 40000; + struct tb_port *host_port, *port; + struct tb_cm *tcm = tb_priv(tb); - while (sw && sw != in->sw) { - bw = sw->link_speed * sw->link_width * 1000; /* Mb/s */ - /* Leave 10% guard band */ - bw -= bw / 10; + host_port = tb_route(in->sw) ? + tb_port_at(tb_route(in->sw), tb->root_switch) : NULL; + + list_for_each_entry(port, &tcm->dp_resources, list) { + if (!tb_port_is_dpout(port)) + continue; + + if (tb_port_is_enabled(port)) { + tb_port_dbg(port, "in use\n"); + continue; + } + + tb_port_dbg(port, "DP OUT available\n"); /* - * Check for any active DP tunnels that go through this - * switch and reduce their consumed bandwidth from - * available. + * Keep the DP tunnel under the topology starting from + * the same host router downstream port. */ - list_for_each_entry(tunnel, &tcm->tunnel_list, list) { - int consumed_bw; + if (host_port && tb_route(port->sw)) { + struct tb_port *p; - if (!tb_tunnel_switch_on_path(tunnel, sw)) + p = tb_port_at(tb_route(port->sw), tb->root_switch); + if (p != host_port) continue; - - consumed_bw = tb_tunnel_consumed_bandwidth(tunnel); - if (consumed_bw < 0) - return consumed_bw; - - bw -= consumed_bw; } - if (bw < available_bw) - available_bw = bw; - - sw = tb_switch_parent(sw); + return port; } - return available_bw; + return NULL; } static void tb_tunnel_dp(struct tb *tb) { + int available_up, available_down, ret; struct tb_cm *tcm = tb_priv(tb); struct tb_port *port, *in, *out; struct tb_tunnel *tunnel; - int available_bw; /* * Find pair of inactive DP IN and DP OUT adapters and then @@ -589,17 +795,21 @@ static void tb_tunnel_dp(struct tb *tb) in = NULL; out = NULL; list_for_each_entry(port, &tcm->dp_resources, list) { + if (!tb_port_is_dpin(port)) + continue; + if (tb_port_is_enabled(port)) { tb_port_dbg(port, "in use\n"); continue; } - tb_port_dbg(port, "available\n"); + tb_port_dbg(port, "DP IN available\n"); - if (!in && tb_port_is_dpin(port)) + out = tb_find_dp_out(tb, port); + if (out) { in = port; - else if (!out && tb_port_is_dpout(port)) - out = port; + break; + } } if (!in) { @@ -616,32 +826,41 @@ static void tb_tunnel_dp(struct tb *tb) return; } - /* Calculate available bandwidth between in and out */ - available_bw = tb_available_bw(tcm, in, out); - if (available_bw < 0) { - tb_warn(tb, "failed to determine available bandwidth\n"); - return; + /* Make all unused USB3 bandwidth available for the new DP tunnel */ + ret = tb_release_unused_usb3_bandwidth(tb, in, out); + if (ret) { + tb_warn(tb, "failed to release unused bandwidth\n"); + goto err_dealloc_dp; } - tb_dbg(tb, "available bandwidth for new DP tunnel %u Mb/s\n", - available_bw); + ret = tb_available_bandwidth(tb, in, out, &available_up, + &available_down); + if (ret) + goto err_reclaim; + + tb_dbg(tb, "available bandwidth for new DP tunnel %u/%u Mb/s\n", + available_up, available_down); - tunnel = tb_tunnel_alloc_dp(tb, in, out, available_bw); + tunnel = tb_tunnel_alloc_dp(tb, in, out, available_up, available_down); if (!tunnel) { tb_port_dbg(out, "could not allocate DP tunnel\n"); - goto dealloc_dp; + goto err_reclaim; } if (tb_tunnel_activate(tunnel)) { tb_port_info(out, "DP tunnel activation failed, aborting\n"); - tb_tunnel_free(tunnel); - goto dealloc_dp; + goto err_free; } list_add_tail(&tunnel->list, &tcm->tunnel_list); + tb_reclaim_usb3_bandwidth(tb, in, out); return; -dealloc_dp: +err_free: + tb_tunnel_free(tunnel); +err_reclaim: + tb_reclaim_usb3_bandwidth(tb, in, out); +err_dealloc_dp: tb_switch_dealloc_dp_resource(in->sw, in); } @@ -827,6 +1046,8 @@ static void tb_handle_hotplug(struct work_struct *work) goto put_sw; } if (ev->unplug) { + tb_retimer_remove_all(port); + if (tb_port_has_remote(port)) { tb_port_dbg(port, "switch unplugged\n"); tb_sw_set_unplugged(port->remote->sw); @@ -1071,6 +1292,7 @@ static int tb_free_unplugged_xdomains(struct tb_switch *sw) if (tb_is_upstream_port(port)) continue; if (port->xdomain && port->xdomain->is_unplugged) { + tb_retimer_remove_all(port); tb_xdomain_remove(port->xdomain); port->xdomain = NULL; ret++; |