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 2022 Oxide Computer Company 14 */ 15 16 #include <err.h> 17 #include <stdio.h> 18 #include <unistd.h> 19 #include <ofmt.h> 20 #include <strings.h> 21 #include <sys/pci.h> 22 23 #include "pcieadm.h" 24 25 typedef struct pcieadm_show_devs { 26 pcieadm_t *psd_pia; 27 ofmt_handle_t psd_ofmt; 28 boolean_t psd_funcs; 29 int psd_nfilts; 30 char **psd_filts; 31 boolean_t *psd_used; 32 uint_t psd_nprint; 33 } pcieadm_show_devs_t; 34 35 typedef enum pcieadm_show_devs_otype { 36 PCIEADM_SDO_VID, 37 PCIEADM_SDO_DID, 38 PCIEADM_SDO_BDF, 39 PCIEADM_SDO_BDF_BUS, 40 PCIEADM_SDO_BDF_DEV, 41 PCIEADM_SDO_BDF_FUNC, 42 PCIEADM_SDO_DRIVER, 43 PCIEADM_SDO_TYPE, 44 PCIEADM_SDO_VENDOR, 45 PCIEADM_SDO_DEVICE, 46 PCIEADM_SDO_PATH, 47 PCIEADM_SDO_MAXSPEED, 48 PCIEADM_SDO_MAXWIDTH, 49 PCIEADM_SDO_CURSPEED, 50 PCIEADM_SDO_CURWIDTH, 51 PCIEADM_SDO_SUPSPEEDS 52 } pcieadm_show_devs_otype_t; 53 54 typedef struct pcieadm_show_devs_ofmt { 55 int psdo_vid; 56 int psdo_did; 57 uint_t psdo_bus; 58 uint_t psdo_dev; 59 uint_t psdo_func; 60 const char *psdo_path; 61 const char *psdo_vendor; 62 const char *psdo_device; 63 const char *psdo_driver; 64 int psdo_instance; 65 int psdo_mwidth; 66 int psdo_cwidth; 67 int64_t psdo_mspeed; 68 int64_t psdo_cspeed; 69 int psdo_nspeeds; 70 int64_t *psdo_sspeeds; 71 } pcieadm_show_devs_ofmt_t; 72 73 static uint_t 74 pcieadm_speed2gen(int64_t speed) 75 { 76 if (speed == 2500000000LL) { 77 return (1); 78 } else if (speed == 5000000000LL) { 79 return (2); 80 } else if (speed == 8000000000LL) { 81 return (3); 82 } else if (speed == 16000000000LL) { 83 return (4); 84 } else if (speed == 32000000000LL) { 85 return (5); 86 } else { 87 return (0); 88 } 89 } 90 91 static const char * 92 pcieadm_speed2str(int64_t speed) 93 { 94 if (speed == 2500000000LL) { 95 return ("2.5"); 96 } else if (speed == 5000000000LL) { 97 return ("5.0"); 98 } else if (speed == 8000000000LL) { 99 return ("8.0"); 100 } else if (speed == 16000000000LL) { 101 return ("16.0"); 102 } else if (speed == 32000000000LL) { 103 return ("32.0"); 104 } else { 105 return (NULL); 106 } 107 } 108 109 static boolean_t 110 pcieadm_show_devs_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen) 111 { 112 const char *str; 113 pcieadm_show_devs_ofmt_t *psdo = ofarg->ofmt_cbarg; 114 boolean_t first = B_TRUE; 115 116 switch (ofarg->ofmt_id) { 117 case PCIEADM_SDO_BDF: 118 if (snprintf(buf, buflen, "%x/%x/%x", psdo->psdo_bus, 119 psdo->psdo_dev, psdo->psdo_func) >= buflen) { 120 return (B_FALSE); 121 } 122 break; 123 case PCIEADM_SDO_BDF_BUS: 124 if (snprintf(buf, buflen, "%x", psdo->psdo_bus) >= buflen) { 125 return (B_FALSE); 126 } 127 break; 128 case PCIEADM_SDO_BDF_DEV: 129 if (snprintf(buf, buflen, "%x", psdo->psdo_dev) >= buflen) { 130 return (B_FALSE); 131 } 132 break; 133 case PCIEADM_SDO_BDF_FUNC: 134 if (snprintf(buf, buflen, "%x", psdo->psdo_func) >= buflen) { 135 return (B_FALSE); 136 } 137 break; 138 case PCIEADM_SDO_DRIVER: 139 if (psdo->psdo_driver == NULL || psdo->psdo_instance == -1) { 140 (void) snprintf(buf, buflen, "--"); 141 } else if (snprintf(buf, buflen, "%s%d", psdo->psdo_driver, 142 psdo->psdo_instance) >= buflen) { 143 return (B_FALSE); 144 } 145 break; 146 case PCIEADM_SDO_PATH: 147 if (strlcat(buf, psdo->psdo_path, buflen) >= buflen) { 148 return (B_TRUE); 149 } 150 break; 151 case PCIEADM_SDO_VID: 152 if (psdo->psdo_vid == -1) { 153 (void) strlcat(buf, "--", buflen); 154 } else if (snprintf(buf, buflen, "%x", psdo->psdo_vid) >= 155 buflen) { 156 return (B_FALSE); 157 } 158 break; 159 case PCIEADM_SDO_DID: 160 if (psdo->psdo_did == -1) { 161 (void) strlcat(buf, "--", buflen); 162 } else if (snprintf(buf, buflen, "%x", psdo->psdo_did) >= 163 buflen) { 164 return (B_FALSE); 165 } 166 break; 167 case PCIEADM_SDO_VENDOR: 168 if (strlcat(buf, psdo->psdo_vendor, buflen) >= buflen) { 169 return (B_FALSE); 170 } 171 break; 172 case PCIEADM_SDO_DEVICE: 173 if (strlcat(buf, psdo->psdo_device, buflen) >= buflen) { 174 return (B_FALSE); 175 } 176 break; 177 case PCIEADM_SDO_MAXWIDTH: 178 if (psdo->psdo_mwidth <= 0) { 179 (void) strlcat(buf, "--", buflen); 180 } else if (snprintf(buf, buflen, "x%u", psdo->psdo_mwidth) >= 181 buflen) { 182 return (B_FALSE); 183 } 184 break; 185 case PCIEADM_SDO_CURWIDTH: 186 if (psdo->psdo_cwidth <= 0) { 187 (void) strlcat(buf, "--", buflen); 188 } else if (snprintf(buf, buflen, "x%u", psdo->psdo_cwidth) >= 189 buflen) { 190 return (B_FALSE); 191 } 192 break; 193 case PCIEADM_SDO_MAXSPEED: 194 str = pcieadm_speed2str(psdo->psdo_mspeed); 195 if (str == NULL) { 196 (void) strlcat(buf, "--", buflen); 197 } else if (snprintf(buf, buflen, "%s GT/s", str) >= buflen) { 198 return (B_FALSE); 199 } 200 break; 201 case PCIEADM_SDO_CURSPEED: 202 str = pcieadm_speed2str(psdo->psdo_cspeed); 203 if (str == NULL) { 204 (void) strlcat(buf, "--", buflen); 205 } else if (snprintf(buf, buflen, "%s GT/s", str) >= buflen) { 206 return (B_FALSE); 207 } 208 break; 209 case PCIEADM_SDO_SUPSPEEDS: 210 buf[0] = 0; 211 for (int i = 0; i < psdo->psdo_nspeeds; i++) { 212 const char *str; 213 214 str = pcieadm_speed2str(psdo->psdo_sspeeds[i]); 215 if (str == NULL) { 216 continue; 217 } 218 219 if (!first) { 220 if (strlcat(buf, ",", buflen) >= buflen) { 221 return (B_FALSE); 222 } 223 } 224 first = B_FALSE; 225 226 if (strlcat(buf, str, buflen) >= buflen) { 227 return (B_FALSE); 228 } 229 } 230 break; 231 case PCIEADM_SDO_TYPE: 232 if (pcieadm_speed2gen(psdo->psdo_cspeed) == 0 || 233 psdo->psdo_mwidth == -1) { 234 if (strlcat(buf, "PCI", buflen) >= buflen) { 235 return (B_FALSE); 236 } 237 } else { 238 if (snprintf(buf, buflen, "PCIe Gen %ux%u", 239 pcieadm_speed2gen(psdo->psdo_cspeed), 240 psdo->psdo_cwidth) >= buflen) { 241 return (B_FALSE); 242 } 243 } 244 break; 245 default: 246 abort(); 247 } 248 return (B_TRUE); 249 } 250 251 static const char *pcieadm_show_dev_fields = "bdf,type,driver,device"; 252 static const char *pcieadm_show_dev_speeds = 253 "bdf,driver,maxspeed,curspeed,maxwidth,curwidth,supspeeds"; 254 static const ofmt_field_t pcieadm_show_dev_ofmt[] = { 255 { "VID", 6, PCIEADM_SDO_VID, pcieadm_show_devs_ofmt_cb }, 256 { "DID", 6, PCIEADM_SDO_DID, pcieadm_show_devs_ofmt_cb }, 257 { "BDF", 8, PCIEADM_SDO_BDF, pcieadm_show_devs_ofmt_cb }, 258 { "DRIVER", 15, PCIEADM_SDO_DRIVER, pcieadm_show_devs_ofmt_cb }, 259 { "TYPE", 15, PCIEADM_SDO_TYPE, pcieadm_show_devs_ofmt_cb }, 260 { "VENDOR", 30, PCIEADM_SDO_VENDOR, pcieadm_show_devs_ofmt_cb }, 261 { "DEVICE", 30, PCIEADM_SDO_DEVICE, pcieadm_show_devs_ofmt_cb }, 262 { "PATH", 30, PCIEADM_SDO_PATH, pcieadm_show_devs_ofmt_cb }, 263 { "BUS", 4, PCIEADM_SDO_BDF_BUS, pcieadm_show_devs_ofmt_cb }, 264 { "DEV", 4, PCIEADM_SDO_BDF_DEV, pcieadm_show_devs_ofmt_cb }, 265 { "FUNC", 4, PCIEADM_SDO_BDF_FUNC, pcieadm_show_devs_ofmt_cb }, 266 { "MAXSPEED", 10, PCIEADM_SDO_MAXSPEED, pcieadm_show_devs_ofmt_cb }, 267 { "MAXWIDTH", 10, PCIEADM_SDO_MAXWIDTH, pcieadm_show_devs_ofmt_cb }, 268 { "CURSPEED", 10, PCIEADM_SDO_CURSPEED, pcieadm_show_devs_ofmt_cb }, 269 { "CURWIDTH", 10, PCIEADM_SDO_CURWIDTH, pcieadm_show_devs_ofmt_cb }, 270 { "SUPSPEEDS", 20, PCIEADM_SDO_SUPSPEEDS, pcieadm_show_devs_ofmt_cb }, 271 { NULL, 0, 0, NULL } 272 }; 273 274 static boolean_t 275 pcieadm_show_devs_match(pcieadm_show_devs_t *psd, 276 pcieadm_show_devs_ofmt_t *psdo) 277 { 278 char dinst[128], bdf[128]; 279 280 if (psd->psd_nfilts == 0) { 281 return (B_TRUE); 282 } 283 284 if (psdo->psdo_driver != NULL && psdo->psdo_instance != -1) { 285 (void) snprintf(dinst, sizeof (dinst), "%s%d", 286 psdo->psdo_driver, psdo->psdo_instance); 287 } 288 (void) snprintf(bdf, sizeof (bdf), "%x/%x/%x", psdo->psdo_bus, 289 psdo->psdo_dev, psdo->psdo_func); 290 291 for (uint_t i = 0; i < psd->psd_nfilts; i++) { 292 const char *filt = psd->psd_filts[i]; 293 294 if (strcmp(filt, psdo->psdo_path) == 0) { 295 psd->psd_used[i] = B_TRUE; 296 return (B_TRUE); 297 } 298 299 if (strcmp(filt, bdf) == 0) { 300 psd->psd_used[i] = B_TRUE; 301 return (B_TRUE); 302 } 303 304 if (psdo->psdo_driver != NULL && 305 strcmp(filt, psdo->psdo_driver) == 0) { 306 psd->psd_used[i] = B_TRUE; 307 return (B_TRUE); 308 } 309 310 if (psdo->psdo_driver != NULL && psdo->psdo_instance != -1 && 311 strcmp(filt, dinst) == 0) { 312 psd->psd_used[i] = B_TRUE; 313 return (B_TRUE); 314 } 315 316 if (strncmp("/devices", filt, strlen("/devices")) == 0) { 317 filt += strlen("/devices"); 318 } 319 320 if (strcmp(filt, psdo->psdo_path) == 0) { 321 psd->psd_used[i] = B_TRUE; 322 return (B_TRUE); 323 } 324 } 325 return (B_FALSE); 326 } 327 328 static int 329 pcieadm_show_devs_walk_cb(di_node_t node, void *arg) 330 { 331 int nprop, *regs = NULL, *did, *vid, *mwidth, *cwidth; 332 int64_t *mspeed, *cspeed, *sspeeds; 333 char *path = NULL; 334 pcieadm_show_devs_t *psd = arg; 335 int ret = DI_WALK_CONTINUE; 336 char venstr[64], devstr[64]; 337 pcieadm_show_devs_ofmt_t oarg; 338 pcidb_hdl_t *pcidb = psd->psd_pia->pia_pcidb; 339 340 bzero(&oarg, sizeof (oarg)); 341 342 path = di_devfs_path(node); 343 if (path == NULL) { 344 err(EXIT_FAILURE, "failed to construct devfs path for node: " 345 "%s (%s)", di_node_name(node)); 346 } 347 348 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg", ®s); 349 if (nprop <= 0) { 350 errx(EXIT_FAILURE, "failed to lookup regs array for %s", 351 path); 352 } 353 354 oarg.psdo_path = path; 355 oarg.psdo_bus = PCI_REG_BUS_G(regs[0]); 356 oarg.psdo_dev = PCI_REG_DEV_G(regs[0]); 357 oarg.psdo_func = PCI_REG_FUNC_G(regs[0]); 358 359 if (oarg.psdo_func != 0 && !psd->psd_funcs) { 360 goto done; 361 } 362 363 oarg.psdo_driver = di_driver_name(node); 364 oarg.psdo_instance = di_instance(node); 365 366 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &did); 367 if (nprop != 1) { 368 oarg.psdo_did = -1; 369 } else { 370 oarg.psdo_did = (uint16_t)*did; 371 } 372 373 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vid); 374 if (nprop != 1) { 375 oarg.psdo_vid = -1; 376 } else { 377 oarg.psdo_vid = (uint16_t)*vid; 378 } 379 380 oarg.psdo_vendor = "--"; 381 if (oarg.psdo_vid != -1) { 382 pcidb_vendor_t *vend = pcidb_lookup_vendor(pcidb, 383 oarg.psdo_vid); 384 if (vend != NULL) { 385 oarg.psdo_vendor = pcidb_vendor_name(vend); 386 } else { 387 (void) snprintf(venstr, sizeof (venstr), 388 "Unknown vendor: 0x%x", oarg.psdo_vid); 389 oarg.psdo_vendor = venstr; 390 } 391 } 392 393 oarg.psdo_device = "--"; 394 if (oarg.psdo_vid != -1 && oarg.psdo_did != -1) { 395 pcidb_device_t *dev = pcidb_lookup_device(pcidb, 396 oarg.psdo_vid, oarg.psdo_did); 397 if (dev != NULL) { 398 oarg.psdo_device = pcidb_device_name(dev); 399 } else { 400 (void) snprintf(devstr, sizeof (devstr), 401 "Unknown device: 0x%x", oarg.psdo_did); 402 oarg.psdo_device = devstr; 403 } 404 } 405 406 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, 407 "pcie-link-maximum-width", &mwidth); 408 if (nprop != 1) { 409 oarg.psdo_mwidth = -1; 410 } else { 411 oarg.psdo_mwidth = *mwidth; 412 } 413 414 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, 415 "pcie-link-current-width", &cwidth); 416 if (nprop != 1) { 417 oarg.psdo_cwidth = -1; 418 } else { 419 oarg.psdo_cwidth = *cwidth; 420 } 421 422 nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node, 423 "pcie-link-maximum-speed", &mspeed); 424 if (nprop != 1) { 425 oarg.psdo_mspeed = -1; 426 } else { 427 oarg.psdo_mspeed = *mspeed; 428 } 429 430 nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node, 431 "pcie-link-current-speed", &cspeed); 432 if (nprop != 1) { 433 oarg.psdo_cspeed = -1; 434 } else { 435 oarg.psdo_cspeed = *cspeed; 436 } 437 438 nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node, 439 "pcie-link-supported-speeds", &sspeeds); 440 if (nprop > 0) { 441 oarg.psdo_nspeeds = nprop; 442 oarg.psdo_sspeeds = sspeeds; 443 } else { 444 oarg.psdo_nspeeds = 0; 445 oarg.psdo_sspeeds = NULL; 446 } 447 448 if (pcieadm_show_devs_match(psd, &oarg)) { 449 ofmt_print(psd->psd_ofmt, &oarg); 450 psd->psd_nprint++; 451 } 452 453 done: 454 if (path != NULL) { 455 di_devfs_path_free(path); 456 } 457 458 return (ret); 459 } 460 461 void 462 pcieadm_show_devs_usage(FILE *f) 463 { 464 (void) fprintf(f, "\tshow-devs\t[-F] [-H] [-s | -o field[,...] [-p]] " 465 "[filter...]\n"); 466 } 467 468 static void 469 pcieadm_show_devs_help(const char *fmt, ...) 470 { 471 if (fmt != NULL) { 472 va_list ap; 473 474 va_start(ap, fmt); 475 vwarnx(fmt, ap); 476 va_end(ap); 477 (void) fprintf(stderr, "\n"); 478 } 479 480 (void) fprintf(stderr, "Usage: %s show-devs [-F] [-H] [-s | -o " 481 "field[,...] [-p]] [filter...]\n", pcieadm_progname); 482 483 (void) fprintf(stderr, "\nList PCI devices and functions in the " 484 "system. Each <filter> selects a set\nof devices to show and " 485 "can be a driver name, instance, /devices path, or\nb/d/f.\n\n" 486 "\t-F\t\tdo not display PCI functions\n" 487 "\t-H\t\tomit the column header\n" 488 "\t-o field\toutput fields to print\n" 489 "\t-p\t\tparsable output (requires -o)\n" 490 "\t-s\t\tlist speeds and widths\n"); 491 492 } 493 494 int 495 pcieadm_show_devs(pcieadm_t *pcip, int argc, char *argv[]) 496 { 497 int c, ret; 498 uint_t flags = 0; 499 const char *fields = NULL; 500 pcieadm_show_devs_t psd; 501 pcieadm_di_walk_t walk; 502 ofmt_status_t oferr; 503 boolean_t parse = B_FALSE; 504 boolean_t speeds = B_FALSE; 505 506 /* 507 * show-devs relies solely on the devinfo snapshot we already took. 508 * Formalize our privs immediately. 509 */ 510 pcieadm_init_privs(pcip); 511 512 bzero(&psd, sizeof (psd)); 513 psd.psd_pia = pcip; 514 psd.psd_funcs = B_TRUE; 515 516 while ((c = getopt(argc, argv, ":FHo:ps")) != -1) { 517 switch (c) { 518 case 'F': 519 psd.psd_funcs = B_FALSE; 520 break; 521 case 'p': 522 parse = B_TRUE; 523 flags |= OFMT_PARSABLE; 524 break; 525 case 'H': 526 flags |= OFMT_NOHEADER; 527 break; 528 case 's': 529 speeds = B_TRUE; 530 break; 531 case 'o': 532 fields = optarg; 533 break; 534 case ':': 535 pcieadm_show_devs_help("option -%c requires an " 536 "argument", optopt); 537 exit(EXIT_USAGE); 538 case '?': 539 pcieadm_show_devs_help("unknown option: -%c", optopt); 540 exit(EXIT_USAGE); 541 } 542 } 543 544 if (parse && fields == NULL) { 545 errx(EXIT_USAGE, "-p requires fields specified with -o"); 546 } 547 548 if (fields != NULL && speeds) { 549 errx(EXIT_USAGE, "-s cannot be used with with -o"); 550 } 551 552 if (fields == NULL) { 553 if (speeds) { 554 fields = pcieadm_show_dev_speeds; 555 } else { 556 fields = pcieadm_show_dev_fields; 557 } 558 } 559 560 argc -= optind; 561 argv += optind; 562 563 if (argc > 0) { 564 psd.psd_nfilts = argc; 565 psd.psd_filts = argv; 566 psd.psd_used = calloc(argc, sizeof (boolean_t)); 567 if (psd.psd_used == NULL) { 568 err(EXIT_FAILURE, "failed to allocate filter tracking " 569 "memory"); 570 } 571 } 572 573 oferr = ofmt_open(fields, pcieadm_show_dev_ofmt, flags, 0, 574 &psd.psd_ofmt); 575 ofmt_check(oferr, parse, psd.psd_ofmt, pcieadm_ofmt_errx, warnx); 576 577 walk.pdw_arg = &psd; 578 walk.pdw_func = pcieadm_show_devs_walk_cb; 579 580 pcieadm_di_walk(pcip, &walk); 581 582 ret = EXIT_SUCCESS; 583 for (int i = 0; i < psd.psd_nfilts; i++) { 584 if (!psd.psd_used[i]) { 585 warnx("filter '%s' did not match any devices", 586 psd.psd_filts[i]); 587 ret = EXIT_FAILURE; 588 } 589 } 590 591 if (psd.psd_nprint == 0) { 592 ret = EXIT_FAILURE; 593 } 594 595 return (ret); 596 } 597