xref: /openbmc/linux/drivers/thunderbolt/path.c (revision dc32d754)
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 
tb_dump_hop(const struct tb_path_hop * hop,const struct tb_regs_hop * regs)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 
tb_path_find_dst_port(struct tb_port * src,int src_hopid,int dst_hopid)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 
tb_path_find_src_hopid(struct tb_port * src,const struct tb_port * dst,int dst_hopid)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
8843bddb26SMika Westerberg  * @alloc_hopid: Allocate HopIDs for the ports
890414bec5SMika Westerberg  *
900414bec5SMika Westerberg  * Follows a path starting from @src and @src_hopid to the last output
9143bddb26SMika Westerberg  * port of the path. Allocates HopIDs for the visited ports (if
9243bddb26SMika Westerberg  * @alloc_hopid is true). Call tb_path_free() to release the path and
9343bddb26SMika Westerberg  * allocated HopIDs when the path is not needed anymore.
940414bec5SMika Westerberg  *
950414bec5SMika Westerberg  * Note function discovers also incomplete paths so caller should check
960414bec5SMika Westerberg  * that the @dst port is the expected one. If it is not, the path can be
970414bec5SMika Westerberg  * cleaned up by calling tb_path_deactivate() before tb_path_free().
980414bec5SMika Westerberg  *
990414bec5SMika Westerberg  * Return: Discovered path on success, %NULL in case of failure
1000414bec5SMika Westerberg  */
tb_path_discover(struct tb_port * src,int src_hopid,struct tb_port * dst,int dst_hopid,struct tb_port ** last,const char * name,bool alloc_hopid)1010414bec5SMika Westerberg struct tb_path *tb_path_discover(struct tb_port *src, int src_hopid,
1020414bec5SMika Westerberg 				 struct tb_port *dst, int dst_hopid,
10343bddb26SMika Westerberg 				 struct tb_port **last, const char *name,
10443bddb26SMika Westerberg 				 bool alloc_hopid)
1050414bec5SMika Westerberg {
1060414bec5SMika Westerberg 	struct tb_port *out_port;
1070414bec5SMika Westerberg 	struct tb_regs_hop hop;
1080414bec5SMika Westerberg 	struct tb_path *path;
1090414bec5SMika Westerberg 	struct tb_switch *sw;
1100414bec5SMika Westerberg 	struct tb_port *p;
1110414bec5SMika Westerberg 	size_t num_hops;
1120414bec5SMika Westerberg 	int ret, i, h;
1130414bec5SMika Westerberg 
1140414bec5SMika Westerberg 	if (src_hopid < 0 && dst) {
1150414bec5SMika Westerberg 		/*
1160414bec5SMika Westerberg 		 * For incomplete paths the intermediate HopID can be
1170414bec5SMika Westerberg 		 * different from the one used by the protocol adapter
1180414bec5SMika Westerberg 		 * so in that case find a path that ends on @dst with
1190414bec5SMika Westerberg 		 * matching @dst_hopid. That should give us the correct
1200414bec5SMika Westerberg 		 * HopID for the @src.
1210414bec5SMika Westerberg 		 */
1220414bec5SMika Westerberg 		src_hopid = tb_path_find_src_hopid(src, dst, dst_hopid);
1230414bec5SMika Westerberg 		if (!src_hopid)
1240414bec5SMika Westerberg 			return NULL;
1250414bec5SMika Westerberg 	}
1260414bec5SMika Westerberg 
1270414bec5SMika Westerberg 	p = src;
1280414bec5SMika Westerberg 	h = src_hopid;
1290414bec5SMika Westerberg 	num_hops = 0;
1300414bec5SMika Westerberg 
1310414bec5SMika Westerberg 	for (i = 0; p && i < TB_PATH_MAX_HOPS; i++) {
1320414bec5SMika Westerberg 		sw = p->sw;
1330414bec5SMika Westerberg 
1340414bec5SMika Westerberg 		ret = tb_port_read(p, &hop, TB_CFG_HOPS, 2 * h, 2);
1350414bec5SMika Westerberg 		if (ret) {
1360414bec5SMika Westerberg 			tb_port_warn(p, "failed to read path at %d\n", h);
1370414bec5SMika Westerberg 			return NULL;
1380414bec5SMika Westerberg 		}
1390414bec5SMika Westerberg 
1400414bec5SMika Westerberg 		/* If the hop is not enabled we got an incomplete path */
1410414bec5SMika Westerberg 		if (!hop.enable)
1420414bec5SMika Westerberg 			break;
1430414bec5SMika Westerberg 
1440414bec5SMika Westerberg 		out_port = &sw->ports[hop.out_port];
1450414bec5SMika Westerberg 		if (last)
1460414bec5SMika Westerberg 			*last = out_port;
1470414bec5SMika Westerberg 
1480414bec5SMika Westerberg 		h = hop.next_hop;
1490414bec5SMika Westerberg 		p = out_port->remote;
1500414bec5SMika Westerberg 		num_hops++;
1510414bec5SMika Westerberg 	}
1520414bec5SMika Westerberg 
1530414bec5SMika Westerberg 	path = kzalloc(sizeof(*path), GFP_KERNEL);
1540414bec5SMika Westerberg 	if (!path)
1550414bec5SMika Westerberg 		return NULL;
1560414bec5SMika Westerberg 
1570414bec5SMika Westerberg 	path->name = name;
1580414bec5SMika Westerberg 	path->tb = src->sw->tb;
1590414bec5SMika Westerberg 	path->path_length = num_hops;
1600414bec5SMika Westerberg 	path->activated = true;
16143bddb26SMika Westerberg 	path->alloc_hopid = alloc_hopid;
1620414bec5SMika Westerberg 
1630414bec5SMika Westerberg 	path->hops = kcalloc(num_hops, sizeof(*path->hops), GFP_KERNEL);
1640414bec5SMika Westerberg 	if (!path->hops) {
1650414bec5SMika Westerberg 		kfree(path);
1660414bec5SMika Westerberg 		return NULL;
1670414bec5SMika Westerberg 	}
1680414bec5SMika Westerberg 
169259e0c71SMika Westerberg 	tb_dbg(path->tb, "discovering %s path starting from %llx:%u\n",
170259e0c71SMika Westerberg 	       path->name, tb_route(src->sw), src->port);
171259e0c71SMika Westerberg 
1720414bec5SMika Westerberg 	p = src;
1730414bec5SMika Westerberg 	h = src_hopid;
1740414bec5SMika Westerberg 
1750414bec5SMika Westerberg 	for (i = 0; i < num_hops; i++) {
1760414bec5SMika Westerberg 		int next_hop;
1770414bec5SMika Westerberg 
1780414bec5SMika Westerberg 		sw = p->sw;
1790414bec5SMika Westerberg 
1800414bec5SMika Westerberg 		ret = tb_port_read(p, &hop, TB_CFG_HOPS, 2 * h, 2);
1810414bec5SMika Westerberg 		if (ret) {
1820414bec5SMika Westerberg 			tb_port_warn(p, "failed to read path at %d\n", h);
1830414bec5SMika Westerberg 			goto err;
1840414bec5SMika Westerberg 		}
1850414bec5SMika Westerberg 
18643bddb26SMika Westerberg 		if (alloc_hopid && tb_port_alloc_in_hopid(p, h, h) < 0)
1870414bec5SMika Westerberg 			goto err;
1880414bec5SMika Westerberg 
1890414bec5SMika Westerberg 		out_port = &sw->ports[hop.out_port];
1900414bec5SMika Westerberg 		next_hop = hop.next_hop;
1910414bec5SMika Westerberg 
19243bddb26SMika Westerberg 		if (alloc_hopid &&
19343bddb26SMika Westerberg 		    tb_port_alloc_out_hopid(out_port, next_hop, next_hop) < 0) {
1940414bec5SMika Westerberg 			tb_port_release_in_hopid(p, h);
1950414bec5SMika Westerberg 			goto err;
1960414bec5SMika Westerberg 		}
1970414bec5SMika Westerberg 
1980414bec5SMika Westerberg 		path->hops[i].in_port = p;
1990414bec5SMika Westerberg 		path->hops[i].in_hop_index = h;
2000414bec5SMika Westerberg 		path->hops[i].in_counter_index = -1;
2010414bec5SMika Westerberg 		path->hops[i].out_port = out_port;
2020414bec5SMika Westerberg 		path->hops[i].next_hop_index = next_hop;
2030414bec5SMika Westerberg 
204259e0c71SMika Westerberg 		tb_dump_hop(&path->hops[i], &hop);
205259e0c71SMika Westerberg 
2060414bec5SMika Westerberg 		h = next_hop;
2070414bec5SMika Westerberg 		p = out_port->remote;
2080414bec5SMika Westerberg 	}
2090414bec5SMika Westerberg 
210259e0c71SMika Westerberg 	tb_dbg(path->tb, "path discovery complete\n");
2110414bec5SMika Westerberg 	return path;
2120414bec5SMika Westerberg 
2130414bec5SMika Westerberg err:
2140414bec5SMika Westerberg 	tb_port_warn(src, "failed to discover path starting at HopID %d\n",
2150414bec5SMika Westerberg 		     src_hopid);
2160414bec5SMika Westerberg 	tb_path_free(path);
2170414bec5SMika Westerberg 	return NULL;
2180414bec5SMika Westerberg }
2190414bec5SMika Westerberg 
220520b6702SAndreas Noever /**
2218c7acaafSMika Westerberg  * tb_path_alloc() - allocate a thunderbolt path between two ports
2228c7acaafSMika Westerberg  * @tb: Domain pointer
2238c7acaafSMika Westerberg  * @src: Source port of the path
2248c7acaafSMika Westerberg  * @src_hopid: HopID used for the first ingress port in the path
2258c7acaafSMika Westerberg  * @dst: Destination port of the path
2268c7acaafSMika Westerberg  * @dst_hopid: HopID used for the last egress port in the path
2278c7acaafSMika Westerberg  * @link_nr: Preferred link if there are dual links on the path
2288c7acaafSMika Westerberg  * @name: Name of the path
2298c7acaafSMika Westerberg  *
2308c7acaafSMika Westerberg  * Creates path between two ports starting with given @src_hopid. Reserves
2318c7acaafSMika Westerberg  * HopIDs for each port (they can be different from @src_hopid depending on
2328c7acaafSMika Westerberg  * how many HopIDs each port already have reserved). If there are dual
23391c0c120SMika Westerberg  * links on the path, prioritizes using @link_nr but takes into account
23491c0c120SMika Westerberg  * that the lanes may be bonded.
235520b6702SAndreas Noever  *
236520b6702SAndreas Noever  * Return: Returns a tb_path on success or NULL on failure.
237520b6702SAndreas Noever  */
tb_path_alloc(struct tb * tb,struct tb_port * src,int src_hopid,struct tb_port * dst,int dst_hopid,int link_nr,const char * name)2388c7acaafSMika Westerberg struct tb_path *tb_path_alloc(struct tb *tb, struct tb_port *src, int src_hopid,
2398c7acaafSMika Westerberg 			      struct tb_port *dst, int dst_hopid, int link_nr,
2408c7acaafSMika Westerberg 			      const char *name)
241520b6702SAndreas Noever {
2427e897bb7SMika Westerberg 	struct tb_port *in_port, *out_port, *first_port, *last_port;
2438c7acaafSMika Westerberg 	int in_hopid, out_hopid;
2448c7acaafSMika Westerberg 	struct tb_path *path;
2458c7acaafSMika Westerberg 	size_t num_hops;
2468c7acaafSMika Westerberg 	int i, ret;
2478c7acaafSMika Westerberg 
2488c7acaafSMika Westerberg 	path = kzalloc(sizeof(*path), GFP_KERNEL);
249520b6702SAndreas Noever 	if (!path)
250520b6702SAndreas Noever 		return NULL;
2518c7acaafSMika Westerberg 
2527e897bb7SMika Westerberg 	first_port = last_port = NULL;
253c64c3f3aSMika Westerberg 	i = 0;
2547e897bb7SMika Westerberg 	tb_for_each_port_on_path(src, dst, in_port) {
2557e897bb7SMika Westerberg 		if (!first_port)
2567e897bb7SMika Westerberg 			first_port = in_port;
2577e897bb7SMika Westerberg 		last_port = in_port;
258c64c3f3aSMika Westerberg 		i++;
2597e897bb7SMika Westerberg 	}
2607e897bb7SMika Westerberg 
2617e897bb7SMika Westerberg 	/* Check that src and dst are reachable */
2627e897bb7SMika Westerberg 	if (first_port != src || last_port != dst) {
2637e897bb7SMika Westerberg 		kfree(path);
2647e897bb7SMika Westerberg 		return NULL;
2657e897bb7SMika Westerberg 	}
266c64c3f3aSMika Westerberg 
267c64c3f3aSMika Westerberg 	/* Each hop takes two ports */
268c64c3f3aSMika Westerberg 	num_hops = i / 2;
2698c7acaafSMika Westerberg 
270520b6702SAndreas Noever 	path->hops = kcalloc(num_hops, sizeof(*path->hops), GFP_KERNEL);
271520b6702SAndreas Noever 	if (!path->hops) {
272520b6702SAndreas Noever 		kfree(path);
273520b6702SAndreas Noever 		return NULL;
274520b6702SAndreas Noever 	}
2758c7acaafSMika Westerberg 
27643bddb26SMika Westerberg 	path->alloc_hopid = true;
27743bddb26SMika Westerberg 
2788c7acaafSMika Westerberg 	in_hopid = src_hopid;
2798c7acaafSMika Westerberg 	out_port = NULL;
2808c7acaafSMika Westerberg 
2818c7acaafSMika Westerberg 	for (i = 0; i < num_hops; i++) {
2828c7acaafSMika Westerberg 		in_port = tb_next_port_on_path(src, dst, out_port);
2838c7acaafSMika Westerberg 		if (!in_port)
2848c7acaafSMika Westerberg 			goto err;
2858c7acaafSMika Westerberg 
28691c0c120SMika Westerberg 		/* When lanes are bonded primary link must be used */
28791c0c120SMika Westerberg 		if (!in_port->bonded && in_port->dual_link_port &&
28891c0c120SMika Westerberg 		    in_port->link_nr != link_nr)
2898c7acaafSMika Westerberg 			in_port = in_port->dual_link_port;
2908c7acaafSMika Westerberg 
2918c7acaafSMika Westerberg 		ret = tb_port_alloc_in_hopid(in_port, in_hopid, in_hopid);
2928c7acaafSMika Westerberg 		if (ret < 0)
2938c7acaafSMika Westerberg 			goto err;
2948c7acaafSMika Westerberg 		in_hopid = ret;
2958c7acaafSMika Westerberg 
2968c7acaafSMika Westerberg 		out_port = tb_next_port_on_path(src, dst, in_port);
2978c7acaafSMika Westerberg 		if (!out_port)
2988c7acaafSMika Westerberg 			goto err;
2998c7acaafSMika Westerberg 
30091c0c120SMika Westerberg 		/*
30191c0c120SMika Westerberg 		 * Pick up right port when going from non-bonded to
30291c0c120SMika Westerberg 		 * bonded or from bonded to non-bonded.
30391c0c120SMika Westerberg 		 */
30491c0c120SMika Westerberg 		if (out_port->dual_link_port) {
30591c0c120SMika Westerberg 			if (!in_port->bonded && out_port->bonded &&
30691c0c120SMika Westerberg 			    out_port->link_nr) {
30791c0c120SMika Westerberg 				/*
30891c0c120SMika Westerberg 				 * Use primary link when going from
30991c0c120SMika Westerberg 				 * non-bonded to bonded.
31091c0c120SMika Westerberg 				 */
3118c7acaafSMika Westerberg 				out_port = out_port->dual_link_port;
31291c0c120SMika Westerberg 			} else if (!out_port->bonded &&
31391c0c120SMika Westerberg 				   out_port->link_nr != link_nr) {
31491c0c120SMika Westerberg 				/*
31591c0c120SMika Westerberg 				 * If out port is not bonded follow
31691c0c120SMika Westerberg 				 * link_nr.
31791c0c120SMika Westerberg 				 */
31891c0c120SMika Westerberg 				out_port = out_port->dual_link_port;
31991c0c120SMika Westerberg 			}
32091c0c120SMika Westerberg 		}
3218c7acaafSMika Westerberg 
3228c7acaafSMika Westerberg 		if (i == num_hops - 1)
3238c7acaafSMika Westerberg 			ret = tb_port_alloc_out_hopid(out_port, dst_hopid,
3248c7acaafSMika Westerberg 						      dst_hopid);
3258c7acaafSMika Westerberg 		else
3268c7acaafSMika Westerberg 			ret = tb_port_alloc_out_hopid(out_port, -1, -1);
3278c7acaafSMika Westerberg 
3288c7acaafSMika Westerberg 		if (ret < 0)
3298c7acaafSMika Westerberg 			goto err;
3308c7acaafSMika Westerberg 		out_hopid = ret;
3318c7acaafSMika Westerberg 
3328c7acaafSMika Westerberg 		path->hops[i].in_hop_index = in_hopid;
3338c7acaafSMika Westerberg 		path->hops[i].in_port = in_port;
3348c7acaafSMika Westerberg 		path->hops[i].in_counter_index = -1;
3358c7acaafSMika Westerberg 		path->hops[i].out_port = out_port;
3368c7acaafSMika Westerberg 		path->hops[i].next_hop_index = out_hopid;
3378c7acaafSMika Westerberg 
3388c7acaafSMika Westerberg 		in_hopid = out_hopid;
3398c7acaafSMika Westerberg 	}
3408c7acaafSMika Westerberg 
341520b6702SAndreas Noever 	path->tb = tb;
342520b6702SAndreas Noever 	path->path_length = num_hops;
3438c7acaafSMika Westerberg 	path->name = name;
3448c7acaafSMika Westerberg 
345520b6702SAndreas Noever 	return path;
3468c7acaafSMika Westerberg 
3478c7acaafSMika Westerberg err:
3488c7acaafSMika Westerberg 	tb_path_free(path);
3498c7acaafSMika Westerberg 	return NULL;
350520b6702SAndreas Noever }
351520b6702SAndreas Noever 
352520b6702SAndreas Noever /**
353ab9f31cfSMika Westerberg  * tb_path_free() - free a path
354ab9f31cfSMika Westerberg  * @path: Path to free
355ab9f31cfSMika Westerberg  *
356ab9f31cfSMika Westerberg  * Frees a path. The path does not need to be deactivated.
357520b6702SAndreas Noever  */
tb_path_free(struct tb_path * path)358520b6702SAndreas Noever void tb_path_free(struct tb_path *path)
359520b6702SAndreas Noever {
36043bddb26SMika Westerberg 	if (path->alloc_hopid) {
3618c7acaafSMika Westerberg 		int i;
3628c7acaafSMika Westerberg 
3638c7acaafSMika Westerberg 		for (i = 0; i < path->path_length; i++) {
3648c7acaafSMika Westerberg 			const struct tb_path_hop *hop = &path->hops[i];
3658c7acaafSMika Westerberg 
3668c7acaafSMika Westerberg 			if (hop->in_port)
3678c7acaafSMika Westerberg 				tb_port_release_in_hopid(hop->in_port,
3688c7acaafSMika Westerberg 							 hop->in_hop_index);
3698c7acaafSMika Westerberg 			if (hop->out_port)
3708c7acaafSMika Westerberg 				tb_port_release_out_hopid(hop->out_port,
3718c7acaafSMika Westerberg 							  hop->next_hop_index);
3728c7acaafSMika Westerberg 		}
37343bddb26SMika Westerberg 	}
3748c7acaafSMika Westerberg 
375520b6702SAndreas Noever 	kfree(path->hops);
376520b6702SAndreas Noever 	kfree(path);
377520b6702SAndreas Noever }
378520b6702SAndreas Noever 
__tb_path_deallocate_nfc(struct tb_path * path,int first_hop)379520b6702SAndreas Noever static void __tb_path_deallocate_nfc(struct tb_path *path, int first_hop)
380520b6702SAndreas Noever {
381520b6702SAndreas Noever 	int i, res;
382520b6702SAndreas Noever 	for (i = first_hop; i < path->path_length; i++) {
383520b6702SAndreas Noever 		res = tb_port_add_nfc_credits(path->hops[i].in_port,
38402c5e7c2SMika Westerberg 					      -path->hops[i].nfc_credits);
385520b6702SAndreas Noever 		if (res)
386520b6702SAndreas Noever 			tb_port_warn(path->hops[i].in_port,
387520b6702SAndreas Noever 				     "nfc credits deallocation failed for hop %d\n",
388520b6702SAndreas Noever 				     i);
389520b6702SAndreas Noever 	}
390520b6702SAndreas Noever }
391520b6702SAndreas Noever 
__tb_path_deactivate_hop(struct tb_port * port,int hop_index,bool clear_fc)39244242d6cSMika Westerberg static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index,
39344242d6cSMika Westerberg 				    bool clear_fc)
39449442693SMika Westerberg {
39549442693SMika Westerberg 	struct tb_regs_hop hop;
39649442693SMika Westerberg 	ktime_t timeout;
39749442693SMika Westerberg 	int ret;
39849442693SMika Westerberg 
39949442693SMika Westerberg 	/* Disable the path */
40049442693SMika Westerberg 	ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2);
40149442693SMika Westerberg 	if (ret)
40249442693SMika Westerberg 		return ret;
40349442693SMika Westerberg 
40449442693SMika Westerberg 	/* Already disabled */
40549442693SMika Westerberg 	if (!hop.enable)
40649442693SMika Westerberg 		return 0;
40749442693SMika Westerberg 
40849442693SMika Westerberg 	hop.enable = 0;
40949442693SMika Westerberg 
41049442693SMika Westerberg 	ret = tb_port_write(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2);
41149442693SMika Westerberg 	if (ret)
41249442693SMika Westerberg 		return ret;
41349442693SMika Westerberg 
41449442693SMika Westerberg 	/* Wait until it is drained */
41549442693SMika Westerberg 	timeout = ktime_add_ms(ktime_get(), 500);
41649442693SMika Westerberg 	do {
41749442693SMika Westerberg 		ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2);
41849442693SMika Westerberg 		if (ret)
41949442693SMika Westerberg 			return ret;
42049442693SMika Westerberg 
42144242d6cSMika Westerberg 		if (!hop.pending) {
42244242d6cSMika Westerberg 			if (clear_fc) {
42381816f50SMika Westerberg 				/*
42481816f50SMika Westerberg 				 * Clear flow control. Protocol adapters
42581816f50SMika Westerberg 				 * IFC and ISE bits are vendor defined
42681816f50SMika Westerberg 				 * in the USB4 spec so we clear them
42781816f50SMika Westerberg 				 * only for pre-USB4 adapters.
42881816f50SMika Westerberg 				 */
42981816f50SMika Westerberg 				if (!tb_switch_is_usb4(port->sw)) {
43044242d6cSMika Westerberg 					hop.ingress_fc = 0;
43144242d6cSMika Westerberg 					hop.ingress_shared_buffer = 0;
43281816f50SMika Westerberg 				}
43381816f50SMika Westerberg 				hop.egress_fc = 0;
43444242d6cSMika Westerberg 				hop.egress_shared_buffer = 0;
43544242d6cSMika Westerberg 
43644242d6cSMika Westerberg 				return tb_port_write(port, &hop, TB_CFG_HOPS,
43744242d6cSMika Westerberg 						     2 * hop_index, 2);
43844242d6cSMika Westerberg 			}
43944242d6cSMika Westerberg 
44049442693SMika Westerberg 			return 0;
44144242d6cSMika Westerberg 		}
44249442693SMika Westerberg 
44349442693SMika Westerberg 		usleep_range(10, 20);
44449442693SMika Westerberg 	} while (ktime_before(ktime_get(), timeout));
44549442693SMika Westerberg 
44649442693SMika Westerberg 	return -ETIMEDOUT;
44749442693SMika Westerberg }
44849442693SMika Westerberg 
449dc32d754SSanath S /**
450dc32d754SSanath S  * tb_path_deactivate_hop() - Deactivate one path in path config space
451dc32d754SSanath S  * @port: Lane or protocol adapter
452dc32d754SSanath S  * @hop_index: HopID of the path to be cleared
453dc32d754SSanath S  *
454dc32d754SSanath S  * This deactivates or clears a single path config space entry at
455dc32d754SSanath S  * @hop_index. Returns %0 in success and negative errno otherwise.
456dc32d754SSanath S  */
tb_path_deactivate_hop(struct tb_port * port,int hop_index)457dc32d754SSanath S int tb_path_deactivate_hop(struct tb_port *port, int hop_index)
458dc32d754SSanath S {
459dc32d754SSanath S 	return __tb_path_deactivate_hop(port, hop_index, true);
460dc32d754SSanath S }
461dc32d754SSanath S 
__tb_path_deactivate_hops(struct tb_path * path,int first_hop)462520b6702SAndreas Noever static void __tb_path_deactivate_hops(struct tb_path *path, int first_hop)
463520b6702SAndreas Noever {
464520b6702SAndreas Noever 	int i, res;
46549442693SMika Westerberg 
466520b6702SAndreas Noever 	for (i = first_hop; i < path->path_length; i++) {
46749442693SMika Westerberg 		res = __tb_path_deactivate_hop(path->hops[i].in_port,
46844242d6cSMika Westerberg 					       path->hops[i].in_hop_index,
46944242d6cSMika Westerberg 					       path->clear_fc);
47049442693SMika Westerberg 		if (res && res != -ENODEV)
471520b6702SAndreas Noever 			tb_port_warn(path->hops[i].in_port,
472520b6702SAndreas Noever 				     "hop deactivation failed for hop %d, index %d\n",
473520b6702SAndreas Noever 				     i, path->hops[i].in_hop_index);
474520b6702SAndreas Noever 	}
475520b6702SAndreas Noever }
476520b6702SAndreas Noever 
tb_path_deactivate(struct tb_path * path)477520b6702SAndreas Noever void tb_path_deactivate(struct tb_path *path)
478520b6702SAndreas Noever {
479520b6702SAndreas Noever 	if (!path->activated) {
480520b6702SAndreas Noever 		tb_WARN(path->tb, "trying to deactivate an inactive path\n");
481520b6702SAndreas Noever 		return;
482520b6702SAndreas Noever 	}
4838c7acaafSMika Westerberg 	tb_dbg(path->tb,
484a3595258SMika Westerberg 	       "deactivating %s path from %llx:%u to %llx:%u\n",
4858c7acaafSMika Westerberg 	       path->name, tb_route(path->hops[0].in_port->sw),
486520b6702SAndreas Noever 	       path->hops[0].in_port->port,
487520b6702SAndreas Noever 	       tb_route(path->hops[path->path_length - 1].out_port->sw),
488520b6702SAndreas Noever 	       path->hops[path->path_length - 1].out_port->port);
489520b6702SAndreas Noever 	__tb_path_deactivate_hops(path, 0);
490520b6702SAndreas Noever 	__tb_path_deallocate_nfc(path, 0);
491520b6702SAndreas Noever 	path->activated = false;
492520b6702SAndreas Noever }
493520b6702SAndreas Noever 
494520b6702SAndreas Noever /**
495520b6702SAndreas Noever  * tb_path_activate() - activate a path
4965fbcb2d1SMika Westerberg  * @path: Path to activate
497520b6702SAndreas Noever  *
498520b6702SAndreas Noever  * Activate a path starting with the last hop and iterating backwards. The
499520b6702SAndreas Noever  * caller must fill path->hops before calling tb_path_activate().
500520b6702SAndreas Noever  *
501520b6702SAndreas Noever  * Return: Returns 0 on success or an error code on failure.
502520b6702SAndreas Noever  */
tb_path_activate(struct tb_path * path)503520b6702SAndreas Noever int tb_path_activate(struct tb_path *path)
504520b6702SAndreas Noever {
505520b6702SAndreas Noever 	int i, res;
506520b6702SAndreas Noever 	enum tb_path_port out_mask, in_mask;
507520b6702SAndreas Noever 	if (path->activated) {
508520b6702SAndreas Noever 		tb_WARN(path->tb, "trying to activate already activated path\n");
509520b6702SAndreas Noever 		return -EINVAL;
510520b6702SAndreas Noever 	}
511520b6702SAndreas Noever 
5128c7acaafSMika Westerberg 	tb_dbg(path->tb,
513a3595258SMika Westerberg 	       "activating %s path from %llx:%u to %llx:%u\n",
5148c7acaafSMika Westerberg 	       path->name, tb_route(path->hops[0].in_port->sw),
515520b6702SAndreas Noever 	       path->hops[0].in_port->port,
516520b6702SAndreas Noever 	       tb_route(path->hops[path->path_length - 1].out_port->sw),
517520b6702SAndreas Noever 	       path->hops[path->path_length - 1].out_port->port);
518520b6702SAndreas Noever 
519520b6702SAndreas Noever 	/* Clear counters. */
520520b6702SAndreas Noever 	for (i = path->path_length - 1; i >= 0; i--) {
521520b6702SAndreas Noever 		if (path->hops[i].in_counter_index == -1)
522520b6702SAndreas Noever 			continue;
523520b6702SAndreas Noever 		res = tb_port_clear_counter(path->hops[i].in_port,
524520b6702SAndreas Noever 					    path->hops[i].in_counter_index);
525520b6702SAndreas Noever 		if (res)
526520b6702SAndreas Noever 			goto err;
527520b6702SAndreas Noever 	}
528520b6702SAndreas Noever 
529520b6702SAndreas Noever 	/* Add non flow controlled credits. */
530520b6702SAndreas Noever 	for (i = path->path_length - 1; i >= 0; i--) {
531520b6702SAndreas Noever 		res = tb_port_add_nfc_credits(path->hops[i].in_port,
53202c5e7c2SMika Westerberg 					      path->hops[i].nfc_credits);
533520b6702SAndreas Noever 		if (res) {
534520b6702SAndreas Noever 			__tb_path_deallocate_nfc(path, i);
535520b6702SAndreas Noever 			goto err;
536520b6702SAndreas Noever 		}
537520b6702SAndreas Noever 	}
538520b6702SAndreas Noever 
539520b6702SAndreas Noever 	/* Activate hops. */
540520b6702SAndreas Noever 	for (i = path->path_length - 1; i >= 0; i--) {
54172ad366fSAndreas Noever 		struct tb_regs_hop hop = { 0 };
54272ad366fSAndreas Noever 
5430414bec5SMika Westerberg 		/* If it is left active deactivate it first */
5440414bec5SMika Westerberg 		__tb_path_deactivate_hop(path->hops[i].in_port,
54544242d6cSMika Westerberg 				path->hops[i].in_hop_index, path->clear_fc);
546520b6702SAndreas Noever 
547520b6702SAndreas Noever 		/* dword 0 */
548520b6702SAndreas Noever 		hop.next_hop = path->hops[i].next_hop_index;
549520b6702SAndreas Noever 		hop.out_port = path->hops[i].out_port->port;
5500414bec5SMika Westerberg 		hop.initial_credits = path->hops[i].initial_credits;
551520b6702SAndreas Noever 		hop.unknown1 = 0;
552520b6702SAndreas Noever 		hop.enable = 1;
553520b6702SAndreas Noever 
554520b6702SAndreas Noever 		/* dword 1 */
555520b6702SAndreas Noever 		out_mask = (i == path->path_length - 1) ?
556520b6702SAndreas Noever 				TB_PATH_DESTINATION : TB_PATH_INTERNAL;
557520b6702SAndreas Noever 		in_mask = (i == 0) ? TB_PATH_SOURCE : TB_PATH_INTERNAL;
558520b6702SAndreas Noever 		hop.weight = path->weight;
559520b6702SAndreas Noever 		hop.unknown2 = 0;
560520b6702SAndreas Noever 		hop.priority = path->priority;
561520b6702SAndreas Noever 		hop.drop_packages = path->drop_packages;
562520b6702SAndreas Noever 		hop.counter = path->hops[i].in_counter_index;
563520b6702SAndreas Noever 		hop.counter_enable = path->hops[i].in_counter_index != -1;
564520b6702SAndreas Noever 		hop.ingress_fc = path->ingress_fc_enable & in_mask;
565520b6702SAndreas Noever 		hop.egress_fc = path->egress_fc_enable & out_mask;
566520b6702SAndreas Noever 		hop.ingress_shared_buffer = path->ingress_shared_buffer
567520b6702SAndreas Noever 					    & in_mask;
568520b6702SAndreas Noever 		hop.egress_shared_buffer = path->egress_shared_buffer
569520b6702SAndreas Noever 					    & out_mask;
570520b6702SAndreas Noever 		hop.unknown3 = 0;
571520b6702SAndreas Noever 
5726755156aSMika Westerberg 		tb_port_dbg(path->hops[i].in_port, "Writing hop %d\n", i);
5736755156aSMika Westerberg 		tb_dump_hop(&path->hops[i], &hop);
574520b6702SAndreas Noever 		res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS,
575520b6702SAndreas Noever 				    2 * path->hops[i].in_hop_index, 2);
576520b6702SAndreas Noever 		if (res) {
577520b6702SAndreas Noever 			__tb_path_deactivate_hops(path, i);
578520b6702SAndreas Noever 			__tb_path_deallocate_nfc(path, 0);
579520b6702SAndreas Noever 			goto err;
580520b6702SAndreas Noever 		}
581520b6702SAndreas Noever 	}
582520b6702SAndreas Noever 	path->activated = true;
58362efe699SMika Westerberg 	tb_dbg(path->tb, "path activation complete\n");
584520b6702SAndreas Noever 	return 0;
585520b6702SAndreas Noever err:
586520b6702SAndreas Noever 	tb_WARN(path->tb, "path activation failed\n");
587520b6702SAndreas Noever 	return res;
588520b6702SAndreas Noever }
589520b6702SAndreas Noever 
590520b6702SAndreas Noever /**
591520b6702SAndreas Noever  * tb_path_is_invalid() - check whether any ports on the path are invalid
5925fbcb2d1SMika Westerberg  * @path: Path to check
593520b6702SAndreas Noever  *
594520b6702SAndreas Noever  * Return: Returns true if the path is invalid, false otherwise.
595520b6702SAndreas Noever  */
tb_path_is_invalid(struct tb_path * path)596520b6702SAndreas Noever bool tb_path_is_invalid(struct tb_path *path)
597520b6702SAndreas Noever {
598520b6702SAndreas Noever 	int i = 0;
599520b6702SAndreas Noever 	for (i = 0; i < path->path_length; i++) {
600520b6702SAndreas Noever 		if (path->hops[i].in_port->sw->is_unplugged)
601520b6702SAndreas Noever 			return true;
602520b6702SAndreas Noever 		if (path->hops[i].out_port->sw->is_unplugged)
603520b6702SAndreas Noever 			return true;
604520b6702SAndreas Noever 	}
605520b6702SAndreas Noever 	return false;
606520b6702SAndreas Noever }
607a11b88adSMika Westerberg 
608a11b88adSMika Westerberg /**
6090bd680cdSMika Westerberg  * tb_path_port_on_path() - Does the path go through certain port
610a11b88adSMika Westerberg  * @path: Path to check
6110bd680cdSMika Westerberg  * @port: Switch to check
612a11b88adSMika Westerberg  *
6130bd680cdSMika Westerberg  * Goes over all hops on path and checks if @port is any of them.
614a11b88adSMika Westerberg  * Direction does not matter.
615a11b88adSMika Westerberg  */
tb_path_port_on_path(const struct tb_path * path,const struct tb_port * port)6160bd680cdSMika Westerberg bool tb_path_port_on_path(const struct tb_path *path, const struct tb_port *port)
617a11b88adSMika Westerberg {
618a11b88adSMika Westerberg 	int i;
619a11b88adSMika Westerberg 
620a11b88adSMika Westerberg 	for (i = 0; i < path->path_length; i++) {
6210bd680cdSMika Westerberg 		if (path->hops[i].in_port == port ||
6220bd680cdSMika Westerberg 		    path->hops[i].out_port == port)
623a11b88adSMika Westerberg 			return true;
624a11b88adSMika Westerberg 	}
625a11b88adSMika Westerberg 
626a11b88adSMika Westerberg 	return false;
627a11b88adSMika Westerberg }
628