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