summaryrefslogtreecommitdiff
path: root/drivers/thunderbolt/tb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thunderbolt/tb.c')
-rw-r--r--drivers/thunderbolt/tb.c774
1 files changed, 618 insertions, 156 deletions
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 27bd6ca6f99e..5acdeb766860 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -16,8 +16,31 @@
#include "tb_regs.h"
#include "tunnel.h"
-#define TB_TIMEOUT 100 /* ms */
-#define MAX_GROUPS 7 /* max Group_ID is 7 */
+#define TB_TIMEOUT 100 /* ms */
+
+/*
+ * Minimum bandwidth (in Mb/s) that is needed in the single transmitter/receiver
+ * direction. This is 40G - 10% guard band bandwidth.
+ */
+#define TB_ASYM_MIN (40000 * 90 / 100)
+
+/*
+ * Threshold bandwidth (in Mb/s) that is used to switch the links to
+ * asymmetric and back. This is selected as 45G which means when the
+ * request is higher than this, we switch the link to asymmetric, and
+ * when it is less than this we switch it back. The 45G is selected so
+ * that we still have 27G (of the total 72G) for bulk PCIe traffic when
+ * switching back to symmetric.
+ */
+#define TB_ASYM_THRESHOLD 45000
+
+#define MAX_GROUPS 7 /* max Group_ID is 7 */
+
+static unsigned int asym_threshold = TB_ASYM_THRESHOLD;
+module_param_named(asym_threshold, asym_threshold, uint, 0444);
+MODULE_PARM_DESC(asym_threshold,
+ "threshold (Mb/s) when to Gen 4 switch link symmetry. 0 disables. (default: "
+ __MODULE_STRING(TB_ASYM_THRESHOLD) ")");
/**
* struct tb_cm - Simple Thunderbolt connection manager
@@ -190,7 +213,7 @@ static void tb_add_dp_resources(struct tb_switch *sw)
if (!tb_switch_query_dp_resource(sw, port))
continue;
- list_add_tail(&port->list, &tcm->dp_resources);
+ list_add(&port->list, &tcm->dp_resources);
tb_port_dbg(port, "DP IN resource available\n");
}
}
@@ -255,13 +278,13 @@ static int tb_enable_clx(struct tb_switch *sw)
* this in the future to cover the whole topology if it turns
* out to be beneficial.
*/
- while (sw && sw->config.depth > 1)
+ while (sw && tb_switch_depth(sw) > 1)
sw = tb_switch_parent(sw);
if (!sw)
return 0;
- if (sw->config.depth != 1)
+ if (tb_switch_depth(sw) != 1)
return 0;
/*
@@ -285,14 +308,32 @@ static int tb_enable_clx(struct tb_switch *sw)
return ret == -EOPNOTSUPP ? 0 : ret;
}
-/* Disables CL states up to the host router */
-static void tb_disable_clx(struct tb_switch *sw)
+/**
+ * tb_disable_clx() - Disable CL states up to host router
+ * @sw: Router to start
+ *
+ * Disables CL states from @sw up to the host router. Returns true if
+ * any CL state were disabled. This can be used to figure out whether
+ * the link was setup by us or the boot firmware so we don't
+ * accidentally enable them if they were not enabled during discovery.
+ */
+static bool tb_disable_clx(struct tb_switch *sw)
{
+ bool disabled = false;
+
do {
- if (tb_switch_clx_disable(sw) < 0)
+ int ret;
+
+ ret = tb_switch_clx_disable(sw);
+ if (ret > 0)
+ disabled = true;
+ else if (ret < 0)
tb_sw_warn(sw, "failed to disable CL states\n");
+
sw = tb_switch_parent(sw);
} while (sw);
+
+ return disabled;
}
static int tb_increase_switch_tmu_accuracy(struct device *dev, void *data)
@@ -553,7 +594,7 @@ static struct tb_tunnel *tb_find_first_usb3_tunnel(struct tb *tb,
struct tb_switch *sw;
/* Pick the router that is deepest in the topology */
- if (dst_port->sw->config.depth > src_port->sw->config.depth)
+ if (tb_port_path_direction_downstream(src_port, dst_port))
sw = dst_port->sw;
else
sw = src_port->sw;
@@ -572,133 +613,294 @@ static struct tb_tunnel *tb_find_first_usb3_tunnel(struct tb *tb,
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);
+/**
+ * tb_consumed_usb3_pcie_bandwidth() - Consumed USB3/PCIe bandwidth over a single link
+ * @tb: Domain structure
+ * @src_port: Source protocol adapter
+ * @dst_port: Destination protocol adapter
+ * @port: USB4 port the consumed bandwidth is calculated
+ * @consumed_up: Consumed upsream bandwidth (Mb/s)
+ * @consumed_down: Consumed downstream bandwidth (Mb/s)
+ *
+ * Calculates consumed USB3 and PCIe bandwidth at @port between path
+ * from @src_port to @dst_port. Does not take tunnel starting from
+ * @src_port and ending from @src_port into account.
+ */
+static int tb_consumed_usb3_pcie_bandwidth(struct tb *tb,
+ struct tb_port *src_port,
+ struct tb_port *dst_port,
+ struct tb_port *port,
+ int *consumed_up,
+ int *consumed_down)
+{
+ int pci_consumed_up, pci_consumed_down;
struct tb_tunnel *tunnel;
- struct tb_port *port;
- tb_dbg(tb, "calculating available bandwidth between %llx:%u <-> %llx:%u\n",
- tb_route(src_port->sw), src_port->port, tb_route(dst_port->sw),
- dst_port->port);
+ *consumed_up = *consumed_down = 0;
tunnel = tb_find_first_usb3_tunnel(tb, src_port, dst_port);
if (tunnel && tunnel->src_port != src_port &&
tunnel->dst_port != dst_port) {
- ret = tb_tunnel_consumed_bandwidth(tunnel, &usb3_consumed_up,
- &usb3_consumed_down);
+ int ret;
+
+ ret = tb_tunnel_consumed_bandwidth(tunnel, consumed_up,
+ consumed_down);
if (ret)
return ret;
- } else {
- usb3_consumed_up = 0;
- usb3_consumed_down = 0;
}
- /* Maximum possible bandwidth asymmetric Gen 4 link is 120 Gb/s */
- *available_up = *available_down = 120000;
+ /*
+ * If there is anything reserved for PCIe bulk traffic take it
+ * into account here too.
+ */
+ if (tb_tunnel_reserved_pci(port, &pci_consumed_up, &pci_consumed_down)) {
+ *consumed_up += pci_consumed_up;
+ *consumed_down += pci_consumed_down;
+ }
- /* 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;
+ return 0;
+}
- if (!tb_port_is_null(port))
+/**
+ * tb_consumed_dp_bandwidth() - Consumed DP bandwidth over a single link
+ * @tb: Domain structure
+ * @src_port: Source protocol adapter
+ * @dst_port: Destination protocol adapter
+ * @port: USB4 port the consumed bandwidth is calculated
+ * @consumed_up: Consumed upsream bandwidth (Mb/s)
+ * @consumed_down: Consumed downstream bandwidth (Mb/s)
+ *
+ * Calculates consumed DP bandwidth at @port between path from @src_port
+ * to @dst_port. Does not take tunnel starting from @src_port and ending
+ * from @src_port into account.
+ */
+static int tb_consumed_dp_bandwidth(struct tb *tb,
+ struct tb_port *src_port,
+ struct tb_port *dst_port,
+ struct tb_port *port,
+ int *consumed_up,
+ int *consumed_down)
+{
+ struct tb_cm *tcm = tb_priv(tb);
+ struct tb_tunnel *tunnel;
+ int ret;
+
+ *consumed_up = *consumed_down = 0;
+
+ /*
+ * 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_invalid(tunnel))
continue;
- if (tb_is_upstream_port(port)) {
- link_speed = port->sw->link_speed;
+ if (!tb_tunnel_is_dp(tunnel))
+ continue;
+
+ if (!tb_tunnel_port_on_path(tunnel, port))
+ continue;
+
+ /*
+ * Ignore the DP tunnel between src_port and dst_port
+ * because it is the same tunnel and we may be
+ * re-calculating estimated bandwidth.
+ */
+ if (tunnel->src_port == src_port &&
+ tunnel->dst_port == dst_port)
+ continue;
+
+ ret = tb_tunnel_consumed_bandwidth(tunnel, &dp_consumed_up,
+ &dp_consumed_down);
+ if (ret)
+ return ret;
+
+ *consumed_up += dp_consumed_up;
+ *consumed_down += dp_consumed_down;
+ }
+
+ return 0;
+}
+
+static bool tb_asym_supported(struct tb_port *src_port, struct tb_port *dst_port,
+ struct tb_port *port)
+{
+ bool downstream = tb_port_path_direction_downstream(src_port, dst_port);
+ enum tb_link_width width;
+
+ if (tb_is_upstream_port(port))
+ width = downstream ? TB_LINK_WIDTH_ASYM_RX : TB_LINK_WIDTH_ASYM_TX;
+ else
+ width = downstream ? TB_LINK_WIDTH_ASYM_TX : TB_LINK_WIDTH_ASYM_RX;
+
+ return tb_port_width_supported(port, width);
+}
+
+/**
+ * tb_maximum_bandwidth() - Maximum bandwidth over a single link
+ * @tb: Domain structure
+ * @src_port: Source protocol adapter
+ * @dst_port: Destination protocol adapter
+ * @port: USB4 port the total bandwidth is calculated
+ * @max_up: Maximum upstream bandwidth (Mb/s)
+ * @max_down: Maximum downstream bandwidth (Mb/s)
+ * @include_asym: Include bandwidth if the link is switched from
+ * symmetric to asymmetric
+ *
+ * Returns maximum possible bandwidth in @max_up and @max_down over a
+ * single link at @port. If @include_asym is set then includes the
+ * additional banwdith if the links are transitioned into asymmetric to
+ * direction from @src_port to @dst_port.
+ */
+static int tb_maximum_bandwidth(struct tb *tb, struct tb_port *src_port,
+ struct tb_port *dst_port, struct tb_port *port,
+ int *max_up, int *max_down, bool include_asym)
+{
+ bool downstream = tb_port_path_direction_downstream(src_port, dst_port);
+ int link_speed, link_width, up_bw, down_bw;
+
+ /*
+ * Can include asymmetric, only if it is actually supported by
+ * the lane adapter.
+ */
+ if (!tb_asym_supported(src_port, dst_port, port))
+ include_asym = false;
+
+ if (tb_is_upstream_port(port)) {
+ link_speed = port->sw->link_speed;
+ /*
+ * sw->link_width is from upstream perspective so we use
+ * the opposite for downstream of the host router.
+ */
+ if (port->sw->link_width == TB_LINK_WIDTH_ASYM_TX) {
+ up_bw = link_speed * 3 * 1000;
+ down_bw = link_speed * 1 * 1000;
+ } else if (port->sw->link_width == TB_LINK_WIDTH_ASYM_RX) {
+ up_bw = link_speed * 1 * 1000;
+ down_bw = link_speed * 3 * 1000;
+ } else if (include_asym) {
/*
- * sw->link_width is from upstream perspective
- * so we use the opposite for downstream of the
- * host router.
+ * The link is symmetric at the moment but we
+ * can switch it to asymmetric as needed. Report
+ * this bandwidth as available (even though it
+ * is not yet enabled).
*/
- if (port->sw->link_width == TB_LINK_WIDTH_ASYM_TX) {
- up_bw = link_speed * 3 * 1000;
- down_bw = link_speed * 1 * 1000;
- } else if (port->sw->link_width == TB_LINK_WIDTH_ASYM_RX) {
+ if (downstream) {
up_bw = link_speed * 1 * 1000;
down_bw = link_speed * 3 * 1000;
} else {
- up_bw = link_speed * port->sw->link_width * 1000;
- down_bw = up_bw;
+ up_bw = link_speed * 3 * 1000;
+ down_bw = link_speed * 1 * 1000;
}
} else {
- link_speed = tb_port_get_link_speed(port);
- if (link_speed < 0)
- return link_speed;
-
- link_width = tb_port_get_link_width(port);
- if (link_width < 0)
- return link_width;
-
- if (link_width == TB_LINK_WIDTH_ASYM_TX) {
+ up_bw = link_speed * port->sw->link_width * 1000;
+ down_bw = up_bw;
+ }
+ } else {
+ link_speed = tb_port_get_link_speed(port);
+ if (link_speed < 0)
+ return link_speed;
+
+ link_width = tb_port_get_link_width(port);
+ if (link_width < 0)
+ return link_width;
+
+ if (link_width == TB_LINK_WIDTH_ASYM_TX) {
+ up_bw = link_speed * 1 * 1000;
+ down_bw = link_speed * 3 * 1000;
+ } else if (link_width == TB_LINK_WIDTH_ASYM_RX) {
+ up_bw = link_speed * 3 * 1000;
+ down_bw = link_speed * 1 * 1000;
+ } else if (include_asym) {
+ /*
+ * The link is symmetric at the moment but we
+ * can switch it to asymmetric as needed. Report
+ * this bandwidth as available (even though it
+ * is not yet enabled).
+ */
+ if (downstream) {
up_bw = link_speed * 1 * 1000;
down_bw = link_speed * 3 * 1000;
- } else if (link_width == TB_LINK_WIDTH_ASYM_RX) {
+ } else {
up_bw = link_speed * 3 * 1000;
down_bw = link_speed * 1 * 1000;
- } else {
- up_bw = link_speed * link_width * 1000;
- down_bw = up_bw;
}
+ } else {
+ up_bw = link_speed * link_width * 1000;
+ down_bw = up_bw;
}
+ }
- /* Leave 10% guard band */
- up_bw -= up_bw / 10;
- down_bw -= down_bw / 10;
-
- tb_port_dbg(port, "link total bandwidth %d/%d Mb/s\n", up_bw,
- down_bw);
+ /* Leave 10% guard band */
+ *max_up = up_bw - up_bw / 10;
+ *max_down = down_bw - down_bw / 10;
- /*
- * 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;
+ tb_port_dbg(port, "link maximum bandwidth %d/%d Mb/s\n", *max_up, *max_down);
+ return 0;
+}
- if (tb_tunnel_is_invalid(tunnel))
- continue;
+/**
+ * tb_available_bandwidth() - Available bandwidth for tunneling
+ * @tb: Domain structure
+ * @src_port: Source protocol adapter
+ * @dst_port: Destination protocol adapter
+ * @available_up: Available bandwidth upstream (Mb/s)
+ * @available_down: Available bandwidth downstream (Mb/s)
+ * @include_asym: Include bandwidth if the link is switched from
+ * symmetric to asymmetric
+ *
+ * Calculates maximum available bandwidth for protocol tunneling between
+ * @src_port and @dst_port at the moment. This is minimum of maximum
+ * link bandwidth across all links reduced by currently consumed
+ * bandwidth on that link.
+ *
+ * If @include_asym is true then includes also bandwidth that can be
+ * added when the links are transitioned into asymmetric (but does not
+ * transition the links).
+ */
+static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port,
+ struct tb_port *dst_port, int *available_up,
+ int *available_down, bool include_asym)
+{
+ struct tb_port *port;
+ int ret;
- if (!tb_tunnel_is_dp(tunnel))
- continue;
+ /* Maximum possible bandwidth asymmetric Gen 4 link is 120 Gb/s */
+ *available_up = *available_down = 120000;
- if (!tb_tunnel_port_on_path(tunnel, port))
- continue;
+ /* Find the minimum available bandwidth over all links */
+ tb_for_each_port_on_path(src_port, dst_port, port) {
+ int max_up, max_down, consumed_up, consumed_down;
- /*
- * Ignore the DP tunnel between src_port and
- * dst_port because it is the same tunnel and we
- * may be re-calculating estimated bandwidth.
- */
- if (tunnel->src_port == src_port &&
- tunnel->dst_port == dst_port)
- continue;
+ if (!tb_port_is_null(port))
+ continue;
- ret = tb_tunnel_consumed_bandwidth(tunnel,
- &dp_consumed_up,
- &dp_consumed_down);
- if (ret)
- return ret;
+ ret = tb_maximum_bandwidth(tb, src_port, dst_port, port,
+ &max_up, &max_down, include_asym);
+ if (ret)
+ return ret;
- up_bw -= dp_consumed_up;
- down_bw -= dp_consumed_down;
- }
+ ret = tb_consumed_usb3_pcie_bandwidth(tb, src_port, dst_port,
+ port, &consumed_up,
+ &consumed_down);
+ if (ret)
+ return ret;
+ max_up -= consumed_up;
+ max_down -= 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;
+ ret = tb_consumed_dp_bandwidth(tb, src_port, dst_port, port,
+ &consumed_up, &consumed_down);
+ if (ret)
+ return ret;
+ max_up -= consumed_up;
+ max_down -= consumed_down;
- if (up_bw < *available_up)
- *available_up = up_bw;
- if (down_bw < *available_down)
- *available_down = down_bw;
+ if (max_up < *available_up)
+ *available_up = max_up;
+ if (max_down < *available_down)
+ *available_down = max_down;
}
if (*available_up < 0)
@@ -729,21 +931,21 @@ static void tb_reclaim_usb3_bandwidth(struct tb *tb, struct tb_port *src_port,
if (!tunnel)
return;
- tb_dbg(tb, "reclaiming unused bandwidth for USB3\n");
+ tb_tunnel_dbg(tunnel, "reclaiming unused bandwidth\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);
+ &available_up, &available_down, false);
if (ret) {
- tb_warn(tb, "failed to calculate available bandwidth\n");
+ tb_tunnel_warn(tunnel, "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_dbg(tunnel, "available bandwidth %d/%d Mb/s\n", available_up,
+ available_down);
tb_tunnel_reclaim_available_bandwidth(tunnel, &available_up, &available_down);
}
@@ -794,8 +996,8 @@ static int tb_tunnel_usb3(struct tb *tb, struct tb_switch *sw)
return ret;
}
- ret = tb_available_bandwidth(tb, down, up, &available_up,
- &available_down);
+ ret = tb_available_bandwidth(tb, down, up, &available_up, &available_down,
+ false);
if (ret)
goto err_reclaim;
@@ -856,6 +1058,225 @@ static int tb_create_usb3_tunnels(struct tb_switch *sw)
return 0;
}
+/**
+ * tb_configure_asym() - Transition links to asymmetric if needed
+ * @tb: Domain structure
+ * @src_port: Source adapter to start the transition
+ * @dst_port: Destination adapter
+ * @requested_up: Additional bandwidth (Mb/s) required upstream
+ * @requested_down: Additional bandwidth (Mb/s) required downstream
+ *
+ * Transition links between @src_port and @dst_port into asymmetric, with
+ * three lanes in the direction from @src_port towards @dst_port and one lane
+ * in the opposite direction, if the bandwidth requirements
+ * (requested + currently consumed) on that link exceed @asym_threshold.
+ *
+ * Must be called with available >= requested over all links.
+ */
+static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
+ struct tb_port *dst_port, int requested_up,
+ int requested_down)
+{
+ struct tb_switch *sw;
+ bool clx, downstream;
+ struct tb_port *up;
+ int ret = 0;
+
+ if (!asym_threshold)
+ return 0;
+
+ /* Disable CL states before doing any transitions */
+ downstream = tb_port_path_direction_downstream(src_port, dst_port);
+ /* Pick up router deepest in the hierarchy */
+ if (downstream)
+ sw = dst_port->sw;
+ else
+ sw = src_port->sw;
+
+ clx = tb_disable_clx(sw);
+
+ tb_for_each_upstream_port_on_path(src_port, dst_port, up) {
+ int consumed_up, consumed_down;
+ enum tb_link_width width;
+
+ ret = tb_consumed_dp_bandwidth(tb, src_port, dst_port, up,
+ &consumed_up, &consumed_down);
+ if (ret)
+ break;
+
+ if (downstream) {
+ /*
+ * Downstream so make sure upstream is within the 36G
+ * (40G - guard band 10%), and the requested is above
+ * what the threshold is.
+ */
+ if (consumed_up + requested_up >= TB_ASYM_MIN) {
+ ret = -ENOBUFS;
+ break;
+ }
+ /* Does consumed + requested exceed the threshold */
+ if (consumed_down + requested_down < asym_threshold)
+ continue;
+
+ width = TB_LINK_WIDTH_ASYM_RX;
+ } else {
+ /* Upstream, the opposite of above */
+ if (consumed_down + requested_down >= TB_ASYM_MIN) {
+ ret = -ENOBUFS;
+ break;
+ }
+ if (consumed_up + requested_up < asym_threshold)
+ continue;
+
+ width = TB_LINK_WIDTH_ASYM_TX;
+ }
+
+ if (up->sw->link_width == width)
+ continue;
+
+ if (!tb_port_width_supported(up, width))
+ continue;
+
+ tb_sw_dbg(up->sw, "configuring asymmetric link\n");
+
+ /*
+ * Here requested + consumed > threshold so we need to
+ * transtion the link into asymmetric now.
+ */
+ ret = tb_switch_set_link_width(up->sw, width);
+ if (ret) {
+ tb_sw_warn(up->sw, "failed to set link width\n");
+ break;
+ }
+ }
+
+ /* Re-enable CL states if they were previosly enabled */
+ if (clx)
+ tb_enable_clx(sw);
+
+ return ret;
+}
+
+/**
+ * tb_configure_sym() - Transition links to symmetric if possible
+ * @tb: Domain structure
+ * @src_port: Source adapter to start the transition
+ * @dst_port: Destination adapter
+ * @requested_up: New lower bandwidth request upstream (Mb/s)
+ * @requested_down: New lower bandwidth request downstream (Mb/s)
+ *
+ * Goes over each link from @src_port to @dst_port and tries to
+ * transition the link to symmetric if the currently consumed bandwidth
+ * allows.
+ */
+static int tb_configure_sym(struct tb *tb, struct tb_port *src_port,
+ struct tb_port *dst_port, int requested_up,
+ int requested_down)
+{
+ struct tb_switch *sw;
+ bool clx, downstream;
+ struct tb_port *up;
+ int ret = 0;
+
+ if (!asym_threshold)
+ return 0;
+
+ /* Disable CL states before doing any transitions */
+ downstream = tb_port_path_direction_downstream(src_port, dst_port);
+ /* Pick up router deepest in the hierarchy */
+ if (downstream)
+ sw = dst_port->sw;
+ else
+ sw = src_port->sw;
+
+ clx = tb_disable_clx(sw);
+
+ tb_for_each_upstream_port_on_path(src_port, dst_port, up) {
+ int consumed_up, consumed_down;
+
+ /* Already symmetric */
+ if (up->sw->link_width <= TB_LINK_WIDTH_DUAL)
+ continue;
+ /* Unplugged, no need to switch */
+ if (up->sw->is_unplugged)
+ continue;
+
+ ret = tb_consumed_dp_bandwidth(tb, src_port, dst_port, up,
+ &consumed_up, &consumed_down);
+ if (ret)
+ break;
+
+ if (downstream) {
+ /*
+ * Downstream so we want the consumed_down < threshold.
+ * Upstream traffic should be less than 36G (40G
+ * guard band 10%) as the link was configured asymmetric
+ * already.
+ */
+ if (consumed_down + requested_down >= asym_threshold)
+ continue;
+ } else {
+ if (consumed_up + requested_up >= asym_threshold)
+ continue;
+ }
+
+ if (up->sw->link_width == TB_LINK_WIDTH_DUAL)
+ continue;
+
+ tb_sw_dbg(up->sw, "configuring symmetric link\n");
+
+ ret = tb_switch_set_link_width(up->sw, TB_LINK_WIDTH_DUAL);
+ if (ret) {
+ tb_sw_warn(up->sw, "failed to set link width\n");
+ break;
+ }
+ }
+
+ /* Re-enable CL states if they were previosly enabled */
+ if (clx)
+ tb_enable_clx(sw);
+
+ return ret;
+}
+
+static void tb_configure_link(struct tb_port *down, struct tb_port *up,
+ struct tb_switch *sw)
+{
+ struct tb *tb = sw->tb;
+
+ /* Link the routers using both links if available */
+ down->remote = up;
+ up->remote = down;
+ if (down->dual_link_port && up->dual_link_port) {
+ down->dual_link_port->remote = up->dual_link_port;
+ up->dual_link_port->remote = down->dual_link_port;
+ }
+
+ /*
+ * Enable lane bonding if the link is currently two single lane
+ * links.
+ */
+ if (sw->link_width < TB_LINK_WIDTH_DUAL)
+ tb_switch_set_link_width(sw, TB_LINK_WIDTH_DUAL);
+
+ /*
+ * Device router that comes up as symmetric link is
+ * connected deeper in the hierarchy, we transition the links
+ * above into symmetric if bandwidth allows.
+ */
+ if (tb_switch_depth(sw) > 1 &&
+ tb_port_get_link_generation(up) >= 4 &&
+ up->sw->link_width == TB_LINK_WIDTH_DUAL) {
+ struct tb_port *host_port;
+
+ host_port = tb_port_at(tb_route(sw), tb->root_switch);
+ tb_configure_sym(tb, host_port, up, 0, 0);
+ }
+
+ /* Set the link configured */
+ tb_switch_configure_link(sw);
+}
+
static void tb_scan_port(struct tb_port *port);
/*
@@ -964,19 +1385,9 @@ static void tb_scan_port(struct tb_port *port)
goto out_rpm_put;
}
- /* Link the switches using both links if available */
upstream_port = tb_upstream_port(sw);
- port->remote = upstream_port;
- upstream_port->remote = port;
- if (port->dual_link_port && upstream_port->dual_link_port) {
- port->dual_link_port->remote = upstream_port->dual_link_port;
- upstream_port->dual_link_port->remote = port->dual_link_port;
- }
+ tb_configure_link(port, upstream_port, sw);
- /* Enable lane bonding if supported */
- tb_switch_lane_bonding_enable(sw);
- /* Set the link configured */
- tb_switch_configure_link(sw);
/*
* CL0s and CL1 are enabled and supported together.
* Silently ignore CLx enabling in case CLx is not supported.
@@ -1040,6 +1451,11 @@ static void tb_deactivate_and_free_tunnel(struct tb_tunnel *tunnel)
* deallocated properly.
*/
tb_switch_dealloc_dp_resource(src_port->sw, src_port);
+ /*
+ * If bandwidth on a link is < asym_threshold
+ * transition the link to symmetric.
+ */
+ tb_configure_sym(tb, src_port, dst_port, 0, 0);
/* Now we can allow the domain to runtime suspend again */
pm_runtime_mark_last_busy(&dst_port->sw->dev);
pm_runtime_put_autosuspend(&dst_port->sw->dev);
@@ -1092,7 +1508,8 @@ static void tb_free_unplugged_children(struct tb_switch *sw)
tb_retimer_remove_all(port);
tb_remove_dp_resources(port->remote->sw);
tb_switch_unconfigure_link(port->remote->sw);
- tb_switch_lane_bonding_disable(port->remote->sw);
+ tb_switch_set_link_width(port->remote->sw,
+ TB_LINK_WIDTH_SINGLE);
tb_switch_remove(port->remote->sw);
port->remote = NULL;
if (port->dual_link_port)
@@ -1188,7 +1605,7 @@ tb_recalc_estimated_bandwidth_for_group(struct tb_bandwidth_group *group)
ret = tb_release_unused_usb3_bandwidth(tb,
first_tunnel->src_port, first_tunnel->dst_port);
if (ret) {
- tb_port_warn(in,
+ tb_tunnel_warn(tunnel,
"failed to release unused bandwidth\n");
break;
}
@@ -1196,9 +1613,9 @@ tb_recalc_estimated_bandwidth_for_group(struct tb_bandwidth_group *group)
out = tunnel->dst_port;
ret = tb_available_bandwidth(tb, in, out, &estimated_up,
- &estimated_down);
+ &estimated_down, true);
if (ret) {
- tb_port_warn(in,
+ tb_tunnel_warn(tunnel,
"failed to re-calculate estimated bandwidth\n");
break;
}
@@ -1209,16 +1626,18 @@ tb_recalc_estimated_bandwidth_for_group(struct tb_bandwidth_group *group)
* - available bandwidth along the path
* - bandwidth allocated for USB 3.x but not used.
*/
- tb_port_dbg(in, "re-calculated estimated bandwidth %u/%u Mb/s\n",
- estimated_up, estimated_down);
+ tb_tunnel_dbg(tunnel,
+ "re-calculated estimated bandwidth %u/%u Mb/s\n",
+ estimated_up, estimated_down);
- if (in->sw->config.depth < out->sw->config.depth)
+ if (tb_port_path_direction_downstream(in, out))
estimated_bw = estimated_down;
else
estimated_bw = estimated_up;
if (usb4_dp_port_set_estimated_bandwidth(in, estimated_bw))
- tb_port_warn(in, "failed to update estimated bandwidth\n");
+ tb_tunnel_warn(tunnel,
+ "failed to update estimated bandwidth\n");
}
if (first_tunnel)
@@ -1282,18 +1701,14 @@ static struct tb_port *tb_find_dp_out(struct tb *tb, struct tb_port *in)
return NULL;
}
-static void tb_tunnel_dp(struct tb *tb)
+static bool tb_tunnel_one_dp(struct tb *tb)
{
int available_up, available_down, ret, link_nr;
struct tb_cm *tcm = tb_priv(tb);
struct tb_port *port, *in, *out;
+ int consumed_up, consumed_down;
struct tb_tunnel *tunnel;
- if (!tb_acpi_may_tunnel_dp()) {
- tb_dbg(tb, "DP tunneling disabled, not creating tunnel\n");
- return;
- }
-
/*
* Find pair of inactive DP IN and DP OUT adapters and then
* establish a DP tunnel between them.
@@ -1311,22 +1726,21 @@ static void tb_tunnel_dp(struct tb *tb)
continue;
}
- tb_port_dbg(port, "DP IN available\n");
+ in = port;
+ tb_port_dbg(in, "DP IN available\n");
out = tb_find_dp_out(tb, port);
- if (out) {
- in = port;
+ if (out)
break;
- }
}
if (!in) {
tb_dbg(tb, "no suitable DP IN adapter available, not tunneling\n");
- return;
+ return false;
}
if (!out) {
tb_dbg(tb, "no suitable DP OUT adapter available, not tunneling\n");
- return;
+ return false;
}
/*
@@ -1369,7 +1783,8 @@ static void tb_tunnel_dp(struct tb *tb)
goto err_detach_group;
}
- ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down);
+ ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down,
+ true);
if (ret)
goto err_reclaim_usb;
@@ -1391,6 +1806,13 @@ static void tb_tunnel_dp(struct tb *tb)
list_add_tail(&tunnel->list, &tcm->tunnel_list);
tb_reclaim_usb3_bandwidth(tb, in, out);
+ /*
+ * Transition the links to asymmetric if the consumption exceeds
+ * the threshold.
+ */
+ if (!tb_tunnel_consumed_bandwidth(tunnel, &consumed_up, &consumed_down))
+ tb_configure_asym(tb, in, out, consumed_up, consumed_down);
+
/* Update the domain with the new bandwidth estimation */
tb_recalc_estimated_bandwidth(tb);
@@ -1399,7 +1821,7 @@ static void tb_tunnel_dp(struct tb *tb)
* TMU mode to HiFi for CL0s to work.
*/
tb_increase_tmu_accuracy(tunnel);
- return;
+ return true;
err_free:
tb_tunnel_free(tunnel);
@@ -1414,6 +1836,19 @@ err_rpm_put:
pm_runtime_put_autosuspend(&out->sw->dev);
pm_runtime_mark_last_busy(&in->sw->dev);
pm_runtime_put_autosuspend(&in->sw->dev);
+
+ return false;
+}
+
+static void tb_tunnel_dp(struct tb *tb)
+{
+ if (!tb_acpi_may_tunnel_dp()) {
+ tb_dbg(tb, "DP tunneling disabled, not creating tunnel\n");
+ return;
+ }
+
+ while (tb_tunnel_one_dp(tb))
+ ;
}
static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port)
@@ -1701,7 +2136,8 @@ static void tb_handle_hotplug(struct work_struct *work)
tb_remove_dp_resources(port->remote->sw);
tb_switch_tmu_disable(port->remote->sw);
tb_switch_unconfigure_link(port->remote->sw);
- tb_switch_lane_bonding_disable(port->remote->sw);
+ tb_switch_set_link_width(port->remote->sw,
+ TB_LINK_WIDTH_SINGLE);
tb_switch_remove(port->remote->sw);
port->remote = NULL;
if (port->dual_link_port)
@@ -1781,8 +2217,8 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
in = tunnel->src_port;
out = tunnel->dst_port;
- tb_port_dbg(in, "bandwidth allocated currently %d/%d Mb/s\n",
- allocated_up, allocated_down);
+ tb_tunnel_dbg(tunnel, "bandwidth allocated currently %d/%d Mb/s\n",
+ allocated_up, allocated_down);
/*
* If we get rounded up request from graphics side, say HBR2 x 4
@@ -1823,20 +2259,26 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
else if (requested_down_corrected < 0)
requested_down_corrected = 0;
- tb_port_dbg(in, "corrected bandwidth request %d/%d Mb/s\n",
- requested_up_corrected, requested_down_corrected);
+ tb_tunnel_dbg(tunnel, "corrected bandwidth request %d/%d Mb/s\n",
+ requested_up_corrected, requested_down_corrected);
if ((*requested_up >= 0 && requested_up_corrected > max_up_rounded) ||
(*requested_down >= 0 && requested_down_corrected > max_down_rounded)) {
- tb_port_dbg(in, "bandwidth request too high (%d/%d Mb/s > %d/%d Mb/s)\n",
- requested_up_corrected, requested_down_corrected,
- max_up_rounded, max_down_rounded);
+ tb_tunnel_dbg(tunnel,
+ "bandwidth request too high (%d/%d Mb/s > %d/%d Mb/s)\n",
+ requested_up_corrected, requested_down_corrected,
+ max_up_rounded, max_down_rounded);
return -ENOBUFS;
}
if ((*requested_up >= 0 && requested_up_corrected <= allocated_up) ||
(*requested_down >= 0 && requested_down_corrected <= allocated_down)) {
/*
+ * If bandwidth on a link is < asym_threshold transition
+ * the link to symmetric.
+ */
+ tb_configure_sym(tb, in, out, *requested_up, *requested_down);
+ /*
* If requested bandwidth is less or equal than what is
* currently allocated to that tunnel we simply change
* the reservation of the tunnel. Since all the tunnels
@@ -1861,17 +2303,33 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
* are also in the same group but we use the same function here
* that we use with the normal bandwidth allocation).
*/
- ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down);
+ ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down,
+ true);
if (ret)
goto reclaim;
- tb_port_dbg(in, "bandwidth available for allocation %d/%d Mb/s\n",
- available_up, available_down);
+ tb_tunnel_dbg(tunnel, "bandwidth available for allocation %d/%d Mb/s\n",
+ available_up, available_down);
if ((*requested_up >= 0 && available_up >= requested_up_corrected) ||
(*requested_down >= 0 && available_down >= requested_down_corrected)) {
+ /*
+ * If bandwidth on a link is >= asym_threshold
+ * transition the link to asymmetric.
+ */
+ ret = tb_configure_asym(tb, in, out, *requested_up,
+ *requested_down);
+ if (ret) {
+ tb_configure_sym(tb, in, out, 0, 0);
+ return ret;
+ }
+
ret = tb_tunnel_alloc_bandwidth(tunnel, requested_up,
requested_down);
+ if (ret) {
+ tb_tunnel_warn(tunnel, "failed to allocate bandwidth\n");
+ tb_configure_sym(tb, in, out, 0, 0);
+ }
} else {
ret = -ENOBUFS;
}
@@ -1937,7 +2395,7 @@ static void tb_handle_dp_bandwidth_request(struct work_struct *work)
out = tunnel->dst_port;
- if (in->sw->config.depth < out->sw->config.depth) {
+ if (tb_port_path_direction_downstream(in, out)) {
requested_up = -1;
requested_down = requested_bw;
} else {
@@ -1948,12 +2406,15 @@ static void tb_handle_dp_bandwidth_request(struct work_struct *work)
ret = tb_alloc_dp_bandwidth(tunnel, &requested_up, &requested_down);
if (ret) {
if (ret == -ENOBUFS)
- tb_port_warn(in, "not enough bandwidth available\n");
+ tb_tunnel_warn(tunnel,
+ "not enough bandwidth available\n");
else
- tb_port_warn(in, "failed to change bandwidth allocation\n");
+ tb_tunnel_warn(tunnel,
+ "failed to change bandwidth allocation\n");
} else {
- tb_port_dbg(in, "bandwidth allocation changed to %d/%d Mb/s\n",
- requested_up, requested_down);
+ tb_tunnel_dbg(tunnel,
+ "bandwidth allocation changed to %d/%d Mb/s\n",
+ requested_up, requested_down);
/* Update other clients about the allocation change */
tb_recalc_estimated_bandwidth(tb);
@@ -2181,7 +2642,8 @@ static void tb_restore_children(struct tb_switch *sw)
continue;
if (port->remote) {
- tb_switch_lane_bonding_enable(port->remote->sw);
+ tb_switch_set_link_width(port->remote->sw,
+ port->remote->sw->link_width);
tb_switch_configure_link(port->remote->sw);
tb_restore_children(port->remote->sw);