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 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 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 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 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 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 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 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 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(>, 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, >) != 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(>, sizeof (gt)); 264 gt.dgt_linkid = linkid; 265 gt.dgt_tran_id = i; 266 267 if (ioctl(dladm_dld_fd(hdl), DLDIOC_GETTRAN, >) != 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 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 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