1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0 2520b6702SAndreas Noever /* 30414bec5SMika Westerberg * Thunderbolt driver - path/tunnel functionality 4520b6702SAndreas Noever * 5520b6702SAndreas Noever * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> 60414bec5SMika Westerberg * Copyright (C) 2019, Intel Corporation 7520b6702SAndreas Noever */ 8520b6702SAndreas Noever 9520b6702SAndreas Noever #include <linux/slab.h> 10520b6702SAndreas Noever #include <linux/errno.h> 1149442693SMika Westerberg #include <linux/delay.h> 1249442693SMika Westerberg #include <linux/ktime.h> 13520b6702SAndreas Noever 14520b6702SAndreas Noever #include "tb.h" 15520b6702SAndreas Noever 166755156aSMika Westerberg static void tb_dump_hop(const struct tb_path_hop *hop, const struct tb_regs_hop *regs) 17520b6702SAndreas Noever { 186755156aSMika Westerberg const struct tb_port *port = hop->in_port; 196755156aSMika Westerberg 206755156aSMika Westerberg tb_port_dbg(port, " In HopID: %d => Out port: %d Out HopID: %d\n", 216755156aSMika Westerberg hop->in_hop_index, regs->out_port, regs->next_hop); 22daa5140fSMika Westerberg tb_port_dbg(port, " Weight: %d Priority: %d Credits: %d Drop: %d\n", 236755156aSMika Westerberg regs->weight, regs->priority, 246755156aSMika Westerberg regs->initial_credits, regs->drop_packages); 25daa5140fSMika Westerberg tb_port_dbg(port, " Counter enabled: %d Counter index: %d\n", 266755156aSMika Westerberg regs->counter_enable, regs->counter); 27daa5140fSMika Westerberg tb_port_dbg(port, " Flow Control (In/Eg): %d/%d Shared Buffer (In/Eg): %d/%d\n", 286755156aSMika Westerberg regs->ingress_fc, regs->egress_fc, 296755156aSMika Westerberg regs->ingress_shared_buffer, regs->egress_shared_buffer); 30daa5140fSMika Westerberg tb_port_dbg(port, " Unknown1: %#x Unknown2: %#x Unknown3: %#x\n", 316755156aSMika Westerberg regs->unknown1, regs->unknown2, regs->unknown3); 32520b6702SAndreas Noever } 33520b6702SAndreas Noever 340414bec5SMika Westerberg static struct tb_port *tb_path_find_dst_port(struct tb_port *src, int src_hopid, 350414bec5SMika Westerberg int dst_hopid) 360414bec5SMika Westerberg { 370414bec5SMika Westerberg struct tb_port *port, *out_port = NULL; 380414bec5SMika Westerberg struct tb_regs_hop hop; 390414bec5SMika Westerberg struct tb_switch *sw; 400414bec5SMika Westerberg int i, ret, hopid; 410414bec5SMika Westerberg 420414bec5SMika Westerberg hopid = src_hopid; 430414bec5SMika Westerberg port = src; 440414bec5SMika Westerberg 450414bec5SMika Westerberg for (i = 0; port && i < TB_PATH_MAX_HOPS; i++) { 460414bec5SMika Westerberg sw = port->sw; 470414bec5SMika Westerberg 480414bec5SMika Westerberg ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hopid, 2); 490414bec5SMika Westerberg if (ret) { 500414bec5SMika Westerberg tb_port_warn(port, "failed to read path at %d\n", hopid); 510414bec5SMika Westerberg return NULL; 520414bec5SMika Westerberg } 530414bec5SMika Westerberg 540414bec5SMika Westerberg if (!hop.enable) 550414bec5SMika Westerberg return NULL; 560414bec5SMika Westerberg 570414bec5SMika Westerberg out_port = &sw->ports[hop.out_port]; 580414bec5SMika Westerberg hopid = hop.next_hop; 590414bec5SMika Westerberg port = out_port->remote; 600414bec5SMika Westerberg } 610414bec5SMika Westerberg 620414bec5SMika Westerberg return out_port && hopid == dst_hopid ? out_port : NULL; 630414bec5SMika Westerberg } 640414bec5SMika Westerberg 650414bec5SMika Westerberg static int tb_path_find_src_hopid(struct tb_port *src, 660414bec5SMika Westerberg const struct tb_port *dst, int dst_hopid) 670414bec5SMika Westerberg { 680414bec5SMika Westerberg struct tb_port *out; 690414bec5SMika Westerberg int i; 700414bec5SMika Westerberg 710414bec5SMika Westerberg for (i = TB_PATH_MIN_HOPID; i <= src->config.max_in_hop_id; i++) { 720414bec5SMika Westerberg out = tb_path_find_dst_port(src, i, dst_hopid); 730414bec5SMika Westerberg if (out == dst) 740414bec5SMika Westerberg return i; 750414bec5SMika Westerberg } 760414bec5SMika Westerberg 770414bec5SMika Westerberg return 0; 780414bec5SMika Westerberg } 790414bec5SMika Westerberg 800414bec5SMika Westerberg /** 810414bec5SMika Westerberg * tb_path_discover() - Discover a path 820414bec5SMika Westerberg * @src: First input port of a path 830414bec5SMika Westerberg * @src_hopid: Starting HopID of a path (%-1 if don't care) 840414bec5SMika Westerberg * @dst: Expected destination port of the path (%NULL if don't care) 850414bec5SMika Westerberg * @dst_hopid: HopID to the @dst (%-1 if don't care) 860414bec5SMika Westerberg * @last: Last port is filled here if not %NULL 870414bec5SMika Westerberg * @name: Name of the path 880414bec5SMika Westerberg * 890414bec5SMika Westerberg * Follows a path starting from @src and @src_hopid to the last output 900414bec5SMika Westerberg * port of the path. Allocates HopIDs for the visited ports. Call 910414bec5SMika Westerberg * tb_path_free() to release the path and allocated HopIDs when the path 920414bec5SMika Westerberg * is not needed anymore. 930414bec5SMika Westerberg * 940414bec5SMika Westerberg * Note function discovers also incomplete paths so caller should check 950414bec5SMika Westerberg * that the @dst port is the expected one. If it is not, the path can be 960414bec5SMika Westerberg * cleaned up by calling tb_path_deactivate() before tb_path_free(). 970414bec5SMika Westerberg * 980414bec5SMika Westerberg * Return: Discovered path on success, %NULL in case of failure 990414bec5SMika Westerberg */ 1000414bec5SMika Westerberg struct tb_path *tb_path_discover(struct tb_port *src, int src_hopid, 1010414bec5SMika Westerberg struct tb_port *dst, int dst_hopid, 1020414bec5SMika Westerberg struct tb_port **last, const char *name) 1030414bec5SMika Westerberg { 1040414bec5SMika Westerberg struct tb_port *out_port; 1050414bec5SMika Westerberg struct tb_regs_hop hop; 1060414bec5SMika Westerberg struct tb_path *path; 1070414bec5SMika Westerberg struct tb_switch *sw; 1080414bec5SMika Westerberg struct tb_port *p; 1090414bec5SMika Westerberg size_t num_hops; 1100414bec5SMika Westerberg int ret, i, h; 1110414bec5SMika Westerberg 1120414bec5SMika Westerberg if (src_hopid < 0 && dst) { 1130414bec5SMika Westerberg /* 1140414bec5SMika Westerberg * For incomplete paths the intermediate HopID can be 1150414bec5SMika Westerberg * different from the one used by the protocol adapter 1160414bec5SMika Westerberg * so in that case find a path that ends on @dst with 1170414bec5SMika Westerberg * matching @dst_hopid. That should give us the correct 1180414bec5SMika Westerberg * HopID for the @src. 1190414bec5SMika Westerberg */ 1200414bec5SMika Westerberg src_hopid = tb_path_find_src_hopid(src, dst, dst_hopid); 1210414bec5SMika Westerberg if (!src_hopid) 1220414bec5SMika Westerberg return NULL; 1230414bec5SMika Westerberg } 1240414bec5SMika Westerberg 1250414bec5SMika Westerberg p = src; 1260414bec5SMika Westerberg h = src_hopid; 1270414bec5SMika Westerberg num_hops = 0; 1280414bec5SMika Westerberg 1290414bec5SMika Westerberg for (i = 0; p && i < TB_PATH_MAX_HOPS; i++) { 1300414bec5SMika Westerberg sw = p->sw; 1310414bec5SMika Westerberg 1320414bec5SMika Westerberg ret = tb_port_read(p, &hop, TB_CFG_HOPS, 2 * h, 2); 1330414bec5SMika Westerberg if (ret) { 1340414bec5SMika Westerberg tb_port_warn(p, "failed to read path at %d\n", h); 1350414bec5SMika Westerberg return NULL; 1360414bec5SMika Westerberg } 1370414bec5SMika Westerberg 1380414bec5SMika Westerberg /* If the hop is not enabled we got an incomplete path */ 1390414bec5SMika Westerberg if (!hop.enable) 1400414bec5SMika Westerberg break; 1410414bec5SMika Westerberg 1420414bec5SMika Westerberg out_port = &sw->ports[hop.out_port]; 1430414bec5SMika Westerberg if (last) 1440414bec5SMika Westerberg *last = out_port; 1450414bec5SMika Westerberg 1460414bec5SMika Westerberg h = hop.next_hop; 1470414bec5SMika Westerberg p = out_port->remote; 1480414bec5SMika Westerberg num_hops++; 1490414bec5SMika Westerberg } 1500414bec5SMika Westerberg 1510414bec5SMika Westerberg path = kzalloc(sizeof(*path), GFP_KERNEL); 1520414bec5SMika Westerberg if (!path) 1530414bec5SMika Westerberg return NULL; 1540414bec5SMika Westerberg 1550414bec5SMika Westerberg path->name = name; 1560414bec5SMika Westerberg path->tb = src->sw->tb; 1570414bec5SMika Westerberg path->path_length = num_hops; 1580414bec5SMika Westerberg path->activated = true; 1590414bec5SMika Westerberg 1600414bec5SMika Westerberg path->hops = kcalloc(num_hops, sizeof(*path->hops), GFP_KERNEL); 1610414bec5SMika Westerberg if (!path->hops) { 1620414bec5SMika Westerberg kfree(path); 1630414bec5SMika Westerberg return NULL; 1640414bec5SMika Westerberg } 1650414bec5SMika Westerberg 1660414bec5SMika Westerberg p = src; 1670414bec5SMika Westerberg h = src_hopid; 1680414bec5SMika Westerberg 1690414bec5SMika Westerberg for (i = 0; i < num_hops; i++) { 1700414bec5SMika Westerberg int next_hop; 1710414bec5SMika Westerberg 1720414bec5SMika Westerberg sw = p->sw; 1730414bec5SMika Westerberg 1740414bec5SMika Westerberg ret = tb_port_read(p, &hop, TB_CFG_HOPS, 2 * h, 2); 1750414bec5SMika Westerberg if (ret) { 1760414bec5SMika Westerberg tb_port_warn(p, "failed to read path at %d\n", h); 1770414bec5SMika Westerberg goto err; 1780414bec5SMika Westerberg } 1790414bec5SMika Westerberg 1800414bec5SMika Westerberg if (tb_port_alloc_in_hopid(p, h, h) < 0) 1810414bec5SMika Westerberg goto err; 1820414bec5SMika Westerberg 1830414bec5SMika Westerberg out_port = &sw->ports[hop.out_port]; 1840414bec5SMika Westerberg next_hop = hop.next_hop; 1850414bec5SMika Westerberg 1860414bec5SMika Westerberg if (tb_port_alloc_out_hopid(out_port, next_hop, next_hop) < 0) { 1870414bec5SMika Westerberg tb_port_release_in_hopid(p, h); 1880414bec5SMika Westerberg goto err; 1890414bec5SMika Westerberg } 1900414bec5SMika Westerberg 1910414bec5SMika Westerberg path->hops[i].in_port = p; 1920414bec5SMika Westerberg path->hops[i].in_hop_index = h; 1930414bec5SMika Westerberg path->hops[i].in_counter_index = -1; 1940414bec5SMika Westerberg path->hops[i].out_port = out_port; 1950414bec5SMika Westerberg path->hops[i].next_hop_index = next_hop; 1960414bec5SMika Westerberg 1970414bec5SMika Westerberg h = next_hop; 1980414bec5SMika Westerberg p = out_port->remote; 1990414bec5SMika Westerberg } 2000414bec5SMika Westerberg 2010414bec5SMika Westerberg return path; 2020414bec5SMika Westerberg 2030414bec5SMika Westerberg err: 2040414bec5SMika Westerberg tb_port_warn(src, "failed to discover path starting at HopID %d\n", 2050414bec5SMika Westerberg src_hopid); 2060414bec5SMika Westerberg tb_path_free(path); 2070414bec5SMika Westerberg return NULL; 2080414bec5SMika Westerberg } 2090414bec5SMika Westerberg 210520b6702SAndreas Noever /** 2118c7acaafSMika Westerberg * tb_path_alloc() - allocate a thunderbolt path between two ports 2128c7acaafSMika Westerberg * @tb: Domain pointer 2138c7acaafSMika Westerberg * @src: Source port of the path 2148c7acaafSMika Westerberg * @src_hopid: HopID used for the first ingress port in the path 2158c7acaafSMika Westerberg * @dst: Destination port of the path 2168c7acaafSMika Westerberg * @dst_hopid: HopID used for the last egress port in the path 2178c7acaafSMika Westerberg * @link_nr: Preferred link if there are dual links on the path 2188c7acaafSMika Westerberg * @name: Name of the path 2198c7acaafSMika Westerberg * 2208c7acaafSMika Westerberg * Creates path between two ports starting with given @src_hopid. Reserves 2218c7acaafSMika Westerberg * HopIDs for each port (they can be different from @src_hopid depending on 2228c7acaafSMika Westerberg * how many HopIDs each port already have reserved). If there are dual 22391c0c120SMika Westerberg * links on the path, prioritizes using @link_nr but takes into account 22491c0c120SMika Westerberg * that the lanes may be bonded. 225520b6702SAndreas Noever * 226520b6702SAndreas Noever * Return: Returns a tb_path on success or NULL on failure. 227520b6702SAndreas Noever */ 2288c7acaafSMika Westerberg struct tb_path *tb_path_alloc(struct tb *tb, struct tb_port *src, int src_hopid, 2298c7acaafSMika Westerberg struct tb_port *dst, int dst_hopid, int link_nr, 2308c7acaafSMika Westerberg const char *name) 231520b6702SAndreas Noever { 2327e897bb7SMika Westerberg struct tb_port *in_port, *out_port, *first_port, *last_port; 2338c7acaafSMika Westerberg int in_hopid, out_hopid; 2348c7acaafSMika Westerberg struct tb_path *path; 2358c7acaafSMika Westerberg size_t num_hops; 2368c7acaafSMika Westerberg int i, ret; 2378c7acaafSMika Westerberg 2388c7acaafSMika Westerberg path = kzalloc(sizeof(*path), GFP_KERNEL); 239520b6702SAndreas Noever if (!path) 240520b6702SAndreas Noever return NULL; 2418c7acaafSMika Westerberg 2427e897bb7SMika Westerberg first_port = last_port = NULL; 243c64c3f3aSMika Westerberg i = 0; 2447e897bb7SMika Westerberg tb_for_each_port_on_path(src, dst, in_port) { 2457e897bb7SMika Westerberg if (!first_port) 2467e897bb7SMika Westerberg first_port = in_port; 2477e897bb7SMika Westerberg last_port = in_port; 248c64c3f3aSMika Westerberg i++; 2497e897bb7SMika Westerberg } 2507e897bb7SMika Westerberg 2517e897bb7SMika Westerberg /* Check that src and dst are reachable */ 2527e897bb7SMika Westerberg if (first_port != src || last_port != dst) { 2537e897bb7SMika Westerberg kfree(path); 2547e897bb7SMika Westerberg return NULL; 2557e897bb7SMika Westerberg } 256c64c3f3aSMika Westerberg 257c64c3f3aSMika Westerberg /* Each hop takes two ports */ 258c64c3f3aSMika Westerberg num_hops = i / 2; 2598c7acaafSMika Westerberg 260520b6702SAndreas Noever path->hops = kcalloc(num_hops, sizeof(*path->hops), GFP_KERNEL); 261520b6702SAndreas Noever if (!path->hops) { 262520b6702SAndreas Noever kfree(path); 263520b6702SAndreas Noever return NULL; 264520b6702SAndreas Noever } 2658c7acaafSMika Westerberg 2668c7acaafSMika Westerberg in_hopid = src_hopid; 2678c7acaafSMika Westerberg out_port = NULL; 2688c7acaafSMika Westerberg 2698c7acaafSMika Westerberg for (i = 0; i < num_hops; i++) { 2708c7acaafSMika Westerberg in_port = tb_next_port_on_path(src, dst, out_port); 2718c7acaafSMika Westerberg if (!in_port) 2728c7acaafSMika Westerberg goto err; 2738c7acaafSMika Westerberg 27491c0c120SMika Westerberg /* When lanes are bonded primary link must be used */ 27591c0c120SMika Westerberg if (!in_port->bonded && in_port->dual_link_port && 27691c0c120SMika Westerberg in_port->link_nr != link_nr) 2778c7acaafSMika Westerberg in_port = in_port->dual_link_port; 2788c7acaafSMika Westerberg 2798c7acaafSMika Westerberg ret = tb_port_alloc_in_hopid(in_port, in_hopid, in_hopid); 2808c7acaafSMika Westerberg if (ret < 0) 2818c7acaafSMika Westerberg goto err; 2828c7acaafSMika Westerberg in_hopid = ret; 2838c7acaafSMika Westerberg 2848c7acaafSMika Westerberg out_port = tb_next_port_on_path(src, dst, in_port); 2858c7acaafSMika Westerberg if (!out_port) 2868c7acaafSMika Westerberg goto err; 2878c7acaafSMika Westerberg 28891c0c120SMika Westerberg /* 28991c0c120SMika Westerberg * Pick up right port when going from non-bonded to 29091c0c120SMika Westerberg * bonded or from bonded to non-bonded. 29191c0c120SMika Westerberg */ 29291c0c120SMika Westerberg if (out_port->dual_link_port) { 29391c0c120SMika Westerberg if (!in_port->bonded && out_port->bonded && 29491c0c120SMika Westerberg out_port->link_nr) { 29591c0c120SMika Westerberg /* 29691c0c120SMika Westerberg * Use primary link when going from 29791c0c120SMika Westerberg * non-bonded to bonded. 29891c0c120SMika Westerberg */ 2998c7acaafSMika Westerberg out_port = out_port->dual_link_port; 30091c0c120SMika Westerberg } else if (!out_port->bonded && 30191c0c120SMika Westerberg out_port->link_nr != link_nr) { 30291c0c120SMika Westerberg /* 30391c0c120SMika Westerberg * If out port is not bonded follow 30491c0c120SMika Westerberg * link_nr. 30591c0c120SMika Westerberg */ 30691c0c120SMika Westerberg out_port = out_port->dual_link_port; 30791c0c120SMika Westerberg } 30891c0c120SMika Westerberg } 3098c7acaafSMika Westerberg 3108c7acaafSMika Westerberg if (i == num_hops - 1) 3118c7acaafSMika Westerberg ret = tb_port_alloc_out_hopid(out_port, dst_hopid, 3128c7acaafSMika Westerberg dst_hopid); 3138c7acaafSMika Westerberg else 3148c7acaafSMika Westerberg ret = tb_port_alloc_out_hopid(out_port, -1, -1); 3158c7acaafSMika Westerberg 3168c7acaafSMika Westerberg if (ret < 0) 3178c7acaafSMika Westerberg goto err; 3188c7acaafSMika Westerberg out_hopid = ret; 3198c7acaafSMika Westerberg 3208c7acaafSMika Westerberg path->hops[i].in_hop_index = in_hopid; 3218c7acaafSMika Westerberg path->hops[i].in_port = in_port; 3228c7acaafSMika Westerberg path->hops[i].in_counter_index = -1; 3238c7acaafSMika Westerberg path->hops[i].out_port = out_port; 3248c7acaafSMika Westerberg path->hops[i].next_hop_index = out_hopid; 3258c7acaafSMika Westerberg 3268c7acaafSMika Westerberg in_hopid = out_hopid; 3278c7acaafSMika Westerberg } 3288c7acaafSMika Westerberg 329520b6702SAndreas Noever path->tb = tb; 330520b6702SAndreas Noever path->path_length = num_hops; 3318c7acaafSMika Westerberg path->name = name; 3328c7acaafSMika Westerberg 333520b6702SAndreas Noever return path; 3348c7acaafSMika Westerberg 3358c7acaafSMika Westerberg err: 3368c7acaafSMika Westerberg tb_path_free(path); 3378c7acaafSMika Westerberg return NULL; 338520b6702SAndreas Noever } 339520b6702SAndreas Noever 340520b6702SAndreas Noever /** 341ab9f31cfSMika Westerberg * tb_path_free() - free a path 342ab9f31cfSMika Westerberg * @path: Path to free 343ab9f31cfSMika Westerberg * 344ab9f31cfSMika Westerberg * Frees a path. The path does not need to be deactivated. 345520b6702SAndreas Noever */ 346520b6702SAndreas Noever void tb_path_free(struct tb_path *path) 347520b6702SAndreas Noever { 3488c7acaafSMika Westerberg int i; 3498c7acaafSMika Westerberg 3508c7acaafSMika Westerberg for (i = 0; i < path->path_length; i++) { 3518c7acaafSMika Westerberg const struct tb_path_hop *hop = &path->hops[i]; 3528c7acaafSMika Westerberg 3538c7acaafSMika Westerberg if (hop->in_port) 3548c7acaafSMika Westerberg tb_port_release_in_hopid(hop->in_port, 3558c7acaafSMika Westerberg hop->in_hop_index); 3568c7acaafSMika Westerberg if (hop->out_port) 3578c7acaafSMika Westerberg tb_port_release_out_hopid(hop->out_port, 3588c7acaafSMika Westerberg hop->next_hop_index); 3598c7acaafSMika Westerberg } 3608c7acaafSMika Westerberg 361520b6702SAndreas Noever kfree(path->hops); 362520b6702SAndreas Noever kfree(path); 363520b6702SAndreas Noever } 364520b6702SAndreas Noever 365520b6702SAndreas Noever static void __tb_path_deallocate_nfc(struct tb_path *path, int first_hop) 366520b6702SAndreas Noever { 367520b6702SAndreas Noever int i, res; 368520b6702SAndreas Noever for (i = first_hop; i < path->path_length; i++) { 369520b6702SAndreas Noever res = tb_port_add_nfc_credits(path->hops[i].in_port, 370520b6702SAndreas Noever -path->nfc_credits); 371520b6702SAndreas Noever if (res) 372520b6702SAndreas Noever tb_port_warn(path->hops[i].in_port, 373520b6702SAndreas Noever "nfc credits deallocation failed for hop %d\n", 374520b6702SAndreas Noever i); 375520b6702SAndreas Noever } 376520b6702SAndreas Noever } 377520b6702SAndreas Noever 37844242d6cSMika Westerberg static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index, 37944242d6cSMika Westerberg bool clear_fc) 38049442693SMika Westerberg { 38149442693SMika Westerberg struct tb_regs_hop hop; 38249442693SMika Westerberg ktime_t timeout; 38349442693SMika Westerberg int ret; 38449442693SMika Westerberg 38549442693SMika Westerberg /* Disable the path */ 38649442693SMika Westerberg ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2); 38749442693SMika Westerberg if (ret) 38849442693SMika Westerberg return ret; 38949442693SMika Westerberg 39049442693SMika Westerberg /* Already disabled */ 39149442693SMika Westerberg if (!hop.enable) 39249442693SMika Westerberg return 0; 39349442693SMika Westerberg 39449442693SMika Westerberg hop.enable = 0; 39549442693SMika Westerberg 39649442693SMika Westerberg ret = tb_port_write(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2); 39749442693SMika Westerberg if (ret) 39849442693SMika Westerberg return ret; 39949442693SMika Westerberg 40049442693SMika Westerberg /* Wait until it is drained */ 40149442693SMika Westerberg timeout = ktime_add_ms(ktime_get(), 500); 40249442693SMika Westerberg do { 40349442693SMika Westerberg ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2); 40449442693SMika Westerberg if (ret) 40549442693SMika Westerberg return ret; 40649442693SMika Westerberg 40744242d6cSMika Westerberg if (!hop.pending) { 40844242d6cSMika Westerberg if (clear_fc) { 40981816f50SMika Westerberg /* 41081816f50SMika Westerberg * Clear flow control. Protocol adapters 41181816f50SMika Westerberg * IFC and ISE bits are vendor defined 41281816f50SMika Westerberg * in the USB4 spec so we clear them 41381816f50SMika Westerberg * only for pre-USB4 adapters. 41481816f50SMika Westerberg */ 41581816f50SMika Westerberg if (!tb_switch_is_usb4(port->sw)) { 41644242d6cSMika Westerberg hop.ingress_fc = 0; 41744242d6cSMika Westerberg hop.ingress_shared_buffer = 0; 41881816f50SMika Westerberg } 41981816f50SMika Westerberg hop.egress_fc = 0; 42044242d6cSMika Westerberg hop.egress_shared_buffer = 0; 42144242d6cSMika Westerberg 42244242d6cSMika Westerberg return tb_port_write(port, &hop, TB_CFG_HOPS, 42344242d6cSMika Westerberg 2 * hop_index, 2); 42444242d6cSMika Westerberg } 42544242d6cSMika Westerberg 42649442693SMika Westerberg return 0; 42744242d6cSMika Westerberg } 42849442693SMika Westerberg 42949442693SMika Westerberg usleep_range(10, 20); 43049442693SMika Westerberg } while (ktime_before(ktime_get(), timeout)); 43149442693SMika Westerberg 43249442693SMika Westerberg return -ETIMEDOUT; 43349442693SMika Westerberg } 43449442693SMika Westerberg 435520b6702SAndreas Noever static void __tb_path_deactivate_hops(struct tb_path *path, int first_hop) 436520b6702SAndreas Noever { 437520b6702SAndreas Noever int i, res; 43849442693SMika Westerberg 439520b6702SAndreas Noever for (i = first_hop; i < path->path_length; i++) { 44049442693SMika Westerberg res = __tb_path_deactivate_hop(path->hops[i].in_port, 44144242d6cSMika Westerberg path->hops[i].in_hop_index, 44244242d6cSMika Westerberg path->clear_fc); 44349442693SMika Westerberg if (res && res != -ENODEV) 444520b6702SAndreas Noever tb_port_warn(path->hops[i].in_port, 445520b6702SAndreas Noever "hop deactivation failed for hop %d, index %d\n", 446520b6702SAndreas Noever i, path->hops[i].in_hop_index); 447520b6702SAndreas Noever } 448520b6702SAndreas Noever } 449520b6702SAndreas Noever 450520b6702SAndreas Noever void tb_path_deactivate(struct tb_path *path) 451520b6702SAndreas Noever { 452520b6702SAndreas Noever if (!path->activated) { 453520b6702SAndreas Noever tb_WARN(path->tb, "trying to deactivate an inactive path\n"); 454520b6702SAndreas Noever return; 455520b6702SAndreas Noever } 4568c7acaafSMika Westerberg tb_dbg(path->tb, 457*a3595258SMika Westerberg "deactivating %s path from %llx:%u to %llx:%u\n", 4588c7acaafSMika Westerberg path->name, tb_route(path->hops[0].in_port->sw), 459520b6702SAndreas Noever path->hops[0].in_port->port, 460520b6702SAndreas Noever tb_route(path->hops[path->path_length - 1].out_port->sw), 461520b6702SAndreas Noever path->hops[path->path_length - 1].out_port->port); 462520b6702SAndreas Noever __tb_path_deactivate_hops(path, 0); 463520b6702SAndreas Noever __tb_path_deallocate_nfc(path, 0); 464520b6702SAndreas Noever path->activated = false; 465520b6702SAndreas Noever } 466520b6702SAndreas Noever 467520b6702SAndreas Noever /** 468520b6702SAndreas Noever * tb_path_activate() - activate a path 469520b6702SAndreas Noever * 470520b6702SAndreas Noever * Activate a path starting with the last hop and iterating backwards. The 471520b6702SAndreas Noever * caller must fill path->hops before calling tb_path_activate(). 472520b6702SAndreas Noever * 473520b6702SAndreas Noever * Return: Returns 0 on success or an error code on failure. 474520b6702SAndreas Noever */ 475520b6702SAndreas Noever int tb_path_activate(struct tb_path *path) 476520b6702SAndreas Noever { 477520b6702SAndreas Noever int i, res; 478520b6702SAndreas Noever enum tb_path_port out_mask, in_mask; 479520b6702SAndreas Noever if (path->activated) { 480520b6702SAndreas Noever tb_WARN(path->tb, "trying to activate already activated path\n"); 481520b6702SAndreas Noever return -EINVAL; 482520b6702SAndreas Noever } 483520b6702SAndreas Noever 4848c7acaafSMika Westerberg tb_dbg(path->tb, 485*a3595258SMika Westerberg "activating %s path from %llx:%u to %llx:%u\n", 4868c7acaafSMika Westerberg path->name, tb_route(path->hops[0].in_port->sw), 487520b6702SAndreas Noever path->hops[0].in_port->port, 488520b6702SAndreas Noever tb_route(path->hops[path->path_length - 1].out_port->sw), 489520b6702SAndreas Noever path->hops[path->path_length - 1].out_port->port); 490520b6702SAndreas Noever 491520b6702SAndreas Noever /* Clear counters. */ 492520b6702SAndreas Noever for (i = path->path_length - 1; i >= 0; i--) { 493520b6702SAndreas Noever if (path->hops[i].in_counter_index == -1) 494520b6702SAndreas Noever continue; 495520b6702SAndreas Noever res = tb_port_clear_counter(path->hops[i].in_port, 496520b6702SAndreas Noever path->hops[i].in_counter_index); 497520b6702SAndreas Noever if (res) 498520b6702SAndreas Noever goto err; 499520b6702SAndreas Noever } 500520b6702SAndreas Noever 501520b6702SAndreas Noever /* Add non flow controlled credits. */ 502520b6702SAndreas Noever for (i = path->path_length - 1; i >= 0; i--) { 503520b6702SAndreas Noever res = tb_port_add_nfc_credits(path->hops[i].in_port, 504520b6702SAndreas Noever path->nfc_credits); 505520b6702SAndreas Noever if (res) { 506520b6702SAndreas Noever __tb_path_deallocate_nfc(path, i); 507520b6702SAndreas Noever goto err; 508520b6702SAndreas Noever } 509520b6702SAndreas Noever } 510520b6702SAndreas Noever 511520b6702SAndreas Noever /* Activate hops. */ 512520b6702SAndreas Noever for (i = path->path_length - 1; i >= 0; i--) { 51372ad366fSAndreas Noever struct tb_regs_hop hop = { 0 }; 51472ad366fSAndreas Noever 5150414bec5SMika Westerberg /* If it is left active deactivate it first */ 5160414bec5SMika Westerberg __tb_path_deactivate_hop(path->hops[i].in_port, 51744242d6cSMika Westerberg path->hops[i].in_hop_index, path->clear_fc); 518520b6702SAndreas Noever 519520b6702SAndreas Noever /* dword 0 */ 520520b6702SAndreas Noever hop.next_hop = path->hops[i].next_hop_index; 521520b6702SAndreas Noever hop.out_port = path->hops[i].out_port->port; 5220414bec5SMika Westerberg hop.initial_credits = path->hops[i].initial_credits; 523520b6702SAndreas Noever hop.unknown1 = 0; 524520b6702SAndreas Noever hop.enable = 1; 525520b6702SAndreas Noever 526520b6702SAndreas Noever /* dword 1 */ 527520b6702SAndreas Noever out_mask = (i == path->path_length - 1) ? 528520b6702SAndreas Noever TB_PATH_DESTINATION : TB_PATH_INTERNAL; 529520b6702SAndreas Noever in_mask = (i == 0) ? TB_PATH_SOURCE : TB_PATH_INTERNAL; 530520b6702SAndreas Noever hop.weight = path->weight; 531520b6702SAndreas Noever hop.unknown2 = 0; 532520b6702SAndreas Noever hop.priority = path->priority; 533520b6702SAndreas Noever hop.drop_packages = path->drop_packages; 534520b6702SAndreas Noever hop.counter = path->hops[i].in_counter_index; 535520b6702SAndreas Noever hop.counter_enable = path->hops[i].in_counter_index != -1; 536520b6702SAndreas Noever hop.ingress_fc = path->ingress_fc_enable & in_mask; 537520b6702SAndreas Noever hop.egress_fc = path->egress_fc_enable & out_mask; 538520b6702SAndreas Noever hop.ingress_shared_buffer = path->ingress_shared_buffer 539520b6702SAndreas Noever & in_mask; 540520b6702SAndreas Noever hop.egress_shared_buffer = path->egress_shared_buffer 541520b6702SAndreas Noever & out_mask; 542520b6702SAndreas Noever hop.unknown3 = 0; 543520b6702SAndreas Noever 5446755156aSMika Westerberg tb_port_dbg(path->hops[i].in_port, "Writing hop %d\n", i); 5456755156aSMika Westerberg tb_dump_hop(&path->hops[i], &hop); 546520b6702SAndreas Noever res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS, 547520b6702SAndreas Noever 2 * path->hops[i].in_hop_index, 2); 548520b6702SAndreas Noever if (res) { 549520b6702SAndreas Noever __tb_path_deactivate_hops(path, i); 550520b6702SAndreas Noever __tb_path_deallocate_nfc(path, 0); 551520b6702SAndreas Noever goto err; 552520b6702SAndreas Noever } 553520b6702SAndreas Noever } 554520b6702SAndreas Noever path->activated = true; 55562efe699SMika Westerberg tb_dbg(path->tb, "path activation complete\n"); 556520b6702SAndreas Noever return 0; 557520b6702SAndreas Noever err: 558520b6702SAndreas Noever tb_WARN(path->tb, "path activation failed\n"); 559520b6702SAndreas Noever return res; 560520b6702SAndreas Noever } 561520b6702SAndreas Noever 562520b6702SAndreas Noever /** 563520b6702SAndreas Noever * tb_path_is_invalid() - check whether any ports on the path are invalid 564520b6702SAndreas Noever * 565520b6702SAndreas Noever * Return: Returns true if the path is invalid, false otherwise. 566520b6702SAndreas Noever */ 567520b6702SAndreas Noever bool tb_path_is_invalid(struct tb_path *path) 568520b6702SAndreas Noever { 569520b6702SAndreas Noever int i = 0; 570520b6702SAndreas Noever for (i = 0; i < path->path_length; i++) { 571520b6702SAndreas Noever if (path->hops[i].in_port->sw->is_unplugged) 572520b6702SAndreas Noever return true; 573520b6702SAndreas Noever if (path->hops[i].out_port->sw->is_unplugged) 574520b6702SAndreas Noever return true; 575520b6702SAndreas Noever } 576520b6702SAndreas Noever return false; 577520b6702SAndreas Noever } 578a11b88adSMika Westerberg 579a11b88adSMika Westerberg /** 5800bd680cdSMika Westerberg * tb_path_port_on_path() - Does the path go through certain port 581a11b88adSMika Westerberg * @path: Path to check 5820bd680cdSMika Westerberg * @port: Switch to check 583a11b88adSMika Westerberg * 5840bd680cdSMika Westerberg * Goes over all hops on path and checks if @port is any of them. 585a11b88adSMika Westerberg * Direction does not matter. 586a11b88adSMika Westerberg */ 5870bd680cdSMika Westerberg bool tb_path_port_on_path(const struct tb_path *path, const struct tb_port *port) 588a11b88adSMika Westerberg { 589a11b88adSMika Westerberg int i; 590a11b88adSMika Westerberg 591a11b88adSMika Westerberg for (i = 0; i < path->path_length; i++) { 5920bd680cdSMika Westerberg if (path->hops[i].in_port == port || 5930bd680cdSMika Westerberg path->hops[i].out_port == port) 594a11b88adSMika Westerberg return true; 595a11b88adSMika Westerberg } 596a11b88adSMika Westerberg 597a11b88adSMika Westerberg return false; 598a11b88adSMika Westerberg } 599