xref: /illumos-gate/usr/src/cmd/cxgbetool/cxgbetool.c (revision fc910014e8a32a65612105835a10995f2c13d942)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright (c) 2018 by Chelsio Communications, Inc.
14  */
15 
16 /*
17  * Copyright 2019 Joyent, Inc.
18  * Copyright 2023 Oxide Computer Company
19  */
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <stropts.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <sys/socket.h>
29 #include <strings.h>
30 #include <sys/varargs.h>
31 #include <errno.h>
32 #include <sys/byteorder.h>
33 #include <inttypes.h>
34 #include <sys/sysmacros.h>
35 #include <err.h>
36 #include <libdevinfo.h>
37 
38 #include "t4nex.h"
39 #include "version.h"
40 #include "osdep.h"
41 #include "t4fw_interface.h"
42 #include "cudbg.h"
43 #include "cudbg_lib_common.h"
44 
45 #define CUDBG_SIZE (32 * 1024 * 1024)
46 #define CUDBG_MAX_ENTITY_STR_LEN 4096
47 #define MAX_PARAM_LEN 4096
48 
49 static char cxgbetool_nexus[PATH_MAX];
50 
51 char *option_list[] = {
52 	"--collect",
53 	"--view",
54 	"--version",
55 };
56 
57 enum {
58 	CUDBG_OPT_COLLECT,
59 	CUDBG_OPT_VIEW,
60 	CUDBG_OPT_VERSION,
61 };
62 
63 /*
64  * Firmware Device Log Dumping
65  */
66 
67 static const char * const devlog_level_strings[] = {
68 	[FW_DEVLOG_LEVEL_EMERG]		= "EMERG",
69 	[FW_DEVLOG_LEVEL_CRIT]		= "CRIT",
70 	[FW_DEVLOG_LEVEL_ERR]		= "ERR",
71 	[FW_DEVLOG_LEVEL_NOTICE]	= "NOTICE",
72 	[FW_DEVLOG_LEVEL_INFO]		= "INFO",
73 	[FW_DEVLOG_LEVEL_DEBUG]		= "DEBUG"
74 };
75 
76 static const char * const devlog_facility_strings[] = {
77 	[FW_DEVLOG_FACILITY_CORE]	= "CORE",
78 	[FW_DEVLOG_FACILITY_CF]		= "CF",
79 	[FW_DEVLOG_FACILITY_SCHED]	= "SCHED",
80 	[FW_DEVLOG_FACILITY_TIMER]	= "TIMER",
81 	[FW_DEVLOG_FACILITY_RES]	= "RES",
82 	[FW_DEVLOG_FACILITY_HW]		= "HW",
83 	[FW_DEVLOG_FACILITY_FLR]	= "FLR",
84 	[FW_DEVLOG_FACILITY_DMAQ]	= "DMAQ",
85 	[FW_DEVLOG_FACILITY_PHY]	= "PHY",
86 	[FW_DEVLOG_FACILITY_MAC]	= "MAC",
87 	[FW_DEVLOG_FACILITY_PORT]	= "PORT",
88 	[FW_DEVLOG_FACILITY_VI]		= "VI",
89 	[FW_DEVLOG_FACILITY_FILTER]	= "FILTER",
90 	[FW_DEVLOG_FACILITY_ACL]	= "ACL",
91 	[FW_DEVLOG_FACILITY_TM]		= "TM",
92 	[FW_DEVLOG_FACILITY_QFC]	= "QFC",
93 	[FW_DEVLOG_FACILITY_DCB]	= "DCB",
94 	[FW_DEVLOG_FACILITY_ETH]	= "ETH",
95 	[FW_DEVLOG_FACILITY_OFLD]	= "OFLD",
96 	[FW_DEVLOG_FACILITY_RI]		= "RI",
97 	[FW_DEVLOG_FACILITY_ISCSI]	= "ISCSI",
98 	[FW_DEVLOG_FACILITY_FCOE]	= "FCOE",
99 	[FW_DEVLOG_FACILITY_FOISCSI]	= "FOISCSI",
100 	[FW_DEVLOG_FACILITY_FOFCOE]	= "FOFCOE",
101 	[FW_DEVLOG_FACILITY_CHNET]	= "CHNET",
102 };
103 
104 static const char *progname;
105 int set_dbg_entity(u8 *dbg_bitmap, char *dbg_entity_list);
106 
107 static int check_option(char *opt)
108 {
109 	int i;
110 
111 	for (i = 0; i < ARRAY_SIZE(option_list); i++) {
112 		if (!strcmp(opt, option_list[i]))
113 			return i;
114 	}
115 	return -1;
116 }
117 
118 static void usage(FILE *fp)
119 {
120 	fprintf(fp, "Usage: %s <t4nex# | cxgbe#> [operation]\n", progname);
121 	fprintf(fp,
122 	    "\tdevlog                              show device log\n"
123 	    "\tloadfw <FW image>                   Flash the FW image\n"
124 	    "\tcudbg <option> [<args>]             Chelsio Unified Debugger\n");
125 	exit(fp == stderr ? 1 : 0);
126 }
127 
128 static int
129 doit(const char *iff_name, unsigned long cmd, void *data)
130 {
131 	int fd = 0;
132 	int rc = 0;
133 
134 	if ((fd = open(iff_name, O_RDWR)) < 0)
135 		return (-1);
136 
137 	rc = (ioctl(fd, cmd, data) < 0) ? errno : rc;
138 	close(fd);
139 	return (rc);
140 }
141 
142 static void
143 get_devlog(int argc, char *argv[], int start_arg, const char *iff_name)
144 {
145 	struct t4_devlog *devlog;
146 	struct fw_devlog_e *entry, *buf;
147 	int rc = 0, first = 0, nentries, i, j, len;
148 	uint64_t ftstamp = UINT64_MAX;
149 
150 	devlog = malloc(T4_DEVLOG_SIZE + sizeof (struct t4_devlog));
151 	if (!devlog)
152 		err(1, "%s: can't allocate devlog buffer", __func__);
153 
154 	devlog->len = T4_DEVLOG_SIZE;
155 	/* Get device log */
156 	rc = doit(iff_name, T4_IOCTL_DEVLOG, devlog);
157 	if (rc == ENOBUFS) {
158 		/*
159 		 * Default buffer size is not sufficient to hold device log.
160 		 * Driver has updated the devlog.len to indicate the expected
161 		 * size. Free the currently allocated devlog.data, allocate
162 		 * again with right size and retry.
163 		 */
164 		len = devlog->len;
165 		free(devlog);
166 
167 		if ((devlog = malloc(len + sizeof (struct t4_devlog))) == NULL)
168 			err(1, "%s: can't reallocate devlog buffer", __func__);
169 
170 		rc = doit(iff_name, T4_IOCTL_DEVLOG, devlog);
171 	}
172 	if (rc) {
173 		free(devlog);
174 		errx(1, "%s: can't get device log", __func__);
175 	}
176 
177 	/* There are nentries number of entries in the buffer */
178 	nentries = (devlog->len / sizeof (struct fw_devlog_e));
179 
180 	buf = (struct fw_devlog_e *)devlog->data;
181 
182 	/* Find the first entry */
183 	for (i = 0; i < nentries; i++) {
184 		entry = &buf[i];
185 
186 		if (entry->timestamp == 0)
187 			break;
188 
189 		entry->timestamp = BE_64(entry->timestamp);
190 		entry->seqno = BE_32(entry->seqno);
191 		for (j = 0; j < 8; j++)
192 			entry->params[j] = BE_32(entry->params[j]);
193 
194 		if (entry->timestamp < ftstamp) {
195 			ftstamp = entry->timestamp;
196 			first = i;
197 		}
198 	}
199 
200 	printf("%10s  %15s  %8s  %8s  %s\n", "Seq#", "Tstamp", "Level",
201 	    "Facility", "Message");
202 
203 	i = first;
204 
205 	do {
206 		entry = &buf[i];
207 
208 		if (entry->timestamp == 0)
209 			break;
210 
211 		printf("%10d  %15llu  %8s  %8s  ", entry->seqno,
212 		    entry->timestamp,
213 		    (entry->level < ARRAY_SIZE(devlog_level_strings) ?
214 		    devlog_level_strings[entry->level] : "UNKNOWN"),
215 		    (entry->facility < ARRAY_SIZE(devlog_facility_strings) ?
216 		    devlog_facility_strings[entry->facility] : "UNKNOWN"));
217 
218 		printf((const char *)entry->fmt, entry->params[0],
219 		    entry->params[1], entry->params[2], entry->params[3],
220 		    entry->params[4], entry->params[5], entry->params[6],
221 		    entry->params[7]);
222 
223 		if (++i == nentries)
224 			i = 0;
225 
226 	} while (i != first);
227 
228 	free(devlog);
229 }
230 
231 static void
232 load_fw(int argc, char *argv[], int start_arg, const char *iff_name)
233 {
234 	const char *fname = argv[start_arg];
235 	struct t4_ldfw *fw;
236 	struct stat sb;
237 	size_t len;
238 	int fd;
239 
240 	if (argc != 4)
241 		errx(1, "incorrect number of arguments");
242 
243 	fd = open(fname, O_RDONLY);
244 	if (fd < 0)
245 		err(1, "%s: opening %s failed", __func__, fname);
246 	if (fstat(fd, &sb) < 0) {
247 		warn("%s: fstat %s failed", __func__, fname);
248 		close(fd);
249 		exit(1);
250 	}
251 	len = (size_t)sb.st_size;
252 
253 	fw = malloc(sizeof (struct t4_ldfw) + len);
254 	if (!fw) {
255 		warn("%s: %s allocate %zu bytes failed",
256 		    __func__, fname, sizeof (struct t4_ldfw) + len);
257 		close(fd);
258 		exit(1);
259 	}
260 
261 	if (read(fd, fw->data, len) < len) {
262 		warn("%s: %s read failed", __func__, fname);
263 		close(fd);
264 		free(fw);
265 		exit(1);
266 	}
267 
268 	close(fd);
269 
270 	fw->len = len;
271 
272 	if (doit(iff_name, T4_IOCTL_LOAD_FW, fw)) {
273 		free(fw);
274 		err(1, "%s: IOCTL failed", __func__);
275 	} else {
276 		printf("FW flash success, reload driver/reboot to take "
277 		    "effect\n");
278 	}
279 
280 	free(fw);
281 }
282 
283 int read_input_file(char *in_file, void **buf, int *buf_size)
284 {
285 	FILE *fptr = NULL;
286 	size_t count;
287 	int rc = 0;
288 
289 	fptr = fopen(in_file, "rb");
290 	if (!fptr) {
291 		perror("error in opening file ");
292 		rc = -1;
293 		goto out;
294 	}
295 	rc = fseek(fptr, 0, SEEK_END);
296 	if (rc < 0) {
297 		perror("error in seeking file ");
298 		rc = -1;
299 		goto out;
300 	}
301 	*buf_size = ftell(fptr);
302 	rc = fseek(fptr, 0, SEEK_SET);
303 	if (rc < 0) {
304 		perror("error in seeking file ");
305 		rc = -1;
306 		goto out;
307 	}
308 	*buf = (void *) malloc(*buf_size);
309 	if (*buf == NULL) {
310 		rc = CUDBG_STATUS_NOSPACE;
311 		goto out;
312 	}
313 	memset(*buf, 0, *buf_size);
314 
315 	count = fread(*buf, 1, *buf_size, fptr);
316 	if (count != *buf_size) {
317 		perror("error in reading from file ");
318 		goto out;
319 	}
320 
321 out:
322 	if (fptr)
323 		fclose(fptr);
324 
325 	return rc;
326 }
327 
328 static void
329 do_collect(char *dbg_entity_list, const char *iff_name, const char *fname)
330 {
331 	struct t4_cudbg_dump *cudbg;
332 	int fd;
333 
334 	cudbg = malloc(sizeof(struct t4_cudbg_dump) + CUDBG_SIZE);
335 	if (!cudbg) {
336 		err(1, "%s:allocate %d bytes failed", __func__, CUDBG_SIZE);
337 	}
338 
339 	memset(cudbg, 0, sizeof(struct t4_cudbg_dump) + CUDBG_SIZE);
340 
341 	cudbg->len = CUDBG_SIZE;
342 
343 	set_dbg_entity(cudbg->bitmap, dbg_entity_list);
344 
345 	if (doit(iff_name, T4_IOCTL_GET_CUDBG, cudbg)) {
346 		free(cudbg);
347 		err(1, "%s: IOCTL failed", __func__);
348 	}
349 
350 	fd = open(fname, O_CREAT | O_TRUNC | O_EXCL | O_WRONLY,
351 		  S_IRUSR | S_IRGRP | S_IROTH);
352 	if (fd < 0) {
353 		err(1, "%s: file open failed", __func__);
354 	}
355 
356 	write(fd, cudbg->data, cudbg->len);
357 	close(fd);
358 	free(cudbg);
359 }
360 
361 static void
362 do_view(char *dbg_entity_list, char *in_file)
363 {
364 	void *handle = NULL;
365 	void *buf = NULL;
366 	int buf_size = 32 * 1024 * 1024;
367 	int  next_offset = 0;
368 	int data_len;
369 	int rc = 0;
370 
371 	handle = cudbg_alloc_handle();
372 	if (!handle)
373 		goto out;
374 	/* rcad from file */
375 	rc = read_input_file(in_file, &buf, &buf_size);
376 	if (rc < 0) {
377 		goto out;
378 	}
379 
380 	set_dbg_entity(((struct cudbg_private *)handle)->dbg_init.dbg_bitmap,
381 			dbg_entity_list);
382 	do {
383 		if (buf_size - next_offset <= 0)
384 			break;
385 
386 		data_len = cudbg_view(handle, buf+next_offset,
387 				buf_size-next_offset, NULL, 0);
388 		next_offset += data_len;
389 		if (data_len > 0)
390 			printf("\n\t\t<========================END============="\
391 					"===========>\t\t\n\n\n");
392 	} while (data_len > 0);
393 
394 out:
395 	if (buf)
396 		free(buf);
397 	if (handle)
398 		cudbg_free_handle(handle);
399 	return;
400 }
401 
402 typedef void (*cudbg_alias_get_entities_cb)(char *dst, u32 dst_size);
403 
404 struct entity_access_list {
405         const char *name;
406         cudbg_alias_get_entities_cb get_entities_cb;
407 };
408 
409 void
410 cudbg_append_string(char *dst, u32 dst_size, char *src)
411 {
412         strlcat(dst, src, dst_size);
413         strlcat(dst, ",", dst_size);
414 }
415 
416 static void
417 cudbg_alias_get_allregs(char *dst, u32 dst_size)
418 {
419         u32 i;
420 
421         for (i = 0; i < ARRAY_SIZE(entity_list); i++)
422                 if (entity_list[i].flag & (1 << ENTITY_FLAG_REGISTER))
423                         cudbg_append_string(dst, dst_size, entity_list[i].name);
424 }
425 
426 static struct entity_access_list ATTRIBUTE_UNUSED entity_alias_list[] = {
427         {"allregs", cudbg_alias_get_allregs},
428 };
429 
430 static int
431 check_dbg_entity(char *entity)
432 {
433 	u32 i;
434 
435 	for (i = 0; i < ARRAY_SIZE(entity_list); i++)
436 		if (!strcmp(entity, entity_list[i].name))
437 			return entity_list[i].bit;
438 	return -1;
439 }
440 
441 /* Get matching alias index from entity_alias_list[] */
442 static
443 int get_alias(const char *entity)
444 {
445 	u32 i;
446 
447 	for (i = 0; i < ARRAY_SIZE(entity_alias_list); i++)
448 		if (!strcmp(entity, entity_alias_list[i].name))
449 			return i;
450 	return -1;
451 }
452 
453 static int
454 parse_entity_list(const char *dbg_entity_list, char *dst,
455 				    u32 dst_size)
456 {
457 	char *tmp_dbg_entity_list;
458 	char *dbg_entity;
459 	int rc, i;
460 
461 	/* Holds single entity name de-limited by comma */
462 	tmp_dbg_entity_list = malloc(CUDBG_MAX_ENTITY_STR_LEN);
463 	if (!tmp_dbg_entity_list)
464 		return ENOMEM;
465 
466 	strlcpy(tmp_dbg_entity_list, dbg_entity_list, CUDBG_MAX_ENTITY_STR_LEN);
467 	dbg_entity = strtok(tmp_dbg_entity_list, ",");
468 	while (dbg_entity != NULL) {
469 		/* See if specified entity name exists.  If it doesn't
470 		 * exist, see if the entity name is an alias.
471 		 * If it's not a valid entity name, bail with error.
472 		 */
473 		rc = check_dbg_entity(dbg_entity);
474 		if (rc < 0) {
475 			i = get_alias(dbg_entity);
476 			if (i < 0) {
477 				/* Not an alias, and not a valid entity name */
478 				printf("\nUnknown entity: %s\n", dbg_entity);
479 				rc = CUDBG_STATUS_ENTITY_NOT_FOUND;
480 				goto out_err;
481 			} else {
482 				/* If alias is found, get all the corresponding
483 				 * debug entities related to the alias.
484 				 */
485 				entity_alias_list[i].get_entities_cb(dst, dst_size);
486 			}
487 		} else {
488 			/* Not an alias, but is a valid entity name.
489 			 * So, append the corresponding debug entity.
490 			 */
491 			cudbg_append_string(dst, dst_size, entity_list[rc].name);
492 		}
493 		dbg_entity = strtok(NULL, ",");
494 	}
495 
496 	rc = 0;
497 
498 out_err:
499 	free(tmp_dbg_entity_list);
500 	return rc;
501 }
502 
503 static
504 int get_entity_list(const char *in_buff, char **out_buff)
505 {
506 	char *dbg_entity_list;
507 	int rc;
508 
509 	/* Allocate enough to hold converted alias string.
510 	 * Must be freed by caller
511 	 */
512 	dbg_entity_list = malloc(CUDBG_MAX_ENTITY_STR_LEN);
513 	if (!dbg_entity_list)
514 		return ENOMEM;
515 
516 	memset(dbg_entity_list, 0, CUDBG_MAX_ENTITY_STR_LEN);
517 	rc = parse_entity_list(in_buff, dbg_entity_list,
518 			       CUDBG_MAX_ENTITY_STR_LEN);
519 	if (rc) {
520 		free(dbg_entity_list);
521 		return rc;
522 	}
523 
524 	/* Remove the last comma */
525 	dbg_entity_list[strlen(dbg_entity_list) - 1] = '\0';
526 	*out_buff = dbg_entity_list;
527 	return 0;
528 }
529 
530 static void
531 put_entity_list(char *buf)
532 {
533 	if (buf)
534 		free(buf);
535 }
536 
537 int
538 set_dbg_entity(u8 *dbg_bitmap, char *dbg_entity_list)
539 {
540 	int i, dbg_entity_bit, rc = 0;
541 	char *dbg_entity;
542 	char *dbg_entity_list_tmp;
543 
544 	dbg_entity_list_tmp = malloc(MAX_PARAM_LEN);
545 	if (!dbg_entity_list_tmp) {
546 		rc = CUDBG_STATUS_NOSPACE;
547 		return rc;
548 	}
549 
550 	if (dbg_entity_list != NULL) {
551 		strlcpy(dbg_entity_list_tmp, dbg_entity_list, MAX_PARAM_LEN);
552 		dbg_entity = strtok(dbg_entity_list_tmp, ",");
553 	}
554 	else
555 		dbg_entity = NULL;
556 
557 	while (dbg_entity != NULL) {
558 		rc = check_dbg_entity(dbg_entity);
559 		if (rc < 0) {
560 			printf("\n\tInvalid debug entity: %s\n", dbg_entity);
561 			//Vishal cudbg_usage();
562 			goto out_free;
563 		}
564 
565 		dbg_entity_bit = rc;
566 
567 		if (dbg_entity_bit == CUDBG_ALL) {
568 			for (i = 1; i < CUDBG_MAX_ENTITY; i++)
569 				set_dbg_bitmap(dbg_bitmap, i);
570 			set_dbg_bitmap(dbg_bitmap, CUDBG_ALL);
571 			break;
572 		} else {
573 			set_dbg_bitmap(dbg_bitmap, dbg_entity_bit);
574 		}
575 
576 		dbg_entity = strtok(NULL, ",");
577 	}
578 
579 	rc = 0;
580 
581 out_free:
582 	free(dbg_entity_list_tmp);
583 	return rc;
584 }
585 
586 
587 static void
588 get_cudbg(int argc, char *argv[], int start_arg, const char *iff_name)
589 {
590 	char *dbg_entity_list = NULL;
591 	int rc = 0, option;
592 
593 	if (start_arg >= argc)
594 		errx(1, "no option provided");
595 
596 	rc = check_option(argv[start_arg++]);
597 	if (rc < 0) {
598 		errx(1, "%s:Invalid option provided", __func__);
599 	}
600 	option = rc;
601 
602 	if (option == CUDBG_OPT_VERSION) {
603 		printf("Library Version %d.%d.%d\n", CUDBG_MAJOR_VERSION,
604 			CUDBG_MINOR_VERSION, CUDBG_BUILD_VERSION);
605 		return;
606 	}
607 
608 	if (argc < 5) {
609 		errx(1, "Invalid number of arguments\n");
610 	}
611 	rc = get_entity_list(argv[start_arg++],
612 			     &dbg_entity_list);
613 	if (rc) {
614 		errx(1, "Error in parsing entity\n");
615 	}
616 
617 	if (argc < 6) {
618 		errx(1, "File name is missing\n");
619 	}
620 
621 	switch (option) {
622 		case CUDBG_OPT_COLLECT:
623 			do_collect(dbg_entity_list, iff_name, argv[start_arg]);
624 			break;
625 		case CUDBG_OPT_VIEW:
626 			do_view(dbg_entity_list, argv[start_arg]);
627 			break;
628 		default:
629 			errx(1, "Wrong option provided\n");
630 	}
631 
632 	put_entity_list(dbg_entity_list);
633 }
634 
635 static void
636 run_cmd(int argc, char *argv[], const char *iff_name)
637 {
638 	if (strcmp(argv[2], "devlog") == 0)
639 		get_devlog(argc, argv, 3, iff_name);
640 	else if (strcmp(argv[2], "loadfw") == 0)
641 		load_fw(argc, argv, 3, iff_name);
642 	else if (strcmp(argv[2], "cudbg") == 0)
643 		get_cudbg(argc, argv, 3, iff_name);
644 	else
645 		usage(stderr);
646 }
647 
648 /*
649  * Traditionally we expect to be given a path to the t4nex device control file
650  * hidden in /devices. To make life easier, we want to also support folks using
651  * the driver instance numbers for either a given t4nex%d or cxgbe%d. We check
652  * to see if we've been given a path to a character device and if so, just
653  * continue straight on with the given argument. Otherwise we attempt to map it
654  * to something known.
655  */
656 static const char *
657 cxgbetool_parse_path(char *arg)
658 {
659 	struct stat st;
660 	di_node_t root, node;
661 	const char *numptr, *errstr;
662 	size_t drvlen;
663 	int inst;
664 	boolean_t is_t4nex = B_TRUE;
665 	char mname[64];
666 
667 	if (stat(arg, &st) == 0) {
668 		if (S_ISCHR(st.st_mode)) {
669 			return (arg);
670 		}
671 	}
672 
673 	if (strncmp(arg, T4_NEXUS_NAME, sizeof (T4_NEXUS_NAME) - 1) == 0) {
674 		drvlen = sizeof (T4_NEXUS_NAME) - 1;
675 	} else if (strncmp(arg, T4_PORT_NAME, sizeof (T4_PORT_NAME) - 1) == 0) {
676 		is_t4nex = B_FALSE;
677 		drvlen = sizeof (T4_PORT_NAME) - 1;
678 	} else {
679 		errx(EXIT_FAILURE, "cannot use device %s: not a character "
680 		    "device or a %s/%s device instance", arg, T4_PORT_NAME,
681 		    T4_NEXUS_NAME);
682 	}
683 
684 	numptr = arg + drvlen;
685 	inst = (int)strtonum(numptr, 0, INT_MAX, &errstr);
686 	if (errstr != NULL) {
687 		errx(EXIT_FAILURE, "failed to parse instance number '%s': %s",
688 		    numptr, errstr);
689 	}
690 
691 	/*
692 	 * Now that we have the instance here, we need to truncate the string at
693 	 * the end of the driver name otherwise di_drv_first_node() will be very
694 	 * confused as there is no driver called say 't4nex0'.
695 	 */
696 	arg[drvlen] = '\0';
697 	root = di_init("/", DINFOCPYALL);
698 	if (root == DI_NODE_NIL) {
699 		err(EXIT_FAILURE, "failed to initialize libdevinfo while "
700 		    "trying to map device name %s", arg);
701 	}
702 
703 	for (node = di_drv_first_node(arg, root); node != DI_NODE_NIL;
704 	    node = di_drv_next_node(node)) {
705 		char *bpath;
706 		di_minor_t minor = DI_MINOR_NIL;
707 
708 		if (di_instance(node) != inst) {
709 			continue;
710 		}
711 
712 		if (!is_t4nex) {
713 			const char *pdrv;
714 			node = di_parent_node(node);
715 			pdrv = di_driver_name(node);
716 			if (pdrv == NULL || strcmp(pdrv, T4_NEXUS_NAME) != 0) {
717 				errx(EXIT_FAILURE, "%s does not have %s "
718 				    "parent, found %s%d", arg, T4_NEXUS_NAME,
719 				    pdrv != NULL ? pdrv : "unknown",
720 				    pdrv != NULL ? di_instance(node) : -1);
721 			}
722 		}
723 
724 		(void) snprintf(mname, sizeof (mname), "%s,%d", T4_NEXUS_NAME,
725 		    di_instance(node));
726 
727 		while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
728 			if (strcmp(di_minor_name(minor), mname) == 0) {
729 				break;
730 			}
731 		}
732 
733 		if (minor == DI_MINOR_NIL) {
734 			errx(EXIT_FAILURE, "failed to find minor %s on %s%d",
735 			    mname, di_driver_name(node), di_instance(node));
736 		}
737 
738 		bpath = di_devfs_minor_path(minor);
739 		if (bpath == NULL) {
740 			err(EXIT_FAILURE, "failed to get minor path for "
741 			    "%s%d:%s", di_driver_name(node), di_instance(node),
742 			    di_minor_name(minor));
743 		}
744 		if (snprintf(cxgbetool_nexus, sizeof (cxgbetool_nexus),
745 		    "/devices%s", bpath) >= sizeof (cxgbetool_nexus)) {
746 			errx(EXIT_FAILURE, "failed to construct full /devices "
747 			    "path for %s: internal path buffer would have "
748 			    "overflowed", bpath);
749 		}
750 		di_devfs_path_free(bpath);
751 
752 		di_fini(root);
753 		return (cxgbetool_nexus);
754 	}
755 
756 	errx(EXIT_FAILURE, "failed to map %s%d to a %s or %s instance",
757 	    arg, inst, T4_PORT_NAME, T4_NEXUS_NAME);
758 }
759 
760 int
761 main(int argc, char *argv[])
762 {
763 	const char *iff_name;
764 
765 	progname = argv[0];
766 
767 	if (argc == 2) {
768 		if (strcmp(argv[1], "-h") == 0 ||
769 		    strcmp(argv[1], "--help") == 0) {
770 			usage(stdout);
771 		}
772 
773 		if (strcmp(argv[1], "-v") == 0 ||
774 		    strcmp(argv[1], "--version") == 0) {
775 			printf("cxgbetool version %s\n", DRV_VERSION);
776 			exit(0);
777 		}
778 	}
779 
780 	if (argc < 3)
781 		usage(stderr);
782 
783 	iff_name = cxgbetool_parse_path(argv[1]);
784 
785 	run_cmd(argc, argv, iff_name);
786 
787 	return (0);
788 }
789