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 pcieadm_show_devs_ofmt_t oarg; 331 pcidb_hdl_t *pcidb = psd->psd_pia->pia_pcidb; 332 333 bzero(&oarg, sizeof (oarg)); 334 335 path = di_devfs_path(node); 336 if (path == NULL) { 337 err(EXIT_FAILURE, "failed to construct devfs path for node: " 338 "%s (%s)", di_node_name(node)); 339 } 340 341 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg", ®s); 342 if (nprop <= 0) { 343 errx(EXIT_FAILURE, "failed to lookup regs array for %s", 344 path); 345 } 346 347 oarg.psdo_path = path; 348 oarg.psdo_bus = PCI_REG_BUS_G(regs[0]); 349 oarg.psdo_dev = PCI_REG_DEV_G(regs[0]); 350 oarg.psdo_func = PCI_REG_FUNC_G(regs[0]); 351 352 if (oarg.psdo_func != 0 && !psd->psd_funcs) { 353 goto done; 354 } 355 356 oarg.psdo_driver = di_driver_name(node); 357 oarg.psdo_instance = di_instance(node); 358 359 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &did); 360 if (nprop != 1) { 361 oarg.psdo_did = -1; 362 } else { 363 oarg.psdo_did = (uint16_t)*did; 364 } 365 366 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vid); 367 if (nprop != 1) { 368 oarg.psdo_vid = -1; 369 } else { 370 oarg.psdo_vid = (uint16_t)*vid; 371 } 372 373 oarg.psdo_vendor = "--"; 374 if (oarg.psdo_vid != -1) { 375 pcidb_vendor_t *vend = pcidb_lookup_vendor(pcidb, 376 oarg.psdo_vid); 377 if (vend != NULL) { 378 oarg.psdo_vendor = pcidb_vendor_name(vend); 379 } 380 } 381 382 oarg.psdo_device = "--"; 383 if (oarg.psdo_vid != -1 && oarg.psdo_did != -1) { 384 pcidb_device_t *dev = pcidb_lookup_device(pcidb, 385 oarg.psdo_vid, oarg.psdo_did); 386 if (dev != NULL) { 387 oarg.psdo_device = pcidb_device_name(dev); 388 389 } 390 } 391 392 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, 393 "pcie-link-maximum-width", &mwidth); 394 if (nprop != 1) { 395 oarg.psdo_mwidth = -1; 396 } else { 397 oarg.psdo_mwidth = *mwidth; 398 } 399 400 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, 401 "pcie-link-current-width", &cwidth); 402 if (nprop != 1) { 403 oarg.psdo_cwidth = -1; 404 } else { 405 oarg.psdo_cwidth = *cwidth; 406 } 407 408 nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node, 409 "pcie-link-maximum-speed", &mspeed); 410 if (nprop != 1) { 411 oarg.psdo_mspeed = -1; 412 } else { 413 oarg.psdo_mspeed = *mspeed; 414 } 415 416 nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node, 417 "pcie-link-current-speed", &cspeed); 418 if (nprop != 1) { 419 oarg.psdo_cspeed = -1; 420 } else { 421 oarg.psdo_cspeed = *cspeed; 422 } 423 424 nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node, 425 "pcie-link-supported-speeds", &sspeeds); 426 if (nprop > 0) { 427 oarg.psdo_nspeeds = nprop; 428 oarg.psdo_sspeeds = sspeeds; 429 } else { 430 oarg.psdo_nspeeds = 0; 431 oarg.psdo_sspeeds = NULL; 432 } 433 434 if (pcieadm_show_devs_match(psd, &oarg)) { 435 ofmt_print(psd->psd_ofmt, &oarg); 436 psd->psd_nprint++; 437 } 438 439 done: 440 if (path != NULL) { 441 di_devfs_path_free(path); 442 } 443 444 return (ret); 445 } 446 447 void 448 pcieadm_show_devs_usage(FILE *f) 449 { 450 (void) fprintf(f, "\tshow-devs\t[-F] [-H] [-s | -o field[,...] [-p]] " 451 "[filter...]\n"); 452 } 453 454 static void 455 pcieadm_show_devs_help(const char *fmt, ...) 456 { 457 if (fmt != NULL) { 458 va_list ap; 459 460 va_start(ap, fmt); 461 vwarnx(fmt, ap); 462 va_end(ap); 463 (void) fprintf(stderr, "\n"); 464 } 465 466 (void) fprintf(stderr, "Usage: %s show-devs [-F] [-H] [-s | -o " 467 "field[,...] [-p]] [filter...]\n", pcieadm_progname); 468 469 (void) fprintf(stderr, "\nList PCI devices and functions in the " 470 "system. Each <filter> selects a set\nof devices to show and " 471 "can be a driver name, instance, /devices path, or\nb/d/f.\n\n" 472 "\t-F\t\tdo not display PCI functions\n" 473 "\t-H\t\tomit the column header\n" 474 "\t-o field\toutput fields to print\n" 475 "\t-p\t\tparsable output (requires -o)\n" 476 "\t-s\t\tlist speeds and widths\n"); 477 478 } 479 480 int 481 pcieadm_show_devs(pcieadm_t *pcip, int argc, char *argv[]) 482 { 483 int c; 484 uint_t flags = 0; 485 const char *fields = NULL; 486 pcieadm_show_devs_t psd; 487 pcieadm_di_walk_t walk; 488 ofmt_status_t oferr; 489 boolean_t parse = B_FALSE; 490 boolean_t speeds = B_FALSE; 491 492 /* 493 * show-devs relies solely on the devinfo snapshot we already took. 494 * Formalize our privs immediately. 495 */ 496 pcieadm_init_privs(pcip); 497 498 bzero(&psd, sizeof (psd)); 499 psd.psd_pia = pcip; 500 psd.psd_funcs = B_TRUE; 501 502 while ((c = getopt(argc, argv, ":FHo:ps")) != -1) { 503 switch (c) { 504 case 'F': 505 psd.psd_funcs = B_FALSE; 506 break; 507 case 'p': 508 parse = B_TRUE; 509 flags |= OFMT_PARSABLE; 510 break; 511 case 'H': 512 flags |= OFMT_NOHEADER; 513 break; 514 case 's': 515 speeds = B_TRUE; 516 break; 517 case 'o': 518 fields = optarg; 519 break; 520 case ':': 521 pcieadm_show_devs_help("option -%c requires an " 522 "argument", optopt); 523 exit(EXIT_USAGE); 524 case '?': 525 pcieadm_show_devs_help("unknown option: -%c", optopt); 526 exit(EXIT_USAGE); 527 } 528 } 529 530 if (parse && fields == NULL) { 531 errx(EXIT_USAGE, "-p requires fields specified with -o"); 532 } 533 534 if (fields != NULL && speeds) { 535 errx(EXIT_USAGE, "-s cannot be used with with -o"); 536 } 537 538 if (fields == NULL) { 539 if (speeds) { 540 fields = pcieadm_show_dev_speeds; 541 } else { 542 fields = pcieadm_show_dev_fields; 543 } 544 } 545 546 argc -= optind; 547 argv += optind; 548 549 if (argc > 0) { 550 psd.psd_nfilts = argc; 551 psd.psd_filts = argv; 552 } 553 554 oferr = ofmt_open(fields, pcieadm_show_dev_ofmt, flags, 0, 555 &psd.psd_ofmt); 556 ofmt_check(oferr, parse, psd.psd_ofmt, pcieadm_ofmt_errx, warnx); 557 558 walk.pdw_arg = &psd; 559 walk.pdw_func = pcieadm_show_devs_walk_cb; 560 561 pcieadm_di_walk(pcip, &walk); 562 563 if (psd.psd_nprint > 0) { 564 return (EXIT_SUCCESS); 565 } else { 566 return (EXIT_FAILURE); 567 } 568 } 569