xref: /openbmc/linux/drivers/thunderbolt/path.c (revision a3595258)
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