Skip to content

Commit aec2c25

Browse files
committed
infiniband-diags: Add build_dr_path_to_lid and use it in iblinkinfo
Add build_dr_path_to_lid() to the ibdiags_tools library so all infiniband-diags tools can build a direct route (DR) path to a destination LID. It follows switch Linear Forwarding Tables via live SMP queries. This is needed for XDR: SMP via LID is not guaranteed to work in XDR fabrics, so utilities must be able to build and walk a direct route to reach a destination LID. Use it in iblinkinfo: when a start point is given via -S/-G, resolve the GUID to a LID and build a DR path to it so the partial scan starts from a DR-addressed portid instead of relying on LID routing. Signed-off-by: Asaf Mazor <amazor@nvidia.com>
1 parent a7453f8 commit aec2c25

4 files changed

Lines changed: 408 additions & 0 deletions

File tree

infiniband-diags/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
publish_internal_headers(""
22
ibdiag_common.h
3+
ibdiag_dr.h
34
ibdiag_sa.h
45
)
56

@@ -10,6 +11,7 @@ install(FILES
1011

1112
add_library(ibdiags_tools STATIC
1213
ibdiag_common.c
14+
ibdiag_dr.c
1315
ibdiag_sa.c
1416
)
1517

infiniband-diags/ibdiag_dr.c

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
2+
// Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
4+
/*
5+
* Direct route construction.
6+
*
7+
* Given a destination LID and a starting portid (LID- or DR-addressed),
8+
* build the direct route to that LID by following switch Linear Forwarding
9+
* Tables hop-by-hop via live SMP queries. This is the single implementation
10+
* of the unicast route walk, available to all diags via ibdiags_tools.
11+
*/
12+
13+
#define _GNU_SOURCE
14+
#include <stdio.h>
15+
#include <stdlib.h>
16+
#include <string.h>
17+
#include <ctype.h>
18+
#include <inttypes.h>
19+
#include <stdbool.h>
20+
21+
#include <infiniband/umad.h>
22+
#include <infiniband/mad.h>
23+
#include <util/node_name_map.h>
24+
25+
#include "ibdiag_common.h"
26+
#include "ibdiag_dr.h"
27+
28+
#define IBDIAG_MAX_HOPS 63
29+
30+
static const char * const dr_node_type_str[] = {
31+
"???",
32+
"ca",
33+
"switch",
34+
"router",
35+
"iwarp rnic"
36+
};
37+
38+
struct dr_node {
39+
int type;
40+
int numports;
41+
uint64_t nodeguid;
42+
char nodeinfo[64];
43+
char nodedesc[IB_SMP_DATA_SIZE + 1];
44+
};
45+
46+
struct dr_port {
47+
uint64_t portguid;
48+
int portnum;
49+
int lid;
50+
int lmc;
51+
int state;
52+
char portinfo[64];
53+
};
54+
55+
struct dr_switch {
56+
int linearcap;
57+
int linearFDBtop;
58+
int enhsp0;
59+
uint8_t fdb[64];
60+
char switchinfo[64];
61+
};
62+
63+
static int dr_get_node(struct dr_node *node, struct dr_port *port,
64+
ib_portid_t *portid, const struct ibmad_port *srcport,
65+
int timeout)
66+
{
67+
void *pi = port->portinfo, *ni = node->nodeinfo, *nd = node->nodedesc;
68+
char *s, *e;
69+
70+
memset(ni, 0, sizeof(node->nodeinfo));
71+
if (!smp_query_via(ni, portid, IB_ATTR_NODE_INFO, 0, timeout, srcport))
72+
return -1;
73+
74+
memset(nd, 0, sizeof(node->nodedesc));
75+
if (!smp_query_via(nd, portid, IB_ATTR_NODE_DESC, 0, timeout, srcport))
76+
return -1;
77+
78+
for (s = nd, e = s + 64; s < e; s++) {
79+
if (!*s)
80+
break;
81+
if (!isprint(*s))
82+
*s = ' ';
83+
}
84+
85+
memset(pi, 0, sizeof(port->portinfo));
86+
if (!smp_query_via(pi, portid, IB_ATTR_PORT_INFO, 0, timeout, srcport))
87+
return -1;
88+
89+
mad_decode_field(ni, IB_NODE_GUID_F, &node->nodeguid);
90+
mad_decode_field(ni, IB_NODE_TYPE_F, &node->type);
91+
mad_decode_field(ni, IB_NODE_NPORTS_F, &node->numports);
92+
93+
mad_decode_field(ni, IB_NODE_PORT_GUID_F, &port->portguid);
94+
mad_decode_field(ni, IB_NODE_LOCAL_PORT_F, &port->portnum);
95+
mad_decode_field(pi, IB_PORT_LID_F, &port->lid);
96+
mad_decode_field(pi, IB_PORT_LMC_F, &port->lmc);
97+
mad_decode_field(pi, IB_PORT_STATE_F, &port->state);
98+
99+
DEBUG("portid %s: got node 0x%" PRIx64 " '%s'", portid2str(portid),
100+
node->nodeguid, node->nodedesc);
101+
return 0;
102+
}
103+
104+
static int dr_switch_lookup(struct dr_switch *sw, ib_portid_t *portid, int lid,
105+
const struct ibmad_port *srcport, int timeout)
106+
{
107+
void *si = sw->switchinfo, *fdb = sw->fdb;
108+
109+
memset(si, 0, sizeof(sw->switchinfo));
110+
if (!smp_query_via(si, portid, IB_ATTR_SWITCH_INFO, 0, timeout,
111+
srcport))
112+
return -1;
113+
114+
mad_decode_field(si, IB_SW_LINEAR_FDB_CAP_F, &sw->linearcap);
115+
mad_decode_field(si, IB_SW_LINEAR_FDB_TOP_F, &sw->linearFDBtop);
116+
mad_decode_field(si, IB_SW_ENHANCED_PORT0_F, &sw->enhsp0);
117+
118+
if (lid >= sw->linearcap && lid > sw->linearFDBtop)
119+
return -1;
120+
121+
memset(fdb, 0, sizeof(sw->fdb));
122+
if (!smp_query_via(fdb, portid, IB_ATTR_LINEARFORWTBL, lid / 64,
123+
timeout, srcport))
124+
return -1;
125+
126+
DEBUG("portid %s: forward lid %d to port %d", portid2str(portid), lid,
127+
sw->fdb[lid % 64]);
128+
return sw->fdb[lid % 64];
129+
}
130+
131+
static bool dr_sameport(struct dr_port *a, struct dr_port *b)
132+
{
133+
return (a->portguid == b->portguid) || (a->lid == b->lid);
134+
}
135+
136+
/*
137+
* Returns true when the port is not active and false when active.
138+
* Base switch port 0 is considered always active.
139+
*/
140+
static bool dr_port_inactive(struct dr_node *node, struct dr_port *port,
141+
struct dr_switch *sw)
142+
{
143+
return port->state != 4 &&
144+
(node->type != IB_NODE_SWITCH ||
145+
(node->type == IB_NODE_SWITCH && sw->enhsp0));
146+
}
147+
148+
static int dr_extend_dpath(ib_portid_t *portid, int nextport)
149+
{
150+
if (portid->drpath.cnt + 2 >= sizeof(portid->drpath.p))
151+
return -1;
152+
++portid->drpath.cnt;
153+
portid->drpath.p[portid->drpath.cnt] = (uint8_t)nextport;
154+
155+
return portid->drpath.cnt;
156+
}
157+
158+
static void dr_dump_endnode(FILE *out, nn_map_t *node_name_map, int dump,
159+
const char *prompt, struct dr_node *node,
160+
struct dr_port *port)
161+
{
162+
char *nodename = NULL;
163+
164+
if (!dump)
165+
return;
166+
if (dump == 1) {
167+
fprintf(out, "%s {0x%016" PRIx64 "}[%d]\n",
168+
prompt, node->nodeguid,
169+
node->type == IB_NODE_SWITCH ? 0 : port->portnum);
170+
return;
171+
}
172+
173+
nodename =
174+
remap_node_name(node_name_map, node->nodeguid, node->nodedesc);
175+
176+
fprintf(out, "%s %s {0x%016" PRIx64 "} portnum %d lid %u-%u \"%s\"\n",
177+
prompt,
178+
(node->type <= IB_NODE_MAX ? dr_node_type_str[node->type] :
179+
"???"),
180+
node->nodeguid,
181+
node->type == IB_NODE_SWITCH ? 0 : port->portnum, port->lid,
182+
port->lid + (1 << port->lmc) - 1, nodename);
183+
184+
free(nodename);
185+
}
186+
187+
static void dr_dump_route(FILE *out, nn_map_t *node_name_map, int dump,
188+
struct dr_node *node, int outport,
189+
struct dr_port *port)
190+
{
191+
char *nodename = NULL;
192+
193+
if (!dump && !ibverbose)
194+
return;
195+
196+
nodename =
197+
remap_node_name(node_name_map, node->nodeguid, node->nodedesc);
198+
199+
if (dump == 1)
200+
fprintf(out, "[%d] -> {0x%016" PRIx64 "}[%d]\n",
201+
outport, port->portguid, port->portnum);
202+
else
203+
fprintf(out, "[%d] -> %s port {0x%016" PRIx64
204+
"}[%d] lid %u-%u \"%s\"\n", outport,
205+
(node->type <=
206+
IB_NODE_MAX ? dr_node_type_str[node->type] : "???"),
207+
port->portguid, port->portnum, port->lid,
208+
port->lid + (1 << port->lmc) - 1, nodename);
209+
210+
free(nodename);
211+
}
212+
213+
int build_dr_path_to_lid(const struct ibmad_port *srcport, int timeout,
214+
ib_portid_t *from, uint16_t dest_lid, int dump,
215+
FILE *out, nn_map_t *node_name_map, int force)
216+
{
217+
struct dr_node fromnode, nextnode;
218+
struct dr_port fromport, toport, nextport;
219+
struct dr_switch sw;
220+
struct dr_node *node;
221+
struct dr_port *port;
222+
int maxhops = IBDIAG_MAX_HOPS;
223+
int portnum, outport = 255, next_sw_outport = 255;
224+
225+
memset(&fromnode, 0, sizeof(fromnode));
226+
memset(&nextnode, 0, sizeof(nextnode));
227+
memset(&fromport, 0, sizeof(fromport));
228+
memset(&toport, 0, sizeof(toport));
229+
memset(&nextport, 0, sizeof(nextport));
230+
memset(&sw, 0, sizeof(sw));
231+
232+
DEBUG("from %s to lid %u", portid2str(from), dest_lid);
233+
234+
if (dr_get_node(&fromnode, &fromport, from, srcport, timeout) < 0) {
235+
IBWARN("can't reach from port");
236+
if (!force)
237+
return -1;
238+
IBWARN("Force: look for lid %d", dest_lid);
239+
}
240+
241+
if (dest_lid > 0)
242+
toport.lid = dest_lid;
243+
244+
node = &fromnode;
245+
port = &fromport;
246+
portnum = port->portnum;
247+
248+
dr_dump_endnode(out, node_name_map, dump, "From", node, port);
249+
250+
if (node->type == IB_NODE_SWITCH) {
251+
next_sw_outport = dr_switch_lookup(&sw, from, dest_lid, srcport,
252+
timeout);
253+
if (next_sw_outport < 0 || next_sw_outport > node->numports) {
254+
outport = next_sw_outport;
255+
goto badtbl;
256+
}
257+
}
258+
259+
while (maxhops--) {
260+
if (dr_port_inactive(node, port, &sw))
261+
goto badport;
262+
263+
if (dr_sameport(port, &toport))
264+
break; /* found */
265+
266+
if (node->type == IB_NODE_SWITCH) {
267+
outport = next_sw_outport;
268+
269+
if (dr_extend_dpath(from, outport) < 0)
270+
goto badpath;
271+
272+
if (dr_get_node(&nextnode, &nextport, from, srcport,
273+
timeout) < 0) {
274+
IBWARN("can't reach port at %s",
275+
portid2str(from));
276+
return -1;
277+
}
278+
if (outport == 0) {
279+
if (!dr_sameport(&nextport, &toport))
280+
goto badtbl;
281+
else
282+
break; /* found SMA port */
283+
}
284+
} else if ((node->type == IB_NODE_CA) ||
285+
(node->type == IB_NODE_ROUTER)) {
286+
int ca_src = 0;
287+
288+
outport = portnum;
289+
if ((dest_lid & ~((1 << port->lmc) - 1)) == port->lid)
290+
break;
291+
if (!dr_sameport(port, &fromport)) {
292+
IBWARN("can't continue: reached CA or router port 0x%" PRIx64 ", lid %d",
293+
port->portguid, port->lid);
294+
return -1;
295+
}
296+
297+
/* we are at CA or router "from" - go one hop back to
298+
* (hopefully) a switch
299+
*/
300+
if (from->drpath.cnt > 0) {
301+
from->drpath.cnt--;
302+
} else {
303+
ca_src = 1;
304+
if (portnum &&
305+
dr_extend_dpath(from, portnum) < 0)
306+
goto badpath;
307+
}
308+
if (dr_get_node(&nextnode, &nextport, from, srcport,
309+
timeout) < 0) {
310+
IBWARN("can't reach port at %s",
311+
portid2str(from));
312+
return -1;
313+
}
314+
/* fix port num to be seen from the CA or router side */
315+
if (!ca_src)
316+
nextport.portnum =
317+
from->drpath.p[from->drpath.cnt + 1];
318+
}
319+
/* only if the next node is a switch, get switch info */
320+
if (nextnode.type == IB_NODE_SWITCH) {
321+
next_sw_outport = dr_switch_lookup(&sw, from, dest_lid,
322+
srcport, timeout);
323+
if (next_sw_outport < 0 ||
324+
next_sw_outport > nextnode.numports) {
325+
outport = next_sw_outport;
326+
goto badtbl;
327+
}
328+
}
329+
330+
port = &nextport;
331+
if (dr_port_inactive(&nextnode, port, &sw))
332+
goto badoutport;
333+
node = &nextnode;
334+
portnum = port->portnum;
335+
dr_dump_route(out, node_name_map, dump, node, outport, port);
336+
}
337+
338+
if (maxhops <= 0) {
339+
IBWARN("no route found after %d hops", IBDIAG_MAX_HOPS);
340+
return -1;
341+
}
342+
343+
dr_dump_endnode(out, node_name_map, dump, "To", node, port);
344+
345+
return 0;
346+
347+
badport:
348+
IBWARN("Bad port state found: node \"%s\" port %d state %d",
349+
clean_nodedesc(node->nodedesc), portnum, port->state);
350+
return -1;
351+
badoutport:
352+
IBWARN("Bad out port state found: node \"%s\" outport %d state %d",
353+
clean_nodedesc(node->nodedesc), outport, port->state);
354+
return -1;
355+
badtbl:
356+
IBWARN("Bad forwarding table entry found at: node \"%s\" lid entry %d is %d (top %d)",
357+
clean_nodedesc(node->nodedesc), dest_lid, outport,
358+
sw.linearFDBtop);
359+
return -1;
360+
badpath:
361+
IBWARN("Direct path too long!");
362+
return -1;
363+
}

0 commit comments

Comments
 (0)