162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Thunderbolt driver - path/tunnel functionality 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> 662306a36Sopenharmony_ci * Copyright (C) 2019, Intel Corporation 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/slab.h> 1062306a36Sopenharmony_ci#include <linux/errno.h> 1162306a36Sopenharmony_ci#include <linux/delay.h> 1262306a36Sopenharmony_ci#include <linux/ktime.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include "tb.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistatic void tb_dump_hop(const struct tb_path_hop *hop, const struct tb_regs_hop *regs) 1762306a36Sopenharmony_ci{ 1862306a36Sopenharmony_ci const struct tb_port *port = hop->in_port; 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci tb_port_dbg(port, " In HopID: %d => Out port: %d Out HopID: %d\n", 2162306a36Sopenharmony_ci hop->in_hop_index, regs->out_port, regs->next_hop); 2262306a36Sopenharmony_ci tb_port_dbg(port, " Weight: %d Priority: %d Credits: %d Drop: %d\n", 2362306a36Sopenharmony_ci regs->weight, regs->priority, 2462306a36Sopenharmony_ci regs->initial_credits, regs->drop_packages); 2562306a36Sopenharmony_ci tb_port_dbg(port, " Counter enabled: %d Counter index: %d\n", 2662306a36Sopenharmony_ci regs->counter_enable, regs->counter); 2762306a36Sopenharmony_ci tb_port_dbg(port, " Flow Control (In/Eg): %d/%d Shared Buffer (In/Eg): %d/%d\n", 2862306a36Sopenharmony_ci regs->ingress_fc, regs->egress_fc, 2962306a36Sopenharmony_ci regs->ingress_shared_buffer, regs->egress_shared_buffer); 3062306a36Sopenharmony_ci tb_port_dbg(port, " Unknown1: %#x Unknown2: %#x Unknown3: %#x\n", 3162306a36Sopenharmony_ci regs->unknown1, regs->unknown2, regs->unknown3); 3262306a36Sopenharmony_ci} 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic struct tb_port *tb_path_find_dst_port(struct tb_port *src, int src_hopid, 3562306a36Sopenharmony_ci int dst_hopid) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci struct tb_port *port, *out_port = NULL; 3862306a36Sopenharmony_ci struct tb_regs_hop hop; 3962306a36Sopenharmony_ci struct tb_switch *sw; 4062306a36Sopenharmony_ci int i, ret, hopid; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci hopid = src_hopid; 4362306a36Sopenharmony_ci port = src; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci for (i = 0; port && i < TB_PATH_MAX_HOPS; i++) { 4662306a36Sopenharmony_ci sw = port->sw; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hopid, 2); 4962306a36Sopenharmony_ci if (ret) { 5062306a36Sopenharmony_ci tb_port_warn(port, "failed to read path at %d\n", hopid); 5162306a36Sopenharmony_ci return NULL; 5262306a36Sopenharmony_ci } 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci if (!hop.enable) 5562306a36Sopenharmony_ci return NULL; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci out_port = &sw->ports[hop.out_port]; 5862306a36Sopenharmony_ci hopid = hop.next_hop; 5962306a36Sopenharmony_ci port = out_port->remote; 6062306a36Sopenharmony_ci } 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci return out_port && hopid == dst_hopid ? out_port : NULL; 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic int tb_path_find_src_hopid(struct tb_port *src, 6662306a36Sopenharmony_ci const struct tb_port *dst, int dst_hopid) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci struct tb_port *out; 6962306a36Sopenharmony_ci int i; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci for (i = TB_PATH_MIN_HOPID; i <= src->config.max_in_hop_id; i++) { 7262306a36Sopenharmony_ci out = tb_path_find_dst_port(src, i, dst_hopid); 7362306a36Sopenharmony_ci if (out == dst) 7462306a36Sopenharmony_ci return i; 7562306a36Sopenharmony_ci } 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci return 0; 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci/** 8162306a36Sopenharmony_ci * tb_path_discover() - Discover a path 8262306a36Sopenharmony_ci * @src: First input port of a path 8362306a36Sopenharmony_ci * @src_hopid: Starting HopID of a path (%-1 if don't care) 8462306a36Sopenharmony_ci * @dst: Expected destination port of the path (%NULL if don't care) 8562306a36Sopenharmony_ci * @dst_hopid: HopID to the @dst (%-1 if don't care) 8662306a36Sopenharmony_ci * @last: Last port is filled here if not %NULL 8762306a36Sopenharmony_ci * @name: Name of the path 8862306a36Sopenharmony_ci * @alloc_hopid: Allocate HopIDs for the ports 8962306a36Sopenharmony_ci * 9062306a36Sopenharmony_ci * Follows a path starting from @src and @src_hopid to the last output 9162306a36Sopenharmony_ci * port of the path. Allocates HopIDs for the visited ports (if 9262306a36Sopenharmony_ci * @alloc_hopid is true). Call tb_path_free() to release the path and 9362306a36Sopenharmony_ci * allocated HopIDs when the path is not needed anymore. 9462306a36Sopenharmony_ci * 9562306a36Sopenharmony_ci * Note function discovers also incomplete paths so caller should check 9662306a36Sopenharmony_ci * that the @dst port is the expected one. If it is not, the path can be 9762306a36Sopenharmony_ci * cleaned up by calling tb_path_deactivate() before tb_path_free(). 9862306a36Sopenharmony_ci * 9962306a36Sopenharmony_ci * Return: Discovered path on success, %NULL in case of failure 10062306a36Sopenharmony_ci */ 10162306a36Sopenharmony_cistruct tb_path *tb_path_discover(struct tb_port *src, int src_hopid, 10262306a36Sopenharmony_ci struct tb_port *dst, int dst_hopid, 10362306a36Sopenharmony_ci struct tb_port **last, const char *name, 10462306a36Sopenharmony_ci bool alloc_hopid) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct tb_port *out_port; 10762306a36Sopenharmony_ci struct tb_regs_hop hop; 10862306a36Sopenharmony_ci struct tb_path *path; 10962306a36Sopenharmony_ci struct tb_switch *sw; 11062306a36Sopenharmony_ci struct tb_port *p; 11162306a36Sopenharmony_ci size_t num_hops; 11262306a36Sopenharmony_ci int ret, i, h; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci if (src_hopid < 0 && dst) { 11562306a36Sopenharmony_ci /* 11662306a36Sopenharmony_ci * For incomplete paths the intermediate HopID can be 11762306a36Sopenharmony_ci * different from the one used by the protocol adapter 11862306a36Sopenharmony_ci * so in that case find a path that ends on @dst with 11962306a36Sopenharmony_ci * matching @dst_hopid. That should give us the correct 12062306a36Sopenharmony_ci * HopID for the @src. 12162306a36Sopenharmony_ci */ 12262306a36Sopenharmony_ci src_hopid = tb_path_find_src_hopid(src, dst, dst_hopid); 12362306a36Sopenharmony_ci if (!src_hopid) 12462306a36Sopenharmony_ci return NULL; 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci p = src; 12862306a36Sopenharmony_ci h = src_hopid; 12962306a36Sopenharmony_ci num_hops = 0; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci for (i = 0; p && i < TB_PATH_MAX_HOPS; i++) { 13262306a36Sopenharmony_ci sw = p->sw; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci ret = tb_port_read(p, &hop, TB_CFG_HOPS, 2 * h, 2); 13562306a36Sopenharmony_ci if (ret) { 13662306a36Sopenharmony_ci tb_port_warn(p, "failed to read path at %d\n", h); 13762306a36Sopenharmony_ci return NULL; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci /* If the hop is not enabled we got an incomplete path */ 14162306a36Sopenharmony_ci if (!hop.enable) 14262306a36Sopenharmony_ci break; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci out_port = &sw->ports[hop.out_port]; 14562306a36Sopenharmony_ci if (last) 14662306a36Sopenharmony_ci *last = out_port; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci h = hop.next_hop; 14962306a36Sopenharmony_ci p = out_port->remote; 15062306a36Sopenharmony_ci num_hops++; 15162306a36Sopenharmony_ci } 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci path = kzalloc(sizeof(*path), GFP_KERNEL); 15462306a36Sopenharmony_ci if (!path) 15562306a36Sopenharmony_ci return NULL; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci path->name = name; 15862306a36Sopenharmony_ci path->tb = src->sw->tb; 15962306a36Sopenharmony_ci path->path_length = num_hops; 16062306a36Sopenharmony_ci path->activated = true; 16162306a36Sopenharmony_ci path->alloc_hopid = alloc_hopid; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci path->hops = kcalloc(num_hops, sizeof(*path->hops), GFP_KERNEL); 16462306a36Sopenharmony_ci if (!path->hops) { 16562306a36Sopenharmony_ci kfree(path); 16662306a36Sopenharmony_ci return NULL; 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci tb_dbg(path->tb, "discovering %s path starting from %llx:%u\n", 17062306a36Sopenharmony_ci path->name, tb_route(src->sw), src->port); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci p = src; 17362306a36Sopenharmony_ci h = src_hopid; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci for (i = 0; i < num_hops; i++) { 17662306a36Sopenharmony_ci int next_hop; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci sw = p->sw; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci ret = tb_port_read(p, &hop, TB_CFG_HOPS, 2 * h, 2); 18162306a36Sopenharmony_ci if (ret) { 18262306a36Sopenharmony_ci tb_port_warn(p, "failed to read path at %d\n", h); 18362306a36Sopenharmony_ci goto err; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci if (alloc_hopid && tb_port_alloc_in_hopid(p, h, h) < 0) 18762306a36Sopenharmony_ci goto err; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci out_port = &sw->ports[hop.out_port]; 19062306a36Sopenharmony_ci next_hop = hop.next_hop; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci if (alloc_hopid && 19362306a36Sopenharmony_ci tb_port_alloc_out_hopid(out_port, next_hop, next_hop) < 0) { 19462306a36Sopenharmony_ci tb_port_release_in_hopid(p, h); 19562306a36Sopenharmony_ci goto err; 19662306a36Sopenharmony_ci } 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci path->hops[i].in_port = p; 19962306a36Sopenharmony_ci path->hops[i].in_hop_index = h; 20062306a36Sopenharmony_ci path->hops[i].in_counter_index = -1; 20162306a36Sopenharmony_ci path->hops[i].out_port = out_port; 20262306a36Sopenharmony_ci path->hops[i].next_hop_index = next_hop; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci tb_dump_hop(&path->hops[i], &hop); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci h = next_hop; 20762306a36Sopenharmony_ci p = out_port->remote; 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci tb_dbg(path->tb, "path discovery complete\n"); 21162306a36Sopenharmony_ci return path; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_cierr: 21462306a36Sopenharmony_ci tb_port_warn(src, "failed to discover path starting at HopID %d\n", 21562306a36Sopenharmony_ci src_hopid); 21662306a36Sopenharmony_ci tb_path_free(path); 21762306a36Sopenharmony_ci return NULL; 21862306a36Sopenharmony_ci} 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci/** 22162306a36Sopenharmony_ci * tb_path_alloc() - allocate a thunderbolt path between two ports 22262306a36Sopenharmony_ci * @tb: Domain pointer 22362306a36Sopenharmony_ci * @src: Source port of the path 22462306a36Sopenharmony_ci * @src_hopid: HopID used for the first ingress port in the path 22562306a36Sopenharmony_ci * @dst: Destination port of the path 22662306a36Sopenharmony_ci * @dst_hopid: HopID used for the last egress port in the path 22762306a36Sopenharmony_ci * @link_nr: Preferred link if there are dual links on the path 22862306a36Sopenharmony_ci * @name: Name of the path 22962306a36Sopenharmony_ci * 23062306a36Sopenharmony_ci * Creates path between two ports starting with given @src_hopid. Reserves 23162306a36Sopenharmony_ci * HopIDs for each port (they can be different from @src_hopid depending on 23262306a36Sopenharmony_ci * how many HopIDs each port already have reserved). If there are dual 23362306a36Sopenharmony_ci * links on the path, prioritizes using @link_nr but takes into account 23462306a36Sopenharmony_ci * that the lanes may be bonded. 23562306a36Sopenharmony_ci * 23662306a36Sopenharmony_ci * Return: Returns a tb_path on success or NULL on failure. 23762306a36Sopenharmony_ci */ 23862306a36Sopenharmony_cistruct tb_path *tb_path_alloc(struct tb *tb, struct tb_port *src, int src_hopid, 23962306a36Sopenharmony_ci struct tb_port *dst, int dst_hopid, int link_nr, 24062306a36Sopenharmony_ci const char *name) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci struct tb_port *in_port, *out_port, *first_port, *last_port; 24362306a36Sopenharmony_ci int in_hopid, out_hopid; 24462306a36Sopenharmony_ci struct tb_path *path; 24562306a36Sopenharmony_ci size_t num_hops; 24662306a36Sopenharmony_ci int i, ret; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci path = kzalloc(sizeof(*path), GFP_KERNEL); 24962306a36Sopenharmony_ci if (!path) 25062306a36Sopenharmony_ci return NULL; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci first_port = last_port = NULL; 25362306a36Sopenharmony_ci i = 0; 25462306a36Sopenharmony_ci tb_for_each_port_on_path(src, dst, in_port) { 25562306a36Sopenharmony_ci if (!first_port) 25662306a36Sopenharmony_ci first_port = in_port; 25762306a36Sopenharmony_ci last_port = in_port; 25862306a36Sopenharmony_ci i++; 25962306a36Sopenharmony_ci } 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci /* Check that src and dst are reachable */ 26262306a36Sopenharmony_ci if (first_port != src || last_port != dst) { 26362306a36Sopenharmony_ci kfree(path); 26462306a36Sopenharmony_ci return NULL; 26562306a36Sopenharmony_ci } 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci /* Each hop takes two ports */ 26862306a36Sopenharmony_ci num_hops = i / 2; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci path->hops = kcalloc(num_hops, sizeof(*path->hops), GFP_KERNEL); 27162306a36Sopenharmony_ci if (!path->hops) { 27262306a36Sopenharmony_ci kfree(path); 27362306a36Sopenharmony_ci return NULL; 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci path->alloc_hopid = true; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci in_hopid = src_hopid; 27962306a36Sopenharmony_ci out_port = NULL; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci for (i = 0; i < num_hops; i++) { 28262306a36Sopenharmony_ci in_port = tb_next_port_on_path(src, dst, out_port); 28362306a36Sopenharmony_ci if (!in_port) 28462306a36Sopenharmony_ci goto err; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci /* When lanes are bonded primary link must be used */ 28762306a36Sopenharmony_ci if (!in_port->bonded && in_port->dual_link_port && 28862306a36Sopenharmony_ci in_port->link_nr != link_nr) 28962306a36Sopenharmony_ci in_port = in_port->dual_link_port; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci ret = tb_port_alloc_in_hopid(in_port, in_hopid, in_hopid); 29262306a36Sopenharmony_ci if (ret < 0) 29362306a36Sopenharmony_ci goto err; 29462306a36Sopenharmony_ci in_hopid = ret; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci out_port = tb_next_port_on_path(src, dst, in_port); 29762306a36Sopenharmony_ci if (!out_port) 29862306a36Sopenharmony_ci goto err; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci /* 30162306a36Sopenharmony_ci * Pick up right port when going from non-bonded to 30262306a36Sopenharmony_ci * bonded or from bonded to non-bonded. 30362306a36Sopenharmony_ci */ 30462306a36Sopenharmony_ci if (out_port->dual_link_port) { 30562306a36Sopenharmony_ci if (!in_port->bonded && out_port->bonded && 30662306a36Sopenharmony_ci out_port->link_nr) { 30762306a36Sopenharmony_ci /* 30862306a36Sopenharmony_ci * Use primary link when going from 30962306a36Sopenharmony_ci * non-bonded to bonded. 31062306a36Sopenharmony_ci */ 31162306a36Sopenharmony_ci out_port = out_port->dual_link_port; 31262306a36Sopenharmony_ci } else if (!out_port->bonded && 31362306a36Sopenharmony_ci out_port->link_nr != link_nr) { 31462306a36Sopenharmony_ci /* 31562306a36Sopenharmony_ci * If out port is not bonded follow 31662306a36Sopenharmony_ci * link_nr. 31762306a36Sopenharmony_ci */ 31862306a36Sopenharmony_ci out_port = out_port->dual_link_port; 31962306a36Sopenharmony_ci } 32062306a36Sopenharmony_ci } 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if (i == num_hops - 1) 32362306a36Sopenharmony_ci ret = tb_port_alloc_out_hopid(out_port, dst_hopid, 32462306a36Sopenharmony_ci dst_hopid); 32562306a36Sopenharmony_ci else 32662306a36Sopenharmony_ci ret = tb_port_alloc_out_hopid(out_port, -1, -1); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci if (ret < 0) 32962306a36Sopenharmony_ci goto err; 33062306a36Sopenharmony_ci out_hopid = ret; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci path->hops[i].in_hop_index = in_hopid; 33362306a36Sopenharmony_ci path->hops[i].in_port = in_port; 33462306a36Sopenharmony_ci path->hops[i].in_counter_index = -1; 33562306a36Sopenharmony_ci path->hops[i].out_port = out_port; 33662306a36Sopenharmony_ci path->hops[i].next_hop_index = out_hopid; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci in_hopid = out_hopid; 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci path->tb = tb; 34262306a36Sopenharmony_ci path->path_length = num_hops; 34362306a36Sopenharmony_ci path->name = name; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci return path; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_cierr: 34862306a36Sopenharmony_ci tb_path_free(path); 34962306a36Sopenharmony_ci return NULL; 35062306a36Sopenharmony_ci} 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci/** 35362306a36Sopenharmony_ci * tb_path_free() - free a path 35462306a36Sopenharmony_ci * @path: Path to free 35562306a36Sopenharmony_ci * 35662306a36Sopenharmony_ci * Frees a path. The path does not need to be deactivated. 35762306a36Sopenharmony_ci */ 35862306a36Sopenharmony_civoid tb_path_free(struct tb_path *path) 35962306a36Sopenharmony_ci{ 36062306a36Sopenharmony_ci if (path->alloc_hopid) { 36162306a36Sopenharmony_ci int i; 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci for (i = 0; i < path->path_length; i++) { 36462306a36Sopenharmony_ci const struct tb_path_hop *hop = &path->hops[i]; 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci if (hop->in_port) 36762306a36Sopenharmony_ci tb_port_release_in_hopid(hop->in_port, 36862306a36Sopenharmony_ci hop->in_hop_index); 36962306a36Sopenharmony_ci if (hop->out_port) 37062306a36Sopenharmony_ci tb_port_release_out_hopid(hop->out_port, 37162306a36Sopenharmony_ci hop->next_hop_index); 37262306a36Sopenharmony_ci } 37362306a36Sopenharmony_ci } 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci kfree(path->hops); 37662306a36Sopenharmony_ci kfree(path); 37762306a36Sopenharmony_ci} 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_cistatic void __tb_path_deallocate_nfc(struct tb_path *path, int first_hop) 38062306a36Sopenharmony_ci{ 38162306a36Sopenharmony_ci int i, res; 38262306a36Sopenharmony_ci for (i = first_hop; i < path->path_length; i++) { 38362306a36Sopenharmony_ci res = tb_port_add_nfc_credits(path->hops[i].in_port, 38462306a36Sopenharmony_ci -path->hops[i].nfc_credits); 38562306a36Sopenharmony_ci if (res) 38662306a36Sopenharmony_ci tb_port_warn(path->hops[i].in_port, 38762306a36Sopenharmony_ci "nfc credits deallocation failed for hop %d\n", 38862306a36Sopenharmony_ci i); 38962306a36Sopenharmony_ci } 39062306a36Sopenharmony_ci} 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_cistatic int __tb_path_deactivate_hop(struct tb_port *port, int hop_index, 39362306a36Sopenharmony_ci bool clear_fc) 39462306a36Sopenharmony_ci{ 39562306a36Sopenharmony_ci struct tb_regs_hop hop; 39662306a36Sopenharmony_ci ktime_t timeout; 39762306a36Sopenharmony_ci int ret; 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci /* Disable the path */ 40062306a36Sopenharmony_ci ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2); 40162306a36Sopenharmony_ci if (ret) 40262306a36Sopenharmony_ci return ret; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci /* Already disabled */ 40562306a36Sopenharmony_ci if (!hop.enable) 40662306a36Sopenharmony_ci return 0; 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci hop.enable = 0; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci ret = tb_port_write(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2); 41162306a36Sopenharmony_ci if (ret) 41262306a36Sopenharmony_ci return ret; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci /* Wait until it is drained */ 41562306a36Sopenharmony_ci timeout = ktime_add_ms(ktime_get(), 500); 41662306a36Sopenharmony_ci do { 41762306a36Sopenharmony_ci ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2); 41862306a36Sopenharmony_ci if (ret) 41962306a36Sopenharmony_ci return ret; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci if (!hop.pending) { 42262306a36Sopenharmony_ci if (clear_fc) { 42362306a36Sopenharmony_ci /* 42462306a36Sopenharmony_ci * Clear flow control. Protocol adapters 42562306a36Sopenharmony_ci * IFC and ISE bits are vendor defined 42662306a36Sopenharmony_ci * in the USB4 spec so we clear them 42762306a36Sopenharmony_ci * only for pre-USB4 adapters. 42862306a36Sopenharmony_ci */ 42962306a36Sopenharmony_ci if (!tb_switch_is_usb4(port->sw)) { 43062306a36Sopenharmony_ci hop.ingress_fc = 0; 43162306a36Sopenharmony_ci hop.ingress_shared_buffer = 0; 43262306a36Sopenharmony_ci } 43362306a36Sopenharmony_ci hop.egress_fc = 0; 43462306a36Sopenharmony_ci hop.egress_shared_buffer = 0; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci return tb_port_write(port, &hop, TB_CFG_HOPS, 43762306a36Sopenharmony_ci 2 * hop_index, 2); 43862306a36Sopenharmony_ci } 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci return 0; 44162306a36Sopenharmony_ci } 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci usleep_range(10, 20); 44462306a36Sopenharmony_ci } while (ktime_before(ktime_get(), timeout)); 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci return -ETIMEDOUT; 44762306a36Sopenharmony_ci} 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_cistatic void __tb_path_deactivate_hops(struct tb_path *path, int first_hop) 45062306a36Sopenharmony_ci{ 45162306a36Sopenharmony_ci int i, res; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci for (i = first_hop; i < path->path_length; i++) { 45462306a36Sopenharmony_ci res = __tb_path_deactivate_hop(path->hops[i].in_port, 45562306a36Sopenharmony_ci path->hops[i].in_hop_index, 45662306a36Sopenharmony_ci path->clear_fc); 45762306a36Sopenharmony_ci if (res && res != -ENODEV) 45862306a36Sopenharmony_ci tb_port_warn(path->hops[i].in_port, 45962306a36Sopenharmony_ci "hop deactivation failed for hop %d, index %d\n", 46062306a36Sopenharmony_ci i, path->hops[i].in_hop_index); 46162306a36Sopenharmony_ci } 46262306a36Sopenharmony_ci} 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_civoid tb_path_deactivate(struct tb_path *path) 46562306a36Sopenharmony_ci{ 46662306a36Sopenharmony_ci if (!path->activated) { 46762306a36Sopenharmony_ci tb_WARN(path->tb, "trying to deactivate an inactive path\n"); 46862306a36Sopenharmony_ci return; 46962306a36Sopenharmony_ci } 47062306a36Sopenharmony_ci tb_dbg(path->tb, 47162306a36Sopenharmony_ci "deactivating %s path from %llx:%u to %llx:%u\n", 47262306a36Sopenharmony_ci path->name, tb_route(path->hops[0].in_port->sw), 47362306a36Sopenharmony_ci path->hops[0].in_port->port, 47462306a36Sopenharmony_ci tb_route(path->hops[path->path_length - 1].out_port->sw), 47562306a36Sopenharmony_ci path->hops[path->path_length - 1].out_port->port); 47662306a36Sopenharmony_ci __tb_path_deactivate_hops(path, 0); 47762306a36Sopenharmony_ci __tb_path_deallocate_nfc(path, 0); 47862306a36Sopenharmony_ci path->activated = false; 47962306a36Sopenharmony_ci} 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci/** 48262306a36Sopenharmony_ci * tb_path_activate() - activate a path 48362306a36Sopenharmony_ci * @path: Path to activate 48462306a36Sopenharmony_ci * 48562306a36Sopenharmony_ci * Activate a path starting with the last hop and iterating backwards. The 48662306a36Sopenharmony_ci * caller must fill path->hops before calling tb_path_activate(). 48762306a36Sopenharmony_ci * 48862306a36Sopenharmony_ci * Return: Returns 0 on success or an error code on failure. 48962306a36Sopenharmony_ci */ 49062306a36Sopenharmony_ciint tb_path_activate(struct tb_path *path) 49162306a36Sopenharmony_ci{ 49262306a36Sopenharmony_ci int i, res; 49362306a36Sopenharmony_ci enum tb_path_port out_mask, in_mask; 49462306a36Sopenharmony_ci if (path->activated) { 49562306a36Sopenharmony_ci tb_WARN(path->tb, "trying to activate already activated path\n"); 49662306a36Sopenharmony_ci return -EINVAL; 49762306a36Sopenharmony_ci } 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci tb_dbg(path->tb, 50062306a36Sopenharmony_ci "activating %s path from %llx:%u to %llx:%u\n", 50162306a36Sopenharmony_ci path->name, tb_route(path->hops[0].in_port->sw), 50262306a36Sopenharmony_ci path->hops[0].in_port->port, 50362306a36Sopenharmony_ci tb_route(path->hops[path->path_length - 1].out_port->sw), 50462306a36Sopenharmony_ci path->hops[path->path_length - 1].out_port->port); 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci /* Clear counters. */ 50762306a36Sopenharmony_ci for (i = path->path_length - 1; i >= 0; i--) { 50862306a36Sopenharmony_ci if (path->hops[i].in_counter_index == -1) 50962306a36Sopenharmony_ci continue; 51062306a36Sopenharmony_ci res = tb_port_clear_counter(path->hops[i].in_port, 51162306a36Sopenharmony_ci path->hops[i].in_counter_index); 51262306a36Sopenharmony_ci if (res) 51362306a36Sopenharmony_ci goto err; 51462306a36Sopenharmony_ci } 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci /* Add non flow controlled credits. */ 51762306a36Sopenharmony_ci for (i = path->path_length - 1; i >= 0; i--) { 51862306a36Sopenharmony_ci res = tb_port_add_nfc_credits(path->hops[i].in_port, 51962306a36Sopenharmony_ci path->hops[i].nfc_credits); 52062306a36Sopenharmony_ci if (res) { 52162306a36Sopenharmony_ci __tb_path_deallocate_nfc(path, i); 52262306a36Sopenharmony_ci goto err; 52362306a36Sopenharmony_ci } 52462306a36Sopenharmony_ci } 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci /* Activate hops. */ 52762306a36Sopenharmony_ci for (i = path->path_length - 1; i >= 0; i--) { 52862306a36Sopenharmony_ci struct tb_regs_hop hop = { 0 }; 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci /* If it is left active deactivate it first */ 53162306a36Sopenharmony_ci __tb_path_deactivate_hop(path->hops[i].in_port, 53262306a36Sopenharmony_ci path->hops[i].in_hop_index, path->clear_fc); 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci /* dword 0 */ 53562306a36Sopenharmony_ci hop.next_hop = path->hops[i].next_hop_index; 53662306a36Sopenharmony_ci hop.out_port = path->hops[i].out_port->port; 53762306a36Sopenharmony_ci hop.initial_credits = path->hops[i].initial_credits; 53862306a36Sopenharmony_ci hop.unknown1 = 0; 53962306a36Sopenharmony_ci hop.enable = 1; 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci /* dword 1 */ 54262306a36Sopenharmony_ci out_mask = (i == path->path_length - 1) ? 54362306a36Sopenharmony_ci TB_PATH_DESTINATION : TB_PATH_INTERNAL; 54462306a36Sopenharmony_ci in_mask = (i == 0) ? TB_PATH_SOURCE : TB_PATH_INTERNAL; 54562306a36Sopenharmony_ci hop.weight = path->weight; 54662306a36Sopenharmony_ci hop.unknown2 = 0; 54762306a36Sopenharmony_ci hop.priority = path->priority; 54862306a36Sopenharmony_ci hop.drop_packages = path->drop_packages; 54962306a36Sopenharmony_ci hop.counter = path->hops[i].in_counter_index; 55062306a36Sopenharmony_ci hop.counter_enable = path->hops[i].in_counter_index != -1; 55162306a36Sopenharmony_ci hop.ingress_fc = path->ingress_fc_enable & in_mask; 55262306a36Sopenharmony_ci hop.egress_fc = path->egress_fc_enable & out_mask; 55362306a36Sopenharmony_ci hop.ingress_shared_buffer = path->ingress_shared_buffer 55462306a36Sopenharmony_ci & in_mask; 55562306a36Sopenharmony_ci hop.egress_shared_buffer = path->egress_shared_buffer 55662306a36Sopenharmony_ci & out_mask; 55762306a36Sopenharmony_ci hop.unknown3 = 0; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci tb_port_dbg(path->hops[i].in_port, "Writing hop %d\n", i); 56062306a36Sopenharmony_ci tb_dump_hop(&path->hops[i], &hop); 56162306a36Sopenharmony_ci res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS, 56262306a36Sopenharmony_ci 2 * path->hops[i].in_hop_index, 2); 56362306a36Sopenharmony_ci if (res) { 56462306a36Sopenharmony_ci __tb_path_deactivate_hops(path, i); 56562306a36Sopenharmony_ci __tb_path_deallocate_nfc(path, 0); 56662306a36Sopenharmony_ci goto err; 56762306a36Sopenharmony_ci } 56862306a36Sopenharmony_ci } 56962306a36Sopenharmony_ci path->activated = true; 57062306a36Sopenharmony_ci tb_dbg(path->tb, "path activation complete\n"); 57162306a36Sopenharmony_ci return 0; 57262306a36Sopenharmony_cierr: 57362306a36Sopenharmony_ci tb_WARN(path->tb, "path activation failed\n"); 57462306a36Sopenharmony_ci return res; 57562306a36Sopenharmony_ci} 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci/** 57862306a36Sopenharmony_ci * tb_path_is_invalid() - check whether any ports on the path are invalid 57962306a36Sopenharmony_ci * @path: Path to check 58062306a36Sopenharmony_ci * 58162306a36Sopenharmony_ci * Return: Returns true if the path is invalid, false otherwise. 58262306a36Sopenharmony_ci */ 58362306a36Sopenharmony_cibool tb_path_is_invalid(struct tb_path *path) 58462306a36Sopenharmony_ci{ 58562306a36Sopenharmony_ci int i = 0; 58662306a36Sopenharmony_ci for (i = 0; i < path->path_length; i++) { 58762306a36Sopenharmony_ci if (path->hops[i].in_port->sw->is_unplugged) 58862306a36Sopenharmony_ci return true; 58962306a36Sopenharmony_ci if (path->hops[i].out_port->sw->is_unplugged) 59062306a36Sopenharmony_ci return true; 59162306a36Sopenharmony_ci } 59262306a36Sopenharmony_ci return false; 59362306a36Sopenharmony_ci} 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci/** 59662306a36Sopenharmony_ci * tb_path_port_on_path() - Does the path go through certain port 59762306a36Sopenharmony_ci * @path: Path to check 59862306a36Sopenharmony_ci * @port: Switch to check 59962306a36Sopenharmony_ci * 60062306a36Sopenharmony_ci * Goes over all hops on path and checks if @port is any of them. 60162306a36Sopenharmony_ci * Direction does not matter. 60262306a36Sopenharmony_ci */ 60362306a36Sopenharmony_cibool tb_path_port_on_path(const struct tb_path *path, const struct tb_port *port) 60462306a36Sopenharmony_ci{ 60562306a36Sopenharmony_ci int i; 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_ci for (i = 0; i < path->path_length; i++) { 60862306a36Sopenharmony_ci if (path->hops[i].in_port == port || 60962306a36Sopenharmony_ci path->hops[i].out_port == port) 61062306a36Sopenharmony_ci return true; 61162306a36Sopenharmony_ci } 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_ci return false; 61462306a36Sopenharmony_ci} 615