/* Minimum number of credits needed for PCIe path */ #define TB_MIN_PCIE_CREDITS 6U /* * Number of credits we try to allocate for each DMA path if not limited * by the host router baMaxHI.
*/ #define TB_DMA_CREDITS 14 /* Minimum number of credits for DMA path */ #define TB_MIN_DMA_CREDITS 1
#define TB_DMA_PRIORITY 5 #define TB_DMA_WEIGHT 1
/* * Reserve additional bandwidth for USB 3.x and PCIe bulk traffic * according to USB4 v2 Connection Manager guide. This ends up reserving * 1500 Mb/s for PCIe and 3000 Mb/s for USB 3.x taking weights into * account.
*/ #define USB4_V2_PCI_MIN_BANDWIDTH (1500 * TB_PCI_WEIGHT) #define USB4_V2_USB3_MIN_BANDWIDTH (1500 * TB_USB3_WEIGHT)
/* * According to VESA spec, the DPRX negotiation shall compete in 5 * seconds after tunnel is established. Since at least i915 can runtime * suspend if there is nothing connected, and that it polls any new * connections every 10 seconds, we use 12 seconds here. * * These are in ms.
*/ #define TB_DPRX_TIMEOUT 12000 #define TB_DPRX_WAIT_TIMEOUT 25 #define TB_DPRX_POLL_DELAY 50
/** * tb_available_credits() - Available credits for PCIe and DMA * @port: Lane adapter to check * @max_dp_streams: If non-%NULL stores maximum number of simultaneous DP * streams possible through this lane adapter
*/ staticunsignedint tb_available_credits(conststruct tb_port *port,
size_t *max_dp_streams)
{ conststruct tb_switch *sw = port->sw; int credits, usb3, pcie, spare;
size_t ndp;
/** * tb_tunnel_event() - Notify userspace about tunneling event * @tb: Domain where the event occurred * @event: Event that happened * @type: Type of the tunnel in question * @src_port: Tunnel source port (can be %NULL) * @dst_port: Tunnel destination port (can be %NULL) * * Notifies userspace about tunneling @event in the domain. The tunnel * does not need to exist (e.g the tunnel was not activated because * there is not enough bandwidth). If the @src_port and @dst_port are * given fill in full %TUNNEL_DETAILS environment variable. Otherwise * uses the shorter one (just the tunnel type).
*/ void tb_tunnel_event(struct tb *tb, enum tb_tunnel_event event, enum tb_tunnel_type type, conststruct tb_port *src_port, conststruct tb_port *dst_port)
{ char *envp[3] = { NULL };
if (WARN_ON_ONCE(event >= ARRAY_SIZE(tb_event_names))) return; if (WARN_ON_ONCE(type >= ARRAY_SIZE(tb_tunnel_names))) return;
envp[0] = kasprintf(GFP_KERNEL, "TUNNEL_EVENT=%s", tb_event_names[event]); if (!envp[0]) return;
/* Only supported of both routers are at least USB4 v2 */ if ((usb4_switch_version(tunnel->src_port->sw) < 2) ||
(usb4_switch_version(tunnel->dst_port->sw) < 2)) return 0;
if (enable && tb_port_get_link_generation(port) < 4) return 0;
ret = usb4_pci_port_set_ext_encapsulation(tunnel->src_port, enable); if (ret) return ret;
/* * Downstream router could be unplugged so disable of encapsulation * in upstream router is still possible.
*/
ret = usb4_pci_port_set_ext_encapsulation(tunnel->dst_port, enable); if (ret) { if (enable) return ret; if (ret != -ENODEV) return ret;
}
staticint tb_pci_activate(struct tb_tunnel *tunnel, bool activate)
{ int res;
if (activate) {
res = tb_pci_set_ext_encapsulation(tunnel, activate); if (res) return res;
}
if (activate)
res = tb_pci_port_enable(tunnel->dst_port, activate); else
res = tb_pci_port_enable(tunnel->src_port, activate); if (res) return res;
if (activate) {
res = tb_pci_port_enable(tunnel->src_port, activate); if (res) return res;
} else { /* Downstream router could be unplugged */
tb_pci_port_enable(tunnel->dst_port, activate);
}
ret = tb_pci_init_credits(hop); if (ret) return ret;
}
return 0;
}
/** * tb_tunnel_discover_pci() - Discover existing PCIe tunnels * @tb: Pointer to the domain structure * @down: PCIe downstream adapter * @alloc_hopid: Allocate HopIDs from visited ports * * If @down adapter is active, follows the tunnel to the PCIe upstream * adapter and back. Returns the discovered tunnel or %NULL if there was * no tunnel.
*/ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, bool alloc_hopid)
{ struct tb_tunnel *tunnel; struct tb_path *path;
if (!tb_pci_port_is_enabled(down)) return NULL;
tunnel = tb_tunnel_alloc(tb, 2, TB_TUNNEL_PCI); if (!tunnel) return NULL;
/* * Discover both paths even if they are not complete. We will * clean them up by calling tb_tunnel_deactivate() below in that * case.
*/
path = tb_path_discover(down, TB_PCI_HOPID, NULL, -1,
&tunnel->dst_port, "PCIe Up", alloc_hopid); if (!path) { /* Just disable the downstream port */
tb_pci_port_enable(down, false); goto err_free;
}
tunnel->paths[TB_PCI_PATH_UP] = path; if (tb_pci_init_path(tunnel->paths[TB_PCI_PATH_UP])) goto err_free;
/* Validate that the tunnel is complete */ if (!tb_port_is_pcie_up(tunnel->dst_port)) {
tb_port_warn(tunnel->dst_port, "path does not end on a PCIe adapter, cleaning up\n"); goto err_deactivate;
}
if (down != tunnel->src_port) {
tb_tunnel_warn(tunnel, "path is not complete, cleaning up\n"); goto err_deactivate;
}
if (!tb_pci_port_is_enabled(tunnel->dst_port)) {
tb_tunnel_warn(tunnel, "tunnel is not fully activated, cleaning up\n"); goto err_deactivate;
}
/** * tb_tunnel_alloc_pci() - allocate a pci tunnel * @tb: Pointer to the domain structure * @up: PCIe upstream adapter port * @down: PCIe downstream adapter port * * Allocate a PCI tunnel. The ports must be of type TB_TYPE_PCIE_UP and * TB_TYPE_PCIE_DOWN. * * Return: Returns a tb_tunnel on success or NULL on failure.
*/ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, struct tb_port *down)
{ struct tb_tunnel *tunnel; struct tb_path *path;
tunnel = tb_tunnel_alloc(tb, 2, TB_TUNNEL_PCI); if (!tunnel) return NULL;
/** * tb_tunnel_reserved_pci() - Amount of bandwidth to reserve for PCIe * @port: Lane 0 adapter * @reserved_up: Upstream bandwidth in Mb/s to reserve * @reserved_down: Downstream bandwidth in Mb/s to reserve * * Can be called to any connected lane 0 adapter to find out how much * bandwidth needs to be left in reserve for possible PCIe bulk traffic. * Returns true if there is something to be reserved and writes the * amount to @reserved_down/@reserved_up. Otherwise returns false and * does not touch the parameters.
*/ bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up, int *reserved_down)
{ if (WARN_ON_ONCE(!port->remote)) returnfalse;
if (!tb_acpi_may_tunnel_pcie()) returnfalse;
if (tb_port_get_link_generation(port) < 4) returnfalse;
/* Must have PCIe adapters */ if (tb_is_upstream_port(port)) { if (!tb_switch_find_port(port->sw, TB_TYPE_PCIE_UP)) returnfalse; if (!tb_switch_find_port(port->remote->sw, TB_TYPE_PCIE_DOWN)) returnfalse;
} else { if (!tb_switch_find_port(port->sw, TB_TYPE_PCIE_DOWN)) returnfalse; if (!tb_switch_find_port(port->remote->sw, TB_TYPE_PCIE_UP)) returnfalse;
}
tb_port_dbg(port, "reserving %u/%u Mb/s for PCIe\n", *reserved_up,
*reserved_down); returntrue;
}
staticbool tb_dp_is_usb4(conststruct tb_switch *sw)
{ /* Titan Ridge DP adapters need the same treatment as USB4 */ return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw);
}
staticint tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out, int timeout_msec)
{
ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec);
u32 val; int ret;
/* Both ends need to support this */ if (!tb_dp_is_usb4(in->sw) || !tb_dp_is_usb4(out->sw)) return 0;
ret = tb_port_read(out, &val, TB_CFG_PORT,
out->cap_adap + DP_STATUS_CTRL, 1); if (ret) return ret;
val |= DP_STATUS_CTRL_UF | DP_STATUS_CTRL_CMHS;
ret = tb_port_write(out, &val, TB_CFG_PORT,
out->cap_adap + DP_STATUS_CTRL, 1); if (ret) return ret;
do {
ret = tb_port_read(out, &val, TB_CFG_PORT,
out->cap_adap + DP_STATUS_CTRL, 1); if (ret) return ret; if (!(val & DP_STATUS_CTRL_CMHS)) return 0;
usleep_range(100, 150);
} while (ktime_before(ktime_get(), timeout));
return -ETIMEDOUT;
}
/* * Returns maximum possible rate from capability supporting only DP 2.0 * and below. Used when DP BW allocation mode is not enabled.
*/ staticinline u32 tb_dp_cap_get_rate(u32 val)
{
u32 rate = (val & DP_COMMON_CAP_RATE_MASK) >> DP_COMMON_CAP_RATE_SHIFT;
switch (rate) { case DP_COMMON_CAP_RATE_RBR: return 1620; case DP_COMMON_CAP_RATE_HBR: return 2700; case DP_COMMON_CAP_RATE_HBR2: return 5400; case DP_COMMON_CAP_RATE_HBR3: return 8100; default: return 0;
}
}
/* * Returns maximum possible rate from capability supporting DP 2.1 * UHBR20, 13.5 and 10 rates as well. Use only when DP BW allocation * mode is enabled.
*/ staticinline u32 tb_dp_cap_get_rate_ext(u32 val)
{ if (val & DP_COMMON_CAP_UHBR20) return 20000; elseif (val & DP_COMMON_CAP_UHBR13_5) return 13500; elseif (val & DP_COMMON_CAP_UHBR10) return 10000;
/* * Find a combination that can fit into max_bw and does not * exceed the maximum rate and lanes supported by the DP OUT and * DP IN adapters.
*/ for (i = 0; i < ARRAY_SIZE(dp_bw); i++) { if (dp_bw[i][0] > out_rate || dp_bw[i][1] > out_lanes) continue;
if (dp_bw[i][0] > in_rate || dp_bw[i][1] > in_lanes) continue;
/* * Copy DP_LOCAL_CAP register to DP_REMOTE_CAP register for * newer generation hardware.
*/ if (in->sw->generation < 2 || out->sw->generation < 2) return 0;
/* * Perform connection manager handshake between IN and OUT ports * before capabilities exchange can take place.
*/
ret = tb_dp_cm_handshake(in, out, 3000); if (ret) return ret;
/* Read both DP_LOCAL_CAP registers */
ret = tb_port_read(in, &in_dp_cap, TB_CFG_PORT,
in->cap_adap + DP_LOCAL_CAP, 1); if (ret) return ret;
ret = tb_port_read(out, &out_dp_cap, TB_CFG_PORT,
out->cap_adap + DP_LOCAL_CAP, 1); if (ret) return ret;
/* Write IN local caps to OUT remote caps */
ret = tb_port_write(out, &in_dp_cap, TB_CFG_PORT,
out->cap_adap + DP_REMOTE_CAP, 1); if (ret) return ret;
/* * If the tunnel bandwidth is limited (max_bw is set) then see * if we need to reduce bandwidth to fit there.
*/
out_rate = tb_dp_cap_get_rate(out_dp_cap);
out_lanes = tb_dp_cap_get_lanes(out_dp_cap);
bw = tb_dp_bandwidth(out_rate, out_lanes);
tb_tunnel_dbg(tunnel, "DP OUT maximum supported bandwidth %u Mb/s x%u = %u Mb/s\n",
out_rate, out_lanes, bw);
if (tb_tunnel_direction_downstream(tunnel))
max_bw = tunnel->max_down; else
max_bw = tunnel->max_up;
/* * Set new rate and number of lanes before writing it to * the IN port remote caps.
*/
out_dp_cap = tb_dp_cap_set_rate(out_dp_cap, new_rate);
out_dp_cap = tb_dp_cap_set_lanes(out_dp_cap, new_lanes);
}
/* * Titan Ridge does not disable AUX timers when it gets * SET_CONFIG with SET_LTTPR_MODE set. This causes problems with * DP tunneling.
*/ if (tb_route(out->sw) && tb_switch_is_titan_ridge(out->sw)) {
out_dp_cap |= DP_COMMON_CAP_LTTPR_NS;
tb_tunnel_dbg(tunnel, "disabling LTTPR\n");
}
ret = usb4_dp_port_set_cm_bandwidth_mode_supported(in, true); if (ret) return ret;
ret = usb4_dp_port_set_group_id(in, in->group->index); if (ret) return ret;
/* * Get the non-reduced rate and lanes based on the lowest * capability of both adapters.
*/
ret = tb_port_read(in, &in_dp_cap, TB_CFG_PORT,
in->cap_adap + DP_LOCAL_CAP, 1); if (ret) return ret;
ret = tb_port_read(out, &out_dp_cap, TB_CFG_PORT,
out->cap_adap + DP_LOCAL_CAP, 1); if (ret) return ret;
ret = usb4_dp_port_set_nrd(in, rate, lanes); if (ret) return ret;
/* * Pick up granularity that supports maximum possible bandwidth. * For that we use the UHBR rates too.
*/
in_rate = tb_dp_cap_get_rate_ext(in_dp_cap);
out_rate = tb_dp_cap_get_rate_ext(out_dp_cap);
rate = min(in_rate, out_rate);
tmp = tb_dp_bandwidth(rate, lanes);
/* * Returns -EINVAL if granularity above is outside of the * accepted ranges.
*/
ret = usb4_dp_port_set_granularity(in, granularity); if (ret) return ret;
/* * Bandwidth estimation is pretty much what we have in * max_up/down fields. For discovery we just read what the * estimation was set to.
*/ if (tb_tunnel_direction_downstream(tunnel))
estimated_bw = tunnel->max_down; else
estimated_bw = tunnel->max_up;
if (!tunnel->dprx_canceled) {
mutex_lock(&tb->lock); if (tb_dp_is_usb4(tunnel->src_port->sw) &&
tb_dp_wait_dprx(tunnel, TB_DPRX_WAIT_TIMEOUT)) { if (ktime_before(ktime_get(), tunnel->dprx_timeout)) {
queue_delayed_work(tb->wq, &tunnel->dprx_work,
msecs_to_jiffies(TB_DPRX_POLL_DELAY));
mutex_unlock(&tb->lock); return;
}
} else {
tb_tunnel_set_active(tunnel, true);
}
mutex_unlock(&tb->lock);
}
if (tunnel->callback)
tunnel->callback(tunnel, tunnel->callback_data);
tb_tunnel_put(tunnel);
}
staticint tb_dp_dprx_start(struct tb_tunnel *tunnel)
{ /* * Bump up the reference to keep the tunnel around. It will be * dropped in tb_dp_dprx_stop() once the tunnel is deactivated.
*/
tb_tunnel_get(tunnel);
ret = tb_dp_port_enable(tunnel->src_port, active); if (ret) return ret;
if (tb_port_is_dpout(tunnel->dst_port)) {
ret = tb_dp_port_enable(tunnel->dst_port, active); if (ret) return ret;
}
return active ? tb_dp_dprx_start(tunnel) : 0;
}
/** * tb_dp_bandwidth_mode_maximum_bandwidth() - Maximum possible bandwidth * @tunnel: DP tunnel to check * @max_bw_rounded: Maximum bandwidth in Mb/s rounded up to the next granularity * * Returns maximum possible bandwidth for this tunnel in Mb/s.
*/ staticint tb_dp_bandwidth_mode_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_bw_rounded)
{ struct tb_port *in = tunnel->src_port; int ret, rate, lanes, max_bw;
u32 cap;
/* * DP IN adapter DP_LOCAL_CAP gets updated to the lowest AUX * read parameter values so this so we can use this to determine * the maximum possible bandwidth over this link. * * See USB4 v2 spec 1.0 10.4.4.5.
*/
ret = tb_port_read(in, &cap, TB_CFG_PORT,
in->cap_adap + DP_LOCAL_CAP, 1); if (ret) return ret;
staticint tb_dp_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up, int *allocated_down)
{ struct tb_port *in = tunnel->src_port;
/* * If we have already set the allocated bandwidth then use that. * Otherwise we read it from the DPRX.
*/ if (usb4_dp_port_bandwidth_mode_enabled(in) && tunnel->bw_mode) { int ret, allocated_bw, max_bw_rounded;
ret = usb4_dp_port_allocated_bandwidth(in); if (ret < 0) return ret;
allocated_bw = ret;
ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel,
&max_bw_rounded); if (ret < 0) return ret; if (allocated_bw == max_bw_rounded)
allocated_bw = ret;
staticint tb_dp_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up, int *alloc_down)
{ struct tb_port *in = tunnel->src_port; int max_bw_rounded, ret, tmp;
if (!usb4_dp_port_bandwidth_mode_enabled(in)) return -EOPNOTSUPP;
ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel, &max_bw_rounded); if (ret < 0) return ret;
if (tb_tunnel_direction_downstream(tunnel)) {
tmp = min(*alloc_down, max_bw_rounded);
ret = usb4_dp_port_allocate_bandwidth(in, tmp); if (ret) return ret;
*alloc_down = tmp;
*alloc_up = 0;
} else {
tmp = min(*alloc_up, max_bw_rounded);
ret = usb4_dp_port_allocate_bandwidth(in, tmp); if (ret) return ret;
*alloc_down = 0;
*alloc_up = tmp;
}
/* Now we can use BW mode registers to figure out the bandwidth */ /* TODO: need to handle discovery too */
tunnel->bw_mode = true; return 0;
}
/* Read cap from tunnel DP IN */ staticint tb_dp_read_cap(struct tb_tunnel *tunnel, unsignedint cap, u32 *rate,
u32 *lanes)
{ struct tb_port *in = tunnel->src_port;
u32 val; int ret;
switch (cap) { case DP_LOCAL_CAP: case DP_REMOTE_CAP: case DP_COMMON_CAP: break;
default:
tb_tunnel_WARN(tunnel, "invalid capability index %#x\n", cap); return -EINVAL;
}
/* * Read from the copied remote cap so that we take into account * if capabilities were reduced during exchange.
*/
ret = tb_port_read(in, &val, TB_CFG_PORT, in->cap_adap + cap, 1); if (ret) return ret;
staticint tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up, int *consumed_down)
{ conststruct tb_switch *sw = tunnel->src_port->sw;
u32 rate = 0, lanes = 0; int ret;
if (tb_dp_is_usb4(sw)) {
ret = tb_dp_wait_dprx(tunnel, 0); if (ret) { if (ret == -ETIMEDOUT) { /* * While we wait for DPRX complete the * tunnel consumes as much as it had * been reserved initially.
*/
ret = tb_dp_read_cap(tunnel, DP_REMOTE_CAP,
&rate, &lanes); if (ret) return ret;
} else { return ret;
}
} else { /* * On USB4 routers check if the bandwidth allocation * mode is enabled first and then read the bandwidth * through those registers.
*/
ret = tb_dp_bandwidth_mode_consumed_bandwidth(tunnel, consumed_up,
consumed_down); if (ret < 0) { if (ret != -EOPNOTSUPP) return ret;
} elseif (!ret) { return 0;
}
ret = tb_dp_read_cap(tunnel, DP_COMMON_CAP, &rate, &lanes); if (ret) return ret;
}
} elseif (sw->generation >= 2) {
ret = tb_dp_read_cap(tunnel, DP_REMOTE_CAP, &rate, &lanes); if (ret) return ret;
} else { /* No bandwidth management for legacy devices */
*consumed_up = 0;
*consumed_down = 0; return 0;
}
if (tb_port_use_credit_allocation(port)) { unsignedint nfc_credits;
size_t max_dp_streams;
tb_available_credits(port, &max_dp_streams); /* * Read the number of currently allocated NFC credits * from the lane adapter. Since we only use them for DP * tunneling we can use that to figure out how many DP * tunnels already go through the lane adapter.
*/
nfc_credits = port->config.nfc_credits &
ADP_CS_4_NFC_BUFFERS_MASK; if (nfc_credits / sw->min_dp_main_credits > max_dp_streams) return -ENOSPC;
/** * tb_tunnel_discover_dp() - Discover existing Display Port tunnels * @tb: Pointer to the domain structure * @in: DP in adapter * @alloc_hopid: Allocate HopIDs from visited ports * * If @in adapter is active, follows the tunnel to the DP out adapter * and back. Returns the discovered tunnel or %NULL if there was no * tunnel. * * Return: DP tunnel or %NULL if no tunnel found.
*/ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, bool alloc_hopid)
{ struct tb_tunnel *tunnel; struct tb_port *port; struct tb_path *path;
if (!tb_dp_port_is_enabled(in)) return NULL;
tunnel = tb_tunnel_alloc(tb, 3, TB_TUNNEL_DP); if (!tunnel) return NULL;
path = tb_path_discover(tunnel->dst_port, -1, in, TB_DP_AUX_RX_HOPID,
&port, "AUX RX", alloc_hopid); if (!path) goto err_deactivate;
tunnel->paths[TB_DP_AUX_PATH_IN] = path;
tb_dp_init_aux_path(tunnel->paths[TB_DP_AUX_PATH_IN], false);
/* Validate that the tunnel is complete */ if (!tb_port_is_dpout(tunnel->dst_port)) {
tb_port_warn(in, "path does not end on a DP adapter, cleaning up\n"); goto err_deactivate;
}
if (!tb_dp_port_is_enabled(tunnel->dst_port)) goto err_deactivate;
if (!tb_dp_port_hpd_is_active(tunnel->dst_port)) goto err_deactivate;
if (port != tunnel->src_port) {
tb_tunnel_warn(tunnel, "path is not complete, cleaning up\n"); goto err_deactivate;
}
/** * tb_tunnel_alloc_dp() - allocate a Display Port tunnel * @tb: Pointer to the domain structure * @in: DP in adapter port * @out: DP out adapter port * @link_nr: Preferred lane adapter when the link is not bonded * @max_up: Maximum available upstream bandwidth for the DP tunnel. * %0 if no available bandwidth. * @max_down: Maximum available downstream bandwidth for the DP tunnel. * %0 if no available bandwidth. * @callback: Optional callback that is called when the DP tunnel is * fully activated (or there is an error) * @callback_data: Optional data for @callback * * Allocates a tunnel between @in and @out that is capable of tunneling * Display Port traffic. If @callback is not %NULL it will be called * after tb_tunnel_activate() once the tunnel has been fully activated. * It can call tb_tunnel_is_active() to check if activation was * successful (or if it returns %false there was some sort of issue). * The @callback is called without @tb->lock held. * * Return: Returns a tb_tunnel on success or &NULL on failure.
*/ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, struct tb_port *out, int link_nr, int max_up, int max_down, void (*callback)(struct tb_tunnel *, void *), void *callback_data)
{ struct tb_tunnel *tunnel; struct tb_path **paths; struct tb_path *path; bool pm_support;
if (WARN_ON(!in->cap_adap || !out->cap_adap)) return NULL;
tunnel = tb_tunnel_alloc(tb, 3, TB_TUNNEL_DP); if (!tunnel) return NULL;
/* * First lane adapter is the one connected to the remote host. * We don't tunnel other traffic over this link so can use all * the credits (except the ones reserved for control traffic).
*/
hop = &path->hops[0];
tmp = min(tb_usable_credits(hop->in_port), credits);
hop->initial_credits = tmp;
hop->in_port->dma_credits += tmp;
for (i = 1; i < path->path_length; i++) { int ret;
ret = tb_dma_reserve_credits(&path->hops[i], credits); if (ret) return ret;
}
return 0;
}
/* Path from NHI to lane adapter */ staticint tb_dma_init_tx_path(struct tb_path *path, unsignedint credits)
{ struct tb_path_hop *hop;
staticvoid tb_dma_destroy(struct tb_tunnel *tunnel)
{ int i;
for (i = 0; i < tunnel->npaths; i++) { if (!tunnel->paths[i]) continue;
tb_dma_destroy_path(tunnel->paths[i]);
}
}
/** * tb_tunnel_alloc_dma() - allocate a DMA tunnel * @tb: Pointer to the domain structure * @nhi: Host controller port * @dst: Destination null port which the other domain is connected to * @transmit_path: HopID used for transmitting packets * @transmit_ring: NHI ring number used to send packets towards the * other domain. Set to %-1 if TX path is not needed. * @receive_path: HopID used for receiving packets * @receive_ring: NHI ring number used to receive packets from the * other domain. Set to %-1 if RX path is not needed. * * Return: Returns a tb_tunnel on success or NULL on failure.
*/ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, struct tb_port *dst, int transmit_path, int transmit_ring, int receive_path, int receive_ring)
{ struct tb_tunnel *tunnel;
size_t npaths = 0, i = 0; struct tb_path *path; int credits;
/* Ring 0 is reserved for control channel */ if (WARN_ON(!receive_ring || !transmit_ring)) return NULL;
if (receive_ring > 0)
npaths++; if (transmit_ring > 0)
npaths++;
if (WARN_ON(!npaths)) return NULL;
tunnel = tb_tunnel_alloc(tb, npaths, TB_TUNNEL_DMA); if (!tunnel) return NULL;
if (receive_ring > 0) {
path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0, "DMA RX"); if (!path) goto err_free;
tunnel->paths[i++] = path; if (tb_dma_init_rx_path(path, credits)) {
tb_tunnel_dbg(tunnel, "not enough buffers for RX path\n"); goto err_free;
}
}
if (transmit_ring > 0) {
path = tb_path_alloc(tb, nhi, transmit_ring, dst, transmit_path, 0, "DMA TX"); if (!path) goto err_free;
tunnel->paths[i++] = path; if (tb_dma_init_tx_path(path, credits)) {
tb_tunnel_dbg(tunnel, "not enough buffers for TX path\n"); goto err_free;
}
}
return tunnel;
err_free:
tb_tunnel_put(tunnel); return NULL;
}
/** * tb_tunnel_match_dma() - Match DMA tunnel * @tunnel: Tunnel to match * @transmit_path: HopID used for transmitting packets. Pass %-1 to ignore. * @transmit_ring: NHI ring number used to send packets towards the * other domain. Pass %-1 to ignore. * @receive_path: HopID used for receiving packets. Pass %-1 to ignore. * @receive_ring: NHI ring number used to receive packets from the * other domain. Pass %-1 to ignore. * * This function can be used to match specific DMA tunnel, if there are * multiple DMA tunnels going through the same XDomain connection. * Returns true if there is match and false otherwise.
*/ bool tb_tunnel_match_dma(conststruct tb_tunnel *tunnel, int transmit_path, int transmit_ring, int receive_path, int receive_ring)
{ conststruct tb_path *tx_path = NULL, *rx_path = NULL; int i;
if (!receive_ring || !transmit_ring) returnfalse;
for (i = 0; i < tunnel->npaths; i++) { conststruct tb_path *path = tunnel->paths[i];
staticvoid tb_usb3_reclaim_available_bandwidth(struct tb_tunnel *tunnel, int *available_up, int *available_down)
{ int ret, max_rate, allocate_up, allocate_down;
ret = tb_usb3_max_link_rate(tunnel->dst_port, tunnel->src_port); if (ret < 0) {
tb_tunnel_warn(tunnel, "failed to read maximum link rate\n"); return;
}
/* * 90% of the max rate can be allocated for isochronous * transfers.
*/
max_rate = ret * 90 / 100;
/* No need to reclaim if already at maximum */ if (tunnel->allocated_up >= max_rate &&
tunnel->allocated_down >= max_rate) return;
/* Don't go lower than what is already allocated */
allocate_up = min(max_rate, *available_up); if (allocate_up < tunnel->allocated_up)
allocate_up = tunnel->allocated_up;
/* If no changes no need to do more */ if (allocate_up == tunnel->allocated_up &&
allocate_down == tunnel->allocated_down) return;
ret = usb4_usb3_port_allocate_bandwidth(tunnel->src_port, &allocate_up,
&allocate_down); if (ret) {
tb_tunnel_info(tunnel, "failed to allocate bandwidth\n"); return;
}
/** * tb_tunnel_discover_usb3() - Discover existing USB3 tunnels * @tb: Pointer to the domain structure * @down: USB3 downstream adapter * @alloc_hopid: Allocate HopIDs from visited ports * * If @down adapter is active, follows the tunnel to the USB3 upstream * adapter and back. Returns the discovered tunnel or %NULL if there was * no tunnel.
*/ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, bool alloc_hopid)
{ struct tb_tunnel *tunnel; struct tb_path *path;
if (!tb_usb3_port_is_enabled(down)) return NULL;
tunnel = tb_tunnel_alloc(tb, 2, TB_TUNNEL_USB3); if (!tunnel) return NULL;
/* * Discover both paths even if they are not complete. We will * clean them up by calling tb_tunnel_deactivate() below in that * case.
*/
path = tb_path_discover(down, TB_USB3_HOPID, NULL, -1,
&tunnel->dst_port, "USB3 Down", alloc_hopid); if (!path) { /* Just disable the downstream port */
tb_usb3_port_enable(down, false); goto err_free;
}
tunnel->paths[TB_USB3_PATH_DOWN] = path;
tb_usb3_init_path(tunnel->paths[TB_USB3_PATH_DOWN]);
/* Validate that the tunnel is complete */ if (!tb_port_is_usb3_up(tunnel->dst_port)) {
tb_port_warn(tunnel->dst_port, "path does not end on an USB3 adapter, cleaning up\n"); goto err_deactivate;
}
if (down != tunnel->src_port) {
tb_tunnel_warn(tunnel, "path is not complete, cleaning up\n"); goto err_deactivate;
}
if (!tb_usb3_port_is_enabled(tunnel->dst_port)) {
tb_tunnel_warn(tunnel, "tunnel is not fully activated, cleaning up\n"); goto err_deactivate;
}
if (!tb_route(down->sw)) { int ret;
/* * Read the initial bandwidth allocation for the first * hop tunnel.
*/
ret = usb4_usb3_port_allocated_bandwidth(down,
&tunnel->allocated_up, &tunnel->allocated_down); if (ret) goto err_deactivate;
/** * tb_tunnel_alloc_usb3() - allocate a USB3 tunnel * @tb: Pointer to the domain structure * @up: USB3 upstream adapter port * @down: USB3 downstream adapter port * @max_up: Maximum available upstream bandwidth for the USB3 tunnel. * %0 if no available bandwidth. * @max_down: Maximum available downstream bandwidth for the USB3 tunnel. * %0 if no available bandwidth. * * Allocate an USB3 tunnel. The ports must be of type @TB_TYPE_USB3_UP and * @TB_TYPE_USB3_DOWN. * * Return: Returns a tb_tunnel on success or %NULL on failure.
*/ struct tb_tunnel *tb_tunnel_alloc_usb3(struct tb *tb, struct tb_port *up, struct tb_port *down, int max_up, int max_down)
{ struct tb_tunnel *tunnel; struct tb_path *path; int max_rate = 0;
if (!tb_route(down->sw) && (max_up > 0 || max_down > 0)) { /* * For USB3 isochronous transfers, we allow bandwidth which is * not higher than 90% of maximum supported bandwidth by USB3 * adapters.
*/
max_rate = tb_usb3_max_link_rate(down, up); if (max_rate < 0) return NULL;
/** * tb_tunnel_is_invalid - check whether an activated path is still valid * @tunnel: Tunnel to check
*/ bool tb_tunnel_is_invalid(struct tb_tunnel *tunnel)
{ int i;
for (i = 0; i < tunnel->npaths; i++) {
WARN_ON(!tunnel->paths[i]->activated); if (tb_path_is_invalid(tunnel->paths[i])) returntrue;
}
returnfalse;
}
/** * tb_tunnel_activate() - activate a tunnel * @tunnel: Tunnel to activate * * Return: 0 on success and negative errno in case if failure. * Specifically returns %-EINPROGRESS if the tunnel activation is still * in progress (that's for DP tunnels to complete DPRX capabilities * read).
*/ int tb_tunnel_activate(struct tb_tunnel *tunnel)
{ int res, i;
tb_tunnel_dbg(tunnel, "activating\n");
/* * Make sure all paths are properly disabled before enabling * them again.
*/ for (i = 0; i < tunnel->npaths; i++) { if (tunnel->paths[i]->activated) {
tb_path_deactivate(tunnel->paths[i]);
tunnel->paths[i]->activated = false;
}
}
tunnel->state = TB_TUNNEL_ACTIVATING;
if (tunnel->pre_activate) {
res = tunnel->pre_activate(tunnel); if (res) return res;
}
for (i = 0; i < tunnel->npaths; i++) {
res = tb_path_activate(tunnel->paths[i]); if (res) goto err;
}
if (tunnel->activate) {
res = tunnel->activate(tunnel, true); if (res) { if (res == -EINPROGRESS) return res; goto err;
}
}
/** * tb_tunnel_deactivate() - deactivate a tunnel * @tunnel: Tunnel to deactivate
*/ void tb_tunnel_deactivate(struct tb_tunnel *tunnel)
{ int i;
tb_tunnel_dbg(tunnel, "deactivating\n");
if (tunnel->activate)
tunnel->activate(tunnel, false);
for (i = 0; i < tunnel->npaths; i++) { if (tunnel->paths[i] && tunnel->paths[i]->activated)
tb_path_deactivate(tunnel->paths[i]);
}
if (tunnel->post_deactivate)
tunnel->post_deactivate(tunnel);
tb_tunnel_set_active(tunnel, false);
}
/** * tb_tunnel_port_on_path() - Does the tunnel go through port * @tunnel: Tunnel to check * @port: Port to check * * Returns true if @tunnel goes through @port (direction does not matter), * false otherwise.
*/ bool tb_tunnel_port_on_path(conststruct tb_tunnel *tunnel, conststruct tb_port *port)
{ int i;
for (i = 0; i < tunnel->npaths; i++) { if (!tunnel->paths[i]) continue;
if (tb_path_port_on_path(tunnel->paths[i], port)) returntrue;
}
returnfalse;
}
// Is tb_tunnel_activate() called for the tunnel staticbool tb_tunnel_is_activated(conststruct tb_tunnel *tunnel)
{ return tunnel->state == TB_TUNNEL_ACTIVATING || tb_tunnel_is_active(tunnel);
}
/** * tb_tunnel_maximum_bandwidth() - Return maximum possible bandwidth * @tunnel: Tunnel to check * @max_up: Maximum upstream bandwidth in Mb/s * @max_down: Maximum downstream bandwidth in Mb/s * * Returns maximum possible bandwidth this tunnel can go if not limited * by other bandwidth clients. If the tunnel does not support this * returns %-EOPNOTSUPP.
*/ int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up, int *max_down)
{ if (!tb_tunnel_is_active(tunnel)) return -ENOTCONN;
if (tunnel->maximum_bandwidth) return tunnel->maximum_bandwidth(tunnel, max_up, max_down); return -EOPNOTSUPP;
}
/** * tb_tunnel_allocated_bandwidth() - Return bandwidth allocated for the tunnel * @tunnel: Tunnel to check * @allocated_up: Currently allocated upstream bandwidth in Mb/s is stored here * @allocated_down: Currently allocated downstream bandwidth in Mb/s is * stored here * * Returns the bandwidth allocated for the tunnel. This may be higher * than what the tunnel actually consumes.
*/ int tb_tunnel_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up, int *allocated_down)
{ if (!tb_tunnel_is_active(tunnel)) return -ENOTCONN;
if (tunnel->allocated_bandwidth) return tunnel->allocated_bandwidth(tunnel, allocated_up,
allocated_down); return -EOPNOTSUPP;
}
/** * tb_tunnel_alloc_bandwidth() - Change tunnel bandwidth allocation * @tunnel: Tunnel whose bandwidth allocation to change * @alloc_up: New upstream bandwidth in Mb/s * @alloc_down: New downstream bandwidth in Mb/s * * Tries to change tunnel bandwidth allocation. If succeeds returns %0 * and updates @alloc_up and @alloc_down to that was actually allocated * (it may not be the same as passed originally). Returns negative errno * in case of failure.
*/ int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up, int *alloc_down)
{ if (!tb_tunnel_is_active(tunnel)) return -ENOTCONN;
if (tunnel->alloc_bandwidth) { int ret;
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.69 Sekunden
(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.