xref: /illumos-gate/usr/src/cmd/dlutil/dltraninfo.c (revision 80b758da23abee3ef0e550f533aada9ce3b1b01f)
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) 2017, Joyent, Inc.
14  * Copyright 2024 Oxide Computer Company
15  */
16 
17 /*
18  * Private utility to dump transceiver information for each physical datalink.
19  * Something like this should eventually be a part of dladm or similar.
20  */
21 
22 #include <stdio.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/hexdump.h>
26 #include <fcntl.h>
27 #include <strings.h>
28 #include <errno.h>
29 #include <ctype.h>
30 #include <unistd.h>
31 #include <limits.h>
32 #include <stdarg.h>
33 #include <libgen.h>
34 
35 #include <libdladm.h>
36 #include <libdllink.h>
37 #include <sys/dld.h>
38 #include <sys/dld_ioc.h>
39 #include <sys/dls_mgmt.h>
40 #include <libsff.h>
41 
42 #define	DLTRAN_KIND_LEN	64
43 
44 static dladm_handle_t dltran_hdl;
45 static char dltran_dlerrmsg[DLADM_STRSIZE];
46 static const char *dltran_progname;
47 static boolean_t dltran_verbose;
48 static boolean_t dltran_hex;
49 static boolean_t dltran_write;
50 static int dltran_outfd;
51 static int dltran_errors;
52 
53 /*
54  * This routine basically assumes that we'll have 16 byte aligned output to
55  * print out the human readable output.
56  */
57 static int
dltran_dump_page_cb(void * arg,uint64_t addr,const char * buf,size_t len __unused)58 dltran_dump_page_cb(void *arg, uint64_t addr, const char *buf,
59     size_t len __unused)
60 {
61 	uint_t page = (uint_t)(uintptr_t)arg;
62 	int ret;
63 
64 	if (addr == UINT64_MAX) {
65 		/* Header row */
66 		ret = printf("page  %s\n", buf);
67 	} else {
68 		ret = printf("0x%02x  %s\n", page, buf);
69 	}
70 
71 	return (ret < 0 ? -1 : 0);
72 }
73 
74 static void
dltran_dump_page(uint8_t * buf,size_t nbytes,uint_t page)75 dltran_dump_page(uint8_t *buf, size_t nbytes, uint_t page)
76 {
77 	static boolean_t first = B_TRUE;
78 	hexdump_flag_t flags = HDF_DEFAULT;
79 
80 	if (first)
81 		first = B_FALSE;
82 	else
83 		flags &= ~HDF_HEADER;
84 
85 	(void) hexdump(buf, nbytes, flags, dltran_dump_page_cb,
86 	    (void *)(uintptr_t)page);
87 }
88 
89 static int
dltran_read_page(datalink_id_t link,uint_t tranid,uint_t page,uint8_t * bufp,size_t * buflen)90 dltran_read_page(datalink_id_t link, uint_t tranid, uint_t page, uint8_t *bufp,
91     size_t *buflen)
92 {
93 	dld_ioc_tranio_t dti;
94 
95 	bzero(bufp, *buflen);
96 	bzero(&dti, sizeof (dti));
97 
98 	dti.dti_linkid = link;
99 	dti.dti_tran_id = tranid;
100 	dti.dti_page = page;
101 	dti.dti_nbytes = *buflen;
102 	dti.dti_off = 0;
103 	dti.dti_buf = (uintptr_t)(void *)bufp;
104 
105 	if (ioctl(dladm_dld_fd(dltran_hdl), DLDIOC_READTRAN, &dti) != 0) {
106 		(void) fprintf(stderr, "failed to read transceiver page "
107 		    "0x%2x: %s\n", page, strerror(errno));
108 		return (1);
109 	}
110 
111 	*buflen = dti.dti_nbytes;
112 	return (0);
113 }
114 
115 static boolean_t
dltran_is_8472(uint8_t * buf)116 dltran_is_8472(uint8_t *buf)
117 {
118 	switch (buf[0]) {
119 	case 0xc:
120 	case 0xd:
121 	case 0x11:
122 		/*
123 		 * Catch cases that refer explicitly to QSFP and newer.
124 		 */
125 		return (B_FALSE);
126 	default:
127 		break;
128 	}
129 
130 	/*
131 	 * Check the byte that indicates compliance with SFF 8472. Use this to
132 	 * know if we can read page 0xa2 or not.
133 	 */
134 	if (buf[94] == 0)
135 		return (B_FALSE);
136 
137 	return (B_TRUE);
138 }
139 
140 static void
dltran_hex_dump(datalink_id_t linkid,uint_t tranid)141 dltran_hex_dump(datalink_id_t linkid, uint_t tranid)
142 {
143 	uint8_t buf[256];
144 	size_t buflen = sizeof (buf);
145 
146 	if (dltran_read_page(linkid, tranid, 0xa0, buf, &buflen) != 0) {
147 		dltran_errors++;
148 		return;
149 	}
150 
151 	dltran_dump_page(buf, buflen, 0xa0);
152 
153 	if (!dltran_is_8472(buf)) {
154 		return;
155 	}
156 
157 	buflen = sizeof (buf);
158 	if (dltran_read_page(linkid, tranid, 0xa2, buf, &buflen) != 0) {
159 		dltran_errors++;
160 		return;
161 	}
162 
163 	dltran_dump_page(buf, buflen, 0xa2);
164 }
165 
166 static void
dltran_write_page(datalink_id_t linkid,uint_t tranid)167 dltran_write_page(datalink_id_t linkid, uint_t tranid)
168 {
169 	uint8_t buf[256];
170 	size_t buflen = sizeof (buf);
171 	off_t off;
172 
173 	if (dltran_read_page(linkid, tranid, 0xa0, buf, &buflen) != 0) {
174 		dltran_errors++;
175 		return;
176 	}
177 
178 	off = 0;
179 	while (buflen > 0) {
180 		ssize_t ret;
181 
182 		ret = write(dltran_outfd, buf + off, buflen);
183 		if (ret == -1) {
184 			(void) fprintf(stderr, "failed to write data "
185 			    "to output file: %s\n", strerror(errno));
186 			dltran_errors++;
187 			return;
188 		}
189 
190 		off += ret;
191 		buflen -= ret;
192 	}
193 }
194 
195 static void
dltran_verbose_dump(datalink_id_t linkid,uint_t tranid)196 dltran_verbose_dump(datalink_id_t linkid, uint_t tranid)
197 {
198 	uint8_t buf[256];
199 	size_t buflen = sizeof (buf);
200 	int ret;
201 	nvlist_t *nvl;
202 
203 	if (dltran_read_page(linkid, tranid, 0xa0, buf, &buflen) != 0) {
204 		dltran_errors++;
205 		return;
206 	}
207 
208 	ret = libsff_parse(buf, buflen, 0xa0, &nvl);
209 	if (ret == 0) {
210 		dump_nvlist(nvl, 8);
211 		nvlist_free(nvl);
212 	} else {
213 		fprintf(stderr, "failed to parse sfp data: %s\n",
214 		    strerror(ret));
215 		dltran_errors++;
216 	}
217 }
218 
219 static int
dltran_dump_transceivers(dladm_handle_t hdl,datalink_id_t linkid,void * arg)220 dltran_dump_transceivers(dladm_handle_t hdl, datalink_id_t linkid, void *arg)
221 {
222 	dladm_status_t status;
223 	char name[MAXLINKNAMELEN];
224 	dld_ioc_gettran_t gt;
225 	uint_t count, i, tranid = UINT_MAX;
226 	boolean_t tran_found = B_FALSE;
227 	uint_t *tranidp = arg;
228 
229 	if (tranidp != NULL)
230 		tranid = *tranidp;
231 
232 	if ((status = dladm_datalink_id2info(hdl, linkid, NULL, NULL, NULL,
233 	    name, sizeof (name))) != DLADM_STATUS_OK) {
234 		(void) fprintf(stderr, "failed to get datalink name for link "
235 		    "%d: %s", linkid, dladm_status2str(status,
236 		    dltran_dlerrmsg));
237 		dltran_errors++;
238 		return (DLADM_WALK_CONTINUE);
239 	}
240 
241 	bzero(&gt, sizeof (gt));
242 	gt.dgt_linkid = linkid;
243 	gt.dgt_tran_id = DLDIOC_GETTRAN_GETNTRAN;
244 
245 	if (ioctl(dladm_dld_fd(hdl), DLDIOC_GETTRAN, &gt) != 0) {
246 		if (errno != ENOTSUP) {
247 			(void) fprintf(stderr, "failed to get transceiver "
248 			    "count for device %s: %s\n",
249 			    name, strerror(errno));
250 			dltran_errors++;
251 		}
252 		return (DLADM_WALK_CONTINUE);
253 	}
254 
255 	count = gt.dgt_tran_id;
256 	(void) printf("%s: discovered %d transceiver%s\n", name, count,
257 	    count > 1 ? "s" : "");
258 	for (i = 0; i < count; i++) {
259 		if (tranid != UINT_MAX && i != tranid)
260 			continue;
261 		if (tranid != UINT_MAX)
262 			tran_found = B_TRUE;
263 		bzero(&gt, sizeof (gt));
264 		gt.dgt_linkid = linkid;
265 		gt.dgt_tran_id = i;
266 
267 		if (ioctl(dladm_dld_fd(hdl), DLDIOC_GETTRAN, &gt) != 0) {
268 			(void) fprintf(stderr, "failed to get tran info for "
269 			    "%s: %s\n", name, strerror(errno));
270 			dltran_errors++;
271 			return (DLADM_WALK_CONTINUE);
272 		}
273 
274 		if (dltran_hex && !gt.dgt_present)
275 			continue;
276 		if (!dltran_hex && !dltran_write) {
277 			(void) printf("\ttransceiver %d present: %s\n", i,
278 			    gt.dgt_present ? "yes" : "no");
279 			if (!gt.dgt_present)
280 				continue;
281 			(void) printf("\ttransceiver %d usable: %s\n", i,
282 			    gt.dgt_usable ? "yes" : "no");
283 		}
284 
285 		if (dltran_verbose) {
286 			dltran_verbose_dump(linkid, i);
287 		}
288 
289 		if (dltran_write) {
290 			if (!gt.dgt_present) {
291 				(void) fprintf(stderr, "warning: no "
292 				    "transceiver present in port %d, not "
293 				    "writing\n", i);
294 				dltran_errors++;
295 				continue;
296 			}
297 			dltran_write_page(linkid, i);
298 		}
299 
300 		if (dltran_hex) {
301 			printf("transceiver %d data:\n", i);
302 			dltran_hex_dump(linkid, i);
303 		}
304 	}
305 
306 	if (tranid != UINT_MAX && !tran_found) {
307 		dltran_errors++;
308 		(void) fprintf(stderr, "failed to find transceiver %d on "
309 		    "link %s\n", tranid, name);
310 	}
311 
312 	return (DLADM_WALK_CONTINUE);
313 }
314 
315 
316 static void
dltran_usage(const char * fmt,...)317 dltran_usage(const char *fmt, ...)
318 {
319 	if (fmt != NULL) {
320 		va_list ap;
321 
322 		(void) fprintf(stderr, "%s: ", dltran_progname);
323 		va_start(ap, fmt);
324 		(void) vfprintf(stderr, fmt, ap);
325 		va_end(ap);
326 	}
327 
328 	(void) fprintf(stderr, "Usage: %s [-x | -v | -w file] [tran]...\n"
329 	    "\n"
330 	    "\t-v	display all transceiver information\n"
331 	    "\t-w	write transceiver data page 0xa0 to file\n"
332 	    "\t-x	dump raw hexadecimal for transceiver\n",
333 	    dltran_progname);
334 }
335 
336 int
main(int argc,char * argv[])337 main(int argc, char *argv[])
338 {
339 	int c;
340 	dladm_status_t status;
341 	const char *outfile = NULL;
342 	uint_t count = 0;
343 
344 	dltran_progname = basename(argv[0]);
345 
346 	while ((c = getopt(argc, argv, ":xvw:")) != -1) {
347 		switch (c) {
348 		case 'v':
349 			dltran_verbose = B_TRUE;
350 			break;
351 		case 'x':
352 			dltran_hex = B_TRUE;
353 			break;
354 		case 'w':
355 			dltran_write = B_TRUE;
356 			outfile = optarg;
357 			break;
358 		case ':':
359 			dltran_usage("option -%c requires an "
360 			    "operand\n", optopt);
361 			return (2);
362 		case '?':
363 		default:
364 			dltran_usage("unknown option: -%c\n", optopt);
365 			return (2);
366 		}
367 	}
368 
369 	argc -= optind;
370 	argv += optind;
371 
372 	if (dltran_verbose)
373 		count++;
374 	if (dltran_hex)
375 		count++;
376 	if (dltran_write)
377 		count++;
378 	if (count > 1) {
379 		(void) fprintf(stderr, "only one of -v, -w, and -x may be "
380 		    "specified\n");
381 		return (2);
382 	}
383 
384 	if (dltran_write) {
385 		if ((dltran_outfd = open(outfile, O_RDWR | O_TRUNC | O_CREAT,
386 		    0644)) < 0) {
387 			(void) fprintf(stderr, "failed to open output file "
388 			    "%s: %s\n", outfile, strerror(errno));
389 			return (1);
390 		}
391 	}
392 
393 	if ((status = dladm_open(&dltran_hdl)) != DLADM_STATUS_OK) {
394 		(void) fprintf(stderr, "failed to open /dev/dld: %s\n",
395 		    dladm_status2str(status, dltran_dlerrmsg));
396 		return (1);
397 	}
398 
399 	if (argc == 0) {
400 		(void) dladm_walk_datalink_id(dltran_dump_transceivers,
401 		    dltran_hdl, NULL, DATALINK_CLASS_PHYS,
402 		    DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
403 	} else {
404 		int i;
405 		char *c;
406 
407 		for (i = 0; i < argc; i++) {
408 			uint_t tran;
409 			uint_t *tranidp = NULL;
410 			datalink_id_t linkid;
411 
412 			if ((c = strrchr(argv[i], '/')) != NULL) {
413 				unsigned long u;
414 				char *eptr;
415 
416 				c++;
417 				errno = 0;
418 				u = strtoul(c, &eptr, 10);
419 				if (errno != 0 || *eptr != '\0' ||
420 				    u >= UINT_MAX) {
421 					(void) fprintf(stderr, "failed to "
422 					    "parse link/transceiver: %s\n",
423 					    argv[i]);
424 					return (1);
425 				}
426 				c--;
427 				*c = '\0';
428 				tran = (uint_t)u;
429 				tranidp = &tran;
430 			}
431 
432 			if ((status = dladm_name2info(dltran_hdl, argv[i],
433 			    &linkid, NULL, NULL, NULL)) != DLADM_STATUS_OK) {
434 				(void) fprintf(stderr, "failed to get link "
435 				    "id for link %s: %s\n", argv[i],
436 				    dladm_status2str(status, dltran_dlerrmsg));
437 				return (1);
438 			}
439 
440 			(void) dltran_dump_transceivers(dltran_hdl, linkid,
441 			    tranidp);
442 		}
443 	}
444 
445 	return (dltran_errors != 0 ? 1 : 0);
446 }
447