#define TB_TIMEOUT 100 /* ms */ #define TB_RELEASE_BW_TIMEOUT 10000 /* ms */
/* * How many time bandwidth allocation request from graphics driver is * retried if the DP tunnel is still activating.
*/ #define TB_BW_ALLOC_RETRIES 3
/* * 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 */
staticunsignedint 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 * @tunnel_list: List of active tunnels * @dp_resources: List of available DP resources for DP tunneling * @hotplug_active: tb_handle_hotplug will stop progressing plug * events and exit if this is not set (it needs to * acquire the lock one more time). Used to drain wq * after cfg has been paused. * @remove_work: Work used to remove any unplugged routers after * runtime resume * @groups: Bandwidth groups used in this domain.
*/ struct tb_cm { struct list_head tunnel_list; struct list_head dp_resources; bool hotplug_active; struct delayed_work remove_work; struct tb_bandwidth_group groups[MAX_GROUPS];
};
tb_switch_for_each_port(sw, port) { if (!tb_port_is_dpin(port)) continue;
if (!tb_switch_query_dp_resource(sw, port)) continue;
/* * If DP IN on device router exist, position it at the * beginning of the DP resources list, so that it is used * before DP IN of the host router. This way external GPU(s) * will be prioritized when pairing DP IN to a DP OUT.
*/ if (tb_route(sw))
list_add(&port->list, &tcm->dp_resources); else
list_add_tail(&port->list, &tcm->dp_resources);
tb_port_dbg(port, "DP IN resource available\n");
}
}
list_for_each_entry(tunnel, &tcm->tunnel_list, list) { if (tb_tunnel_is_dp(tunnel))
tb_discover_dp_resource(tb, tunnel->dst_port);
}
}
/* Enables CL states up to host router */ staticint tb_enable_clx(struct tb_switch *sw)
{ struct tb_cm *tcm = tb_priv(sw->tb); unsignedint clx = TB_CL0S | TB_CL1; conststruct tb_tunnel *tunnel; int ret;
/* * Currently only enable CLx for the first link. This is enough * to allow the CPU to save energy at least on Intel hardware * and makes it slightly simpler to implement. We may change * this in the future to cover the whole topology if it turns * out to be beneficial.
*/ while (sw && tb_switch_depth(sw) > 1)
sw = tb_switch_parent(sw);
if (!sw) return 0;
if (tb_switch_depth(sw) != 1) return 0;
/* * If we are re-enabling then check if there is an active DMA * tunnel and in that case bail out.
*/
list_for_each_entry(tunnel, &tcm->tunnel_list, list) { if (tb_tunnel_is_dma(tunnel)) { if (tb_tunnel_port_on_path(tunnel, tb_upstream_port(sw))) return 0;
}
}
/* * Initially try with CL2. If that's not supported by the * topology try with CL0s and CL1 and then give up.
*/
ret = tb_switch_clx_enable(sw, clx | TB_CL2); if (ret == -EOPNOTSUPP)
ret = tb_switch_clx_enable(sw, clx); return ret == -EOPNOTSUPP ? 0 : ret;
}
/** * 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.
*/ staticbool tb_disable_clx(struct tb_switch *sw)
{ bool disabled = false;
do { int ret;
ret = tb_switch_clx_disable(sw); if (ret > 0)
disabled = true; elseif (ret < 0)
tb_sw_warn(sw, "failed to disable CL states\n");
/* * Once first DP tunnel is established we change the TMU * accuracy of first depth child routers (and the host router) * to the highest. This is needed for the DP tunneling to work * but also allows CL0s. * * If both routers are v2 then we don't need to do anything as * they are using enhanced TMU mode that allows all CLx.
*/
sw = tunnel->tb->root_switch;
device_for_each_child(&sw->dev, NULL, tb_increase_switch_tmu_accuracy);
}
staticint tb_enable_tmu(struct tb_switch *sw)
{ int ret;
/* * If both routers at the end of the link are v2 we simply * enable the enhanched uni-directional mode. That covers all * the CL states. For v1 and before we need to use the normal * rate to allow CL1 (when supported). Otherwise we keep the TMU * running at the highest accuracy.
*/
ret = tb_switch_tmu_configure(sw,
TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI); if (ret == -EOPNOTSUPP) { if (tb_switch_clx_is_enabled(sw, TB_CL1)) { /* * Figure out uni-directional HiFi TMU requirements * currently in the domain. If there are no * uni-directional HiFi requirements we can put the TMU * into LowRes mode. * * Deliberately skip bi-directional HiFi links * as these work independently of other links * (and they do not allow any CL states anyway).
*/ if (tb_tmu_hifi_uni_required(sw->tb))
ret = tb_switch_tmu_configure(sw,
TB_SWITCH_TMU_MODE_HIFI_UNI); else
ret = tb_switch_tmu_configure(sw,
TB_SWITCH_TMU_MODE_LOWRES);
} else {
ret = tb_switch_tmu_configure(sw, TB_SWITCH_TMU_MODE_HIFI_BI);
}
/* If not supported, fallback to bi-directional HiFi */ if (ret == -EOPNOTSUPP)
ret = tb_switch_tmu_configure(sw, TB_SWITCH_TMU_MODE_HIFI_BI);
} if (ret) return ret;
/* If it is already enabled in correct mode, don't touch it */ if (tb_switch_tmu_is_enabled(sw)) return 0;
ret = tb_switch_tmu_disable(sw); if (ret) return ret;
ret = tb_switch_tmu_post_time(sw); if (ret) return ret;
/** * tb_find_unused_port() - return the first inactive port on @sw * @sw: Switch to find the port on * @type: Port type to look for
*/ staticstruct tb_port *tb_find_unused_port(struct tb_switch *sw, enum tb_port_type type)
{ struct tb_port *port;
tb_switch_for_each_port(sw, port) { if (tb_is_upstream_port(port)) continue; if (port->config.type != type) continue; if (!port->cap_adap) continue; if (tb_port_is_enabled(port)) continue; return port;
} return NULL;
}
/* Pick the router that is deepest in the topology */ if (tb_port_path_direction_downstream(src_port, dst_port))
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;
/** * 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 USB3 tunnel starting from * @src_port and ending on @src_port into account because that bandwidth is * already included in as part of the "first hop" USB3 tunnel.
*/ staticint 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;
*consumed_up = *consumed_down = 0;
tunnel = tb_find_first_usb3_tunnel(tb, src_port, dst_port); if (tunnel && !tb_port_is_usb3_down(src_port) &&
!tb_port_is_usb3_up(dst_port)) { int ret;
ret = tb_tunnel_consumed_bandwidth(tunnel, consumed_up,
consumed_down); if (ret) return ret;
}
/* * 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;
}
return 0;
}
/** * 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. * * If there is bandwidth reserved for any of the groups between * @src_port and @dst_port (but not yet used) that is also taken into * account in the returned consumed bandwidth.
*/ staticint 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)
{ int group_reserved[MAX_GROUPS] = {}; struct tb_cm *tcm = tb_priv(tb); struct tb_tunnel *tunnel; bool downstream; int i, 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) { conststruct tb_bandwidth_group *group; int dp_consumed_up, dp_consumed_down;
if (tb_tunnel_is_invalid(tunnel)) continue;
if (!tb_tunnel_is_dp(tunnel)) continue;
if (!tb_tunnel_port_on_path(tunnel, port)) continue;
/* * Calculate what is reserved for groups crossing the * same ports only once (as that is reserved for all the * tunnels in the group).
*/
group = tunnel->src_port->group; if (group && group->reserved && !group_reserved[group->index])
group_reserved[group->index] = group->reserved;
/* * 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;
/** * 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.
*/ staticint 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;
} elseif (port->sw->link_width == TB_LINK_WIDTH_ASYM_RX) {
up_bw = link_speed * 1 * 1000;
down_bw = link_speed * 3 * 1000;
} elseif (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 {
up_bw = link_speed * 3 * 1000;
down_bw = link_speed * 1 * 1000;
}
} else {
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;
} elseif (link_width == TB_LINK_WIDTH_ASYM_RX) {
up_bw = link_speed * 3 * 1000;
down_bw = link_speed * 1 * 1000;
} elseif (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 {
up_bw = link_speed * 3 * 1000;
down_bw = link_speed * 1 * 1000;
}
} else {
up_bw = link_speed * link_width * 1000;
down_bw = up_bw;
}
}
/** * 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).
*/ staticint 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;
/* Maximum possible bandwidth asymmetric Gen 4 link is 120 Gb/s */
*available_up = *available_down = 120000;
/* 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;
if (!tb_port_is_null(port)) continue;
ret = tb_maximum_bandwidth(tb, src_port, dst_port, port,
&max_up, &max_down, include_asym); if (ret) return ret;
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;
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 (max_up < *available_up)
*available_up = max_up; if (max_down < *available_down)
*available_down = max_down;
}
if (*available_up < 0)
*available_up = 0; if (*available_down < 0)
*available_down = 0;
/* * 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, false); if (ret) {
tb_tunnel_warn(tunnel, "failed to calculate available bandwidth\n"); return;
}
if (!tb_acpi_may_tunnel_usb3()) {
tb_dbg(tb, "USB3 tunneling disabled, not creating tunnel\n"); return 0;
}
up = tb_switch_find_port(sw, TB_TYPE_USB3_UP); 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.
*/
port = tb_switch_downstream_port(sw);
down = tb_find_usb3_down(parent, port); if (!down) return 0;
if (tb_route(parent)) { struct tb_port *parent_up; /* * Check first that the parent switch has its upstream USB3 * port enabled. Otherwise the chain is not complete and * there is no point setting up a new tunnel.
*/
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;
}
ret = tb_available_bandwidth(tb, down, up, &available_up, &available_down, false); if (ret) goto err_reclaim;
tb_port_dbg(up, "available bandwidth for new USB3 tunnel %d/%d Mb/s\n",
available_up, available_down);
/* * If the available bandwidth is less than 1.5 Gb/s notify * userspace that the connected isochronous device may not work * properly.
*/ if (available_up < 1500 || available_down < 1500)
tb_tunnel_event(tb, TB_TUNNEL_LOW_BANDWIDTH, TB_TUNNEL_USB3,
down, up);
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");
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_put(tunnel);
err_reclaim: if (tb_route(parent))
tb_reclaim_usb3_bandwidth(tb, down, up);
return ret;
}
staticint tb_create_usb3_tunnels(struct tb_switch *sw)
{ struct tb_port *port; int ret;
if (!tb_acpi_may_tunnel_usb3()) return 0;
if (tb_route(sw)) {
ret = tb_tunnel_usb3(sw->tb, sw); if (ret) return ret;
}
tb_switch_for_each_port(sw, port) { if (!tb_port_has_remote(port)) continue;
ret = tb_create_usb3_tunnels(port->remote->sw); if (ret) return ret;
}
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.
*/ staticint tb_configure_asym(struct tb *tb, struct tb_port *src_port, struct tb_port *dst_port, int requested_up, int requested_down)
{ bool clx = false, clx_disabled = false, downstream; struct tb_switch *sw; struct tb_port *up; int ret = 0;
if (!asym_threshold) return 0;
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;
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_up = TB_LINK_WIDTH_ASYM_RX;
width_down = TB_LINK_WIDTH_ASYM_TX;
} 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;
if (!tb_port_width_supported(up, width_up) ||
!tb_port_width_supported(down, width_down)) continue;
/* * Disable CL states before doing any transitions. We * delayed it until now that we know there is a real * transition taking place.
*/ if (!clx_disabled) {
clx = tb_disable_clx(sw);
clx_disabled = true;
}
/* * Here requested + consumed > threshold so we need to * transtion the link into asymmetric now.
*/
ret = tb_switch_set_link_width(up->sw, width_up); 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 * @keep_asym: Keep asymmetric link if preferred * * Goes over each link from @src_port to @dst_port and tries to * transition the link to symmetric if the currently consumed bandwidth * allows and link asymmetric preference is ignored (if @keep_asym is %false).
*/ staticint tb_configure_sym(struct tb *tb, struct tb_port *src_port, struct tb_port *dst_port, bool keep_asym)
{ bool clx = false, clx_disabled = false, downstream; struct tb_switch *sw; struct tb_port *up; int ret = 0;
if (!asym_threshold) return 0;
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;
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 >= asym_threshold) continue;
} else { if (consumed_up >= asym_threshold) continue;
}
if (up->sw->link_width == TB_LINK_WIDTH_DUAL) continue;
/* * Here consumed < threshold so we can transition the * link to symmetric. * * However, if the router prefers asymmetric link we * honor that (unless @keep_asym is %false).
*/ if (keep_asym &&
up->sw->preferred_link_width > TB_LINK_WIDTH_DUAL) {
tb_sw_dbg(up->sw, "keeping preferred asymmetric link\n"); continue;
}
/* Disable CL states before doing any transitions */ if (!clx_disabled) {
clx = tb_disable_clx(sw);
clx_disabled = true;
}
/* 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;
if (port->config.type != TB_TYPE_PORT) return; if (port->dual_link_port && port->link_nr) return; /* * Downstream switch is reachable through two ports. * Only scan on the primary port (link_nr == 0).
*/
if (port->usb4)
pm_runtime_get_sync(&port->usb4->dev);
if (tb_wait_for_port(port, false) <= 0) goto out_rpm_put; if (port->remote) {
tb_port_dbg(port, "port already has a remote\n"); goto out_rpm_put;
}
sw = tb_switch_alloc(port->sw->tb, &port->sw->dev,
tb_downstream_route(port)); if (IS_ERR(sw)) { /* * Make the downstream retimers available even if there * is no router connected.
*/
tb_retimer_scan(port, true);
/* * If there is an error accessing the connected switch * it may be connected to another domain. Also we allow * the other domain to be connected to a max depth switch.
*/ if (PTR_ERR(sw) == -EIO || PTR_ERR(sw) == -EADDRNOTAVAIL)
tb_scan_xdomain(port); goto out_rpm_put;
}
if (tb_switch_configure(sw)) {
tb_switch_put(sw); goto out_rpm_put;
}
/* * If there was previously another domain connected remove it * first.
*/ if (port->xdomain) {
tb_xdomain_remove(port->xdomain);
tb_port_unconfigure_xdomain(port);
port->xdomain = NULL;
}
/* * Do not send uevents until we have discovered all existing * tunnels and know which switches were authorized already by * the boot firmware.
*/ if (!tcm->hotplug_active) {
dev_set_uevent_suppress(&sw->dev, true);
discovery = true;
}
/* * At the moment Thunderbolt 2 and beyond (devices with LC) we * can support runtime PM.
*/
sw->rpm = sw->generation > 1;
if (tb_switch_add(sw)) {
tb_switch_put(sw); goto out_rpm_put;
}
/* * Scan for downstream retimers. We only scan them after the * router has been enumerated to avoid issues with certain * Pluggable devices that expect the host to enumerate them * within certain timeout.
*/
tb_retimer_scan(port, true);
/* * CL0s and CL1 are enabled and supported together. * Silently ignore CLx enabling in case CLx is not supported.
*/ if (discovery)
tb_sw_dbg(sw, "discovery, not touching CL states\n"); elseif (tb_enable_clx(sw))
tb_sw_warn(sw, "failed to enable CL states\n");
if (tb_enable_tmu(sw))
tb_sw_warn(sw, "failed to enable TMU\n");
/* * Configuration valid needs to be set after the TMU has been * enabled for the upstream port of the router so we do it here.
*/
tb_switch_configuration_valid(sw);
/* * Create USB 3.x tunnels only when the switch is plugged to the * domain. This is because we scan the domain also during discovery * and want to discover existing USB 3.x tunnels before we create * any new.
*/ 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);
out_rpm_put: if (port->usb4) {
pm_runtime_mark_last_busy(&port->usb4->dev);
pm_runtime_put_autosuspend(&port->usb4->dev);
}
}
if (!usb4_dp_port_bandwidth_mode_enabled(in)) continue;
tunnel = tb_find_tunnel(tb, TB_TUNNEL_DP, in, NULL); if (WARN_ON(!tunnel)) break;
if (!first_tunnel) { /* * Since USB3 bandwidth is shared by all DP * tunnels under the host router USB4 port, even * if they do not begin from the host router, we * can release USB3 bandwidth just once and not * for each tunnel separately.
*/
first_tunnel = tunnel;
ret = tb_release_unused_usb3_bandwidth(tb,
first_tunnel->src_port, first_tunnel->dst_port); if (ret) {
tb_tunnel_warn(tunnel, "failed to release unused bandwidth\n"); break;
}
}
out = tunnel->dst_port;
ret = tb_available_bandwidth(tb, in, out, &estimated_up,
&estimated_down, true); if (ret) {
tb_tunnel_warn(tunnel, "failed to re-calculate estimated bandwidth\n"); break;
}
/* * Estimated bandwidth includes: * - already allocated bandwidth for the DP tunnel * - available bandwidth along the path * - bandwidth allocated for USB 3.x but not used.
*/ if (tb_tunnel_direction_downstream(tunnel))
estimated_bw = estimated_down; else
estimated_bw = estimated_up;
/* * If there is reserved bandwidth for the group that is * not yet released we report that too.
*/
tb_tunnel_dbg(tunnel, "re-calculated estimated bandwidth %u (+ %u reserved) = %u Mb/s\n",
estimated_bw, group->reserved,
estimated_bw + group->reserved);
if (usb4_dp_port_set_estimated_bandwidth(in,
estimated_bw + group->reserved))
tb_tunnel_warn(tunnel, "failed to update estimated bandwidth\n");
}
if (first_tunnel)
tb_reclaim_usb3_bandwidth(tb, first_tunnel->src_port,
first_tunnel->dst_port);
tb_dbg(tb, "bandwidth estimation for group %u done\n", group->index);
}
/* * All the tunnels in the group go through the same USB4 links * so we find the first one here and pass the IN and OUT * adapters to tb_configure_sym() which now transitions the * links back to symmetric if bandwidth requirement < asym_threshold. * * We do this here to avoid unnecessary transitions (for example * if the graphics released bandwidth for other tunnel in the * same group).
*/
in = list_first_entry(&group->ports, struct tb_port, group_list);
tunnel = tb_find_tunnel(group->tb, TB_TUNNEL_DP, in, NULL); if (tunnel)
tb_configure_sym(group->tb, in, tunnel->dst_port, true);
}
/* * Find all DP tunnels that go through all the same USB4 links * as this one. Because we always setup tunnels the same way we * can just check for the routers at both ends of the tunnels * and if they are the same we have a match.
*/
list_for_each_entry(tunnel, &tcm->tunnel_list, list) { if (!tb_tunnel_is_dp(tunnel)) continue;
if (tunnel->src_port->sw == in->sw &&
tunnel->dst_port->sw == out->sw) {
group = tunnel->src_port->group; if (group) {
tb_bandwidth_group_attach_port(group, in); return group;
}
}
}
/* Pick up next available group then */
group = tb_find_free_bandwidth_group(tcm); if (group)
tb_bandwidth_group_attach_port(group, in); else
tb_port_warn(in, "no available bandwidth groups\n");
return group;
}
staticvoid tb_discover_bandwidth_group(struct tb_cm *tcm, struct tb_port *in, struct tb_port *out)
{ if (usb4_dp_port_bandwidth_mode_enabled(in)) { int index, i;
index = usb4_dp_port_group_id(in); for (i = 0; i < ARRAY_SIZE(tcm->groups); i++) { if (tcm->groups[i].index == index) {
tb_bandwidth_group_attach_port(&tcm->groups[i], in); return;
}
}
}
if (group) {
in->group = NULL;
list_del_init(&in->group_list);
tb_port_dbg(in, "detached from bandwidth group %d\n", group->index);
/* No more tunnels so release the reserved bandwidth if any */ if (list_empty(&group->ports)) {
cancel_delayed_work(&group->release_work);
__release_group_bandwidth(group);
}
}
}
switch (tunnel->type) { case TB_TUNNEL_DP:
tb_detach_bandwidth_group(src_port); /* * In case of DP tunnel make sure the DP IN resource is * 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, true); /* 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);
pm_runtime_mark_last_busy(&src_port->sw->dev);
pm_runtime_put_autosuspend(&src_port->sw->dev);
fallthrough;
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_put(tunnel);
}
/* * tb_free_invalid_tunnels() - destroy tunnels of devices that have gone away
*/ staticvoid tb_free_invalid_tunnels(struct tb *tb)
{ struct tb_cm *tcm = tb_priv(tb); struct tb_tunnel *tunnel; struct tb_tunnel *n;
list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { if (tb_tunnel_is_invalid(tunnel))
tb_deactivate_and_free_tunnel(tunnel);
}
}
/* * To keep plugging devices consistently in the same PCIe * hierarchy, do mapping here for switch downstream PCIe ports.
*/ if (tb_switch_is_usb4(sw)) {
down = usb4_switch_map_pcie_down(sw, port);
} elseif (!tb_route(sw)) { int phy_port = tb_phy_port_from_link(port->port); int index;
/* * Hard-coded Thunderbolt port to PCIe down port mapping * per controller.
*/ if (tb_switch_is_cactus_ridge(sw) ||
tb_switch_is_alpine_ridge(sw))
index = !phy_port ? 6 : 7; elseif (tb_switch_is_falcon_ridge(sw))
index = !phy_port ? 6 : 8; elseif (tb_switch_is_titan_ridge(sw))
index = !phy_port ? 8 : 9; else goto out;
/* Validate the hard-coding */ if (WARN_ON(index > sw->config.max_port_number)) goto out;
down = &sw->ports[index];
}
if (down) { if (WARN_ON(!tb_port_is_pcie_down(down))) goto out; if (tb_pci_port_is_enabled(down)) goto out;
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, "DP OUT in use\n"); continue;
}
/* Needs to be on different routers */ if (in->sw == port->sw) {
tb_port_dbg(port, "skipping DP OUT on same router\n"); continue;
}
tb_port_dbg(port, "DP OUT available\n");
/* * Keep the DP tunnel under the topology starting from * the same host router downstream port.
*/ if (host_port && tb_route(port->sw)) { struct tb_port *p;
p = tb_port_at(tb_route(port->sw), tb->root_switch); if (p != host_port) continue;
}
/* If fail reading tunnel's consumed bandwidth, tear it down */
ret = tb_tunnel_consumed_bandwidth(tunnel, &consumed_up,
&consumed_down); if (ret) {
tb_tunnel_warn(tunnel, "failed to read consumed bandwidth, tearing down\n");
tb_deactivate_and_free_tunnel(tunnel);
} else {
tb_reclaim_usb3_bandwidth(tb, in, out); /* * Transition the links to asymmetric if the * consumption exceeds the threshold.
*/
tb_configure_asym(tb, in, out, consumed_up,
consumed_down); /* * Update the domain with the new bandwidth * estimation.
*/
tb_recalc_estimated_bandwidth(tb); /* * In case of DP tunnel exists, change host * router's 1st children TMU mode to HiFi for * CL0s to work.
*/
tb_increase_tmu_accuracy(tunnel);
}
} else { struct tb_port *in = tunnel->src_port;
/* * This tunnel failed to establish. This means DPRX * negotiation most likely did not complete which * happens either because there is no graphics driver * loaded or not all DP cables where connected to the * discrete router. * * In both cases we remove the DP IN adapter from the * available resources as it is not usable. This will * also tear down the tunnel and try to re-use the * released DP OUT. * * It will be added back only if there is hotplug for * the DP IN again.
*/
tb_tunnel_warn(tunnel, "not active, tearing down\n");
tb_dp_resource_unavailable(tb, in, "DPRX negotiation failed");
}
mutex_unlock(&tb->lock);
/* * This is only applicable to links that are not bonded (so * when Thunderbolt 1 hardware is involved somewhere in the * topology). For these try to share the DP bandwidth between * the two lanes.
*/
link_nr = 1;
list_for_each_entry(tunnel, &tcm->tunnel_list, list) { if (tb_tunnel_is_dp(tunnel)) {
link_nr = 0; break;
}
}
/* * DP stream needs the domain to be active so runtime resume * both ends of the tunnel. * * This should bring the routers in the middle active as well * and keeps the domain from runtime suspending while the DP * tunnel is active.
*/
pm_runtime_get_sync(&in->sw->dev);
pm_runtime_get_sync(&out->sw->dev);
if (tb_switch_alloc_dp_resource(in->sw, in)) {
tb_port_dbg(in, "no resource available for DP IN, not tunneling\n"); goto err_rpm_put;
}
if (!tb_attach_bandwidth_group(tcm, in, out)) goto err_dealloc_dp;
/* 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_detach_group;
}
ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down, true); if (ret) {
tb_tunnel_event(tb, TB_TUNNEL_NO_BANDWIDTH, TB_TUNNEL_DP, in, out); goto err_reclaim_usb;
}
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, link_nr, available_up,
available_down, tb_dp_tunnel_active,
tb_domain_get(tb)); if (!tunnel) {
tb_port_dbg(out, "could not allocate DP tunnel\n"); goto err_reclaim_usb;
}
list_add_tail(&tunnel->list, &tcm->tunnel_list);
ret = tb_tunnel_activate(tunnel); if (ret && ret != -EINPROGRESS) {
tb_port_info(out, "DP tunnel activation failed, aborting\n");
list_del(&tunnel->list); goto err_free;
}
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.
*/
tb_dbg(tb, "looking for DP IN <-> DP OUT pairs:\n");
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, "DP IN in use\n"); continue;
}
in = port;
tb_port_dbg(in, "DP IN available\n");
out = tb_find_dp_out(tb, port); if (out)
tb_tunnel_one_dp(tb, in, out); else
tb_port_dbg(in, "no suitable DP OUT adapter available, not tunneling\n");
}
if (!in)
tb_dbg(tb, "no suitable DP IN adapter available, not tunneling\n");
}
if (!(sw->quirks & QUIRK_KEEP_POWER_IN_DP_REDRIVE)) return;
/* * If we get hot-unplug for the DP IN port of the host router * and the DP resource is not available anymore it means there * is a monitor connected directly to the Type-C port and we are * in "redrive" mode. For this to work we cannot enter RTD3 so * we bump up the runtime PM reference count here.
*/ if (!tb_port_is_dpin(port)) return; if (tb_route(sw)) return; if (!tb_switch_query_dp_resource(sw, port)) {
port->redrive = true;
pm_runtime_get(&sw->dev);
tb_port_dbg(port, "enter redrive mode, keeping powered\n");
}
}
/* * Called during system and runtime suspend to forcefully exit redrive * mode without querying whether the resource is available.
*/ staticvoid tb_switch_exit_redrive(struct tb_switch *sw)
{ struct tb_port *port;
if (!(sw->quirks & QUIRK_KEEP_POWER_IN_DP_REDRIVE)) return;
tb_switch_for_each_port(sw, port) { if (!tb_port_is_dpin(port)) continue;
if (tb_port_is_dpin(port)) {
tb_port_dbg(port, "DP IN resource unavailable: %s\n", reason);
in = port;
out = NULL;
} else {
tb_port_dbg(port, "DP OUT resource unavailable: %s\n", reason);
in = NULL;
out = port;
}
tunnel = tb_find_tunnel(tb, TB_TUNNEL_DP, in, out); if (tunnel)
tb_deactivate_and_free_tunnel(tunnel); else
tb_enter_redrive(port);
list_del_init(&port->list);
/* * See if there is another DP OUT port that can be used for * to create another tunnel.
*/
tb_recalc_estimated_bandwidth(tb);
tb_tunnel_dp(tb);
}
/* * Tear down all DP tunnels and release their resources. They * will be re-established after resume based on plug events.
*/
list_for_each_entry_safe_reverse(tunnel, n, &tcm->tunnel_list, list) { if (tb_tunnel_is_dp(tunnel))
tb_deactivate_and_free_tunnel(tunnel);
}
while (!list_empty(&tcm->dp_resources)) { struct tb_port *port;
port = list_first_entry(&tcm->dp_resources, struct tb_port, list);
list_del_init(&port->list);
}
}
up = tb_switch_find_port(sw, TB_TYPE_PCIE_UP); if (!up) return 0;
/* * Look up available down port. Since we are chaining it should * be found right above this switch.
*/
port = tb_switch_downstream_port(sw);
down = tb_find_pcie_down(tb_switch_parent(sw), port); if (!down) return 0;
tunnel = tb_tunnel_alloc_pci(tb, up, down); if (!tunnel) return -ENOMEM;
/* * PCIe L1 is needed to enable CL0s for Titan Ridge so enable it * here.
*/ if (tb_switch_pcie_l1_enable(sw))
tb_sw_warn(sw, "failed to enable PCIe L1 for Titan Ridge\n");
if (tb_switch_xhci_connect(sw))
tb_sw_warn(sw, "failed to connect xHCI\n");
list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { if (!tb_tunnel_is_dma(tunnel)) continue; if (tunnel->src_port != nhi_port || tunnel->dst_port != dst_port) continue;
if (tb_tunnel_match_dma(tunnel, transmit_path, transmit_ring,
receive_path, receive_ring))
tb_deactivate_and_free_tunnel(tunnel);
}
/* * Try to re-enable CL states now, it is OK if this fails * because we may still have another DMA tunnel active through * the same host router USB4 downstream port.
*/
tb_enable_clx(sw);
}
staticint tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, int receive_ring)
{ if (!xd->is_unplugged) {
mutex_lock(&tb->lock);
__tb_disconnect_xdomain_paths(tb, xd, transmit_path,
transmit_ring, receive_path,
receive_ring);
mutex_unlock(&tb->lock);
} return 0;
}
/* Bring the domain back from sleep if it was suspended */
pm_runtime_get_sync(&tb->dev);
mutex_lock(&tb->lock); if (!tcm->hotplug_active) goto out; /* during init, suspend or shutdown */
sw = tb_switch_find_by_route(tb, ev->route); if (!sw) {
tb_warn(tb, "hotplug event from non existent switch %llx:%x (unplug: %d)\n",
ev->route, ev->port, ev->unplug); goto out;
} if (ev->port > sw->config.max_port_number) {
tb_warn(tb, "hotplug event from non existent port %llx:%x (unplug: %d)\n",
ev->route, ev->port, ev->unplug); goto put_sw;
}
port = &sw->ports[ev->port]; if (tb_is_upstream_port(port)) {
tb_dbg(tb, "hotplug event for upstream port %llx:%x (unplug: %d)\n",
ev->route, ev->port, ev->unplug); goto put_sw;
}
pm_runtime_get_sync(&sw->dev);
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);
tb_free_invalid_tunnels(tb);
tb_remove_dp_resources(port->remote->sw);
tb_switch_tmu_disable(port->remote->sw);
tb_switch_unconfigure_link(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)
port->dual_link_port->remote = NULL; /* Maybe we can create another DP tunnel */
tb_recalc_estimated_bandwidth(tb);
tb_tunnel_dp(tb);
} elseif (port->xdomain) { struct tb_xdomain *xd = tb_xdomain_get(port->xdomain);
tb_port_dbg(port, "xdomain unplugged\n"); /* * Service drivers are unbound during * tb_xdomain_remove() so setting XDomain as * unplugged here prevents deadlock if they call * tb_xdomain_disable_paths(). We will tear down * all the tunnels below.
*/
xd->is_unplugged = true;
tb_xdomain_remove(xd);
port->xdomain = NULL;
__tb_disconnect_xdomain_paths(tb, xd, -1, -1, -1, -1);
tb_xdomain_put(xd);
tb_port_unconfigure_xdomain(port);
} elseif (tb_port_is_dpout(port) || tb_port_is_dpin(port)) {
tb_dp_resource_unavailable(tb, port, "adapter unplug");
} elseif (!port->port) {
tb_sw_dbg(sw, "xHCI disconnect request\n");
tb_switch_xhci_disconnect(sw);
} else {
tb_port_dbg(port, "got unplug event for disconnected port, ignoring\n");
}
} elseif (port->remote) {
tb_port_dbg(port, "got plug event for connected port, ignoring\n");
} elseif (!port->port && sw->authorized) {
tb_sw_dbg(sw, "xHCI connect request\n");
tb_switch_xhci_connect(sw);
} else { if (tb_port_is_null(port)) {
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.23Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.