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 2024 Oxide Computer Company 14 */ 15 16 /* 17 * A tool to interface with the pci.ids database driven by libpcidb. 18 */ 19 20 #include <stdio.h> 21 #include <stdarg.h> 22 #include <pcidb.h> 23 #include <err.h> 24 #include <libgen.h> 25 #include <string.h> 26 #include <strings.h> 27 #include <stdlib.h> 28 #include <ofmt.h> 29 #include <errno.h> 30 #include <sys/debug.h> 31 #include <priv.h> 32 33 #define EXIT_USAGE 2 34 35 static char *pcidb_progname; 36 37 typedef enum { 38 PCIDB_MODE_UNKNOWN, 39 PCIDB_MODE_LIST, 40 PCIDB_MODE_SEARCH, 41 PCIDB_MODE_LOOKUP 42 } pcidb_mode_t; 43 44 typedef enum { 45 PCIDB_TABLE_NONE, 46 PCIDB_TABLE_VENDOR, 47 PCIDB_TABLE_DEVICE, 48 PCIDB_TABLE_SUBSYSTEM, 49 PCIDB_TABLE_CLASS, 50 PCIDB_TABLE_SUBCLASS, 51 PCIDB_TABLE_PROGIF 52 } pcidb_table_t; 53 54 typedef enum { 55 PCIDB_TGRP_NONE, 56 PCIDB_TGRP_DEV, 57 PCIDB_TGRP_CLASS 58 } pcidb_tgrp_t; 59 60 typedef enum { 61 PCIDB_OFMT_VID, 62 PCIDB_OFMT_VENSTR, 63 PCIDB_OFMT_DID, 64 PCIDB_OFMT_DEVSTR, 65 PCIDB_OFMT_SVID, 66 PCIDB_OFMT_SDID, 67 PCIDB_OFMT_SUBVENSTR, 68 PCIDB_OFMT_SUBSYSSTR, 69 PCIDB_OFMT_BCC, 70 PCIDB_OFMT_CLASSSTR, 71 PCIDB_OFMT_SCC, 72 PCIDB_OFMT_SUBCLASSSTR, 73 PCIDB_OFMT_PI, 74 PCIDB_OFMT_PROGIFSTR 75 } pcidb_ofmt_t; 76 77 typedef struct pcidb_filter { 78 const char *pft_raw; 79 boolean_t pft_used; 80 pcidb_table_t pft_table; 81 pcidb_tgrp_t pft_tgrp; 82 uint32_t pft_vend; 83 uint32_t pft_dev; 84 uint32_t pft_subven; 85 uint32_t pft_subdev; 86 uint32_t pft_class; 87 uint32_t pft_subclass; 88 uint32_t pft_progif; 89 } pcidb_filter_t; 90 91 #define PCIDB_NOFILTER UINT32_MAX 92 93 typedef struct pcidb_walk { 94 pcidb_hdl_t *pw_hdl; 95 ofmt_handle_t pw_ofmt; 96 pcidb_vendor_t *pw_vendor; 97 pcidb_device_t *pw_device; 98 pcidb_subvd_t *pw_subvd; 99 pcidb_class_t *pw_class; 100 pcidb_subclass_t *pw_subclass; 101 pcidb_progif_t *pw_progif; 102 boolean_t pw_strcase; 103 uint_t pw_nfilters; 104 pcidb_filter_t *pw_filters; 105 } pcidb_walk_t; 106 107 static boolean_t 108 pcidb_write_vendor(ofmt_arg_t *ofarg, char *buf, uint_t buflen) 109 { 110 pcidb_walk_t *walk = ofarg->ofmt_cbarg; 111 112 VERIFY(walk->pw_vendor != NULL); 113 switch (ofarg->ofmt_id) { 114 case PCIDB_OFMT_VID: 115 (void) snprintf(buf, buflen, "%x", 116 pcidb_vendor_id(walk->pw_vendor)); 117 break; 118 case PCIDB_OFMT_VENSTR: 119 (void) strlcpy(buf, pcidb_vendor_name(walk->pw_vendor), buflen); 120 break; 121 default: 122 abort(); 123 } 124 return (B_TRUE); 125 } 126 127 static boolean_t 128 pcidb_write_device(ofmt_arg_t *ofarg, char *buf, uint_t buflen) 129 { 130 pcidb_walk_t *walk = ofarg->ofmt_cbarg; 131 132 VERIFY(walk->pw_device != NULL); 133 switch (ofarg->ofmt_id) { 134 case PCIDB_OFMT_DID: 135 (void) snprintf(buf, buflen, "%x", 136 pcidb_device_id(walk->pw_device)); 137 break; 138 case PCIDB_OFMT_DEVSTR: 139 (void) strlcpy(buf, pcidb_device_name(walk->pw_device), buflen); 140 break; 141 default: 142 abort(); 143 } 144 return (B_TRUE); 145 } 146 147 static boolean_t 148 pcidb_write_subsystem(ofmt_arg_t *ofarg, char *buf, uint_t buflen) 149 { 150 pcidb_walk_t *walk = ofarg->ofmt_cbarg; 151 pcidb_vendor_t *vendor; 152 153 VERIFY(walk->pw_subvd != NULL); 154 switch (ofarg->ofmt_id) { 155 case PCIDB_OFMT_SVID: 156 (void) snprintf(buf, buflen, "%x", 157 pcidb_subvd_svid(walk->pw_subvd)); 158 break; 159 case PCIDB_OFMT_SDID: 160 (void) snprintf(buf, buflen, "%x", 161 pcidb_subvd_sdid(walk->pw_subvd)); 162 break; 163 case PCIDB_OFMT_SUBSYSSTR: 164 (void) strlcpy(buf, pcidb_subvd_name(walk->pw_subvd), buflen); 165 break; 166 case PCIDB_OFMT_SUBVENSTR: 167 vendor = pcidb_lookup_vendor(walk->pw_hdl, 168 pcidb_subvd_svid(walk->pw_subvd)); 169 if (vendor == NULL) { 170 return (B_FALSE); 171 } 172 (void) strlcpy(buf, pcidb_vendor_name(vendor), buflen); 173 break; 174 default: 175 abort(); 176 } 177 return (B_TRUE); 178 } 179 180 static boolean_t 181 pcidb_write_class(ofmt_arg_t *ofarg, char *buf, uint_t buflen) 182 { 183 pcidb_walk_t *walk = ofarg->ofmt_cbarg; 184 185 VERIFY(walk->pw_class != NULL); 186 switch (ofarg->ofmt_id) { 187 case PCIDB_OFMT_BCC: 188 (void) snprintf(buf, buflen, "%x", 189 pcidb_class_code(walk->pw_class)); 190 break; 191 case PCIDB_OFMT_CLASSSTR: 192 (void) strlcpy(buf, pcidb_class_name(walk->pw_class), buflen); 193 break; 194 default: 195 abort(); 196 } 197 return (B_TRUE); 198 } 199 200 static boolean_t 201 pcidb_write_subclass(ofmt_arg_t *ofarg, char *buf, uint_t buflen) 202 { 203 pcidb_walk_t *walk = ofarg->ofmt_cbarg; 204 205 VERIFY(walk->pw_subclass != NULL); 206 switch (ofarg->ofmt_id) { 207 case PCIDB_OFMT_SCC: 208 (void) snprintf(buf, buflen, "%x", 209 pcidb_subclass_code(walk->pw_subclass)); 210 break; 211 case PCIDB_OFMT_SUBCLASSSTR: 212 (void) strlcpy(buf, pcidb_subclass_name(walk->pw_subclass), 213 buflen); 214 break; 215 default: 216 abort(); 217 } 218 return (B_TRUE); 219 } 220 221 static boolean_t 222 pcidb_write_progif(ofmt_arg_t *ofarg, char *buf, uint_t buflen) 223 { 224 pcidb_walk_t *walk = ofarg->ofmt_cbarg; 225 226 VERIFY(walk->pw_progif != NULL); 227 switch (ofarg->ofmt_id) { 228 case PCIDB_OFMT_PI: 229 (void) snprintf(buf, buflen, "%x", 230 pcidb_progif_code(walk->pw_progif)); 231 break; 232 case PCIDB_OFMT_PROGIFSTR: 233 (void) strlcpy(buf, pcidb_progif_name(walk->pw_progif), 234 buflen); 235 break; 236 default: 237 abort(); 238 } 239 return (B_TRUE); 240 } 241 242 static const char *pcidb_vendor_fields = "vid,vendor"; 243 static const ofmt_field_t pcidb_vendor_ofmt[] = { 244 { "VID", 8, PCIDB_OFMT_VID, pcidb_write_vendor }, 245 { "VENDOR", 30, PCIDB_OFMT_VENSTR, pcidb_write_vendor }, 246 { NULL, 0, 0, NULL } 247 }; 248 249 static const char *pcidb_device_fields = "vid,did,vendor,device"; 250 static const ofmt_field_t pcidb_device_ofmt[] = { 251 { "VID", 8, PCIDB_OFMT_VID, pcidb_write_vendor }, 252 { "VENDOR", 30, PCIDB_OFMT_VENSTR, pcidb_write_vendor }, 253 { "DID", 8, PCIDB_OFMT_DID, pcidb_write_device }, 254 { "DEVICE", 30, PCIDB_OFMT_DEVSTR, pcidb_write_device }, 255 { NULL, 0, 0, NULL } 256 }; 257 258 static const char *pcidb_subsystem_fields = "vid,did,svid,sdid,subsystem"; 259 static const ofmt_field_t pcidb_subsystem_ofmt[] = { 260 { "VID", 8, PCIDB_OFMT_VID, pcidb_write_vendor }, 261 { "VENDOR", 30, PCIDB_OFMT_VENSTR, pcidb_write_vendor }, 262 { "DID", 8, PCIDB_OFMT_DID, pcidb_write_device }, 263 { "DEVICE", 30, PCIDB_OFMT_DEVSTR, pcidb_write_device }, 264 { "SVID", 8, PCIDB_OFMT_SVID, pcidb_write_subsystem }, 265 { "SDID", 8, PCIDB_OFMT_SDID, pcidb_write_subsystem }, 266 { "SUBSYSTEM", 30, PCIDB_OFMT_SUBSYSSTR, pcidb_write_subsystem }, 267 { "SUBVENDOR", 30, PCIDB_OFMT_SUBVENSTR, pcidb_write_subsystem }, 268 { NULL, 0, 0, NULL } 269 }; 270 271 static const char *pcidb_class_fields = "bcc,class"; 272 static const ofmt_field_t pcidb_class_ofmt[] = { 273 { "BCC", 6, PCIDB_OFMT_BCC, pcidb_write_class }, 274 { "CLASS", 30, PCIDB_OFMT_CLASSSTR, pcidb_write_class }, 275 { NULL, 0, 0, NULL } 276 }; 277 278 static const char *pcidb_subclass_fields = "bcc,scc,class,subclass"; 279 static const ofmt_field_t pcidb_subclass_ofmt[] = { 280 { "BCC", 6, PCIDB_OFMT_BCC, pcidb_write_class }, 281 { "CLASS", 30, PCIDB_OFMT_CLASSSTR, pcidb_write_class }, 282 { "SCC", 6, PCIDB_OFMT_SCC, pcidb_write_subclass }, 283 { "SUBCLASS", 30, PCIDB_OFMT_SUBCLASSSTR, pcidb_write_subclass }, 284 { NULL, 0, 0, NULL } 285 }; 286 287 static const char *pcidb_progif_fields = "bcc,scc,pi,subclass,interface"; 288 static const ofmt_field_t pcidb_progif_ofmt[] = { 289 { "BCC", 6, PCIDB_OFMT_BCC, pcidb_write_class }, 290 { "CLASS", 30, PCIDB_OFMT_CLASSSTR, pcidb_write_class }, 291 { "SCC", 6, PCIDB_OFMT_SCC, pcidb_write_subclass }, 292 { "SUBCLASS", 30, PCIDB_OFMT_SUBCLASSSTR, pcidb_write_subclass }, 293 { "PI", 6, PCIDB_OFMT_PI, pcidb_write_progif }, 294 { "INTERFACE", 30, PCIDB_OFMT_PROGIFSTR, pcidb_write_progif }, 295 { NULL, 0, 0, NULL } 296 }; 297 298 static void 299 pcidb_ofmt_errx(const char *fmt, ...) 300 { 301 va_list ap; 302 303 va_start(ap, fmt); 304 verrx(EXIT_FAILURE, fmt, ap); 305 } 306 307 /* 308 * Check to see if any of our filters match. Note, our filters may overlap and 309 * one may be more specific than another. As a result, once we find a match we 310 * check all remaining filters and check if any of them would also match this 311 * just to reduce the chance of user error. For example, if we didn't do this a 312 * series of filters such as "pci8086 pci8086,10d3" will cause the latter to 313 * never be matched. 314 */ 315 static boolean_t 316 pcidb_filter_match(pcidb_walk_t *walk) 317 { 318 boolean_t match = B_FALSE; 319 pcidb_tgrp_t grp = PCIDB_TGRP_NONE; 320 321 if (walk->pw_nfilters == 0) { 322 return (B_TRUE); 323 } 324 325 for (uint_t i = 0; i < walk->pw_nfilters; i++) { 326 pcidb_filter_t *filt = &walk->pw_filters[i]; 327 328 if (match && (filt->pft_used || grp != filt->pft_tgrp)) { 329 continue; 330 } 331 332 if (filt->pft_vend != PCIDB_NOFILTER && 333 (walk->pw_vendor == NULL || 334 filt->pft_vend != pcidb_vendor_id(walk->pw_vendor))) { 335 continue; 336 } 337 338 if (filt->pft_dev != PCIDB_NOFILTER && 339 (walk->pw_device == NULL || 340 filt->pft_dev != pcidb_device_id(walk->pw_device))) { 341 continue; 342 } 343 344 if (filt->pft_subven != PCIDB_NOFILTER && 345 (walk->pw_subvd == NULL || 346 filt->pft_subven != pcidb_subvd_svid(walk->pw_subvd))) { 347 continue; 348 } 349 350 if (filt->pft_subdev != PCIDB_NOFILTER && 351 (walk->pw_subvd == NULL || 352 filt->pft_subdev != pcidb_subvd_sdid(walk->pw_subvd))) { 353 continue; 354 } 355 356 if (filt->pft_class != PCIDB_NOFILTER && 357 (walk->pw_class == NULL || 358 filt->pft_class != pcidb_class_code(walk->pw_class))) { 359 continue; 360 } 361 362 if (filt->pft_subclass != PCIDB_NOFILTER && 363 (walk->pw_subclass == NULL || 364 filt->pft_subclass != 365 pcidb_subclass_code(walk->pw_subclass))) { 366 continue; 367 } 368 369 if (filt->pft_progif != PCIDB_NOFILTER && 370 (walk->pw_progif == NULL || 371 filt->pft_progif != pcidb_progif_code(walk->pw_progif))) { 372 continue; 373 } 374 375 filt->pft_used = B_TRUE; 376 grp = filt->pft_tgrp; 377 match = B_TRUE; 378 } 379 380 return (match); 381 } 382 383 static void 384 pcidb_walk_vendors(pcidb_walk_t *walk) 385 { 386 pcidb_hdl_t *hdl = walk->pw_hdl; 387 388 for (pcidb_vendor_t *vend = pcidb_vendor_iter(hdl); vend != NULL; 389 vend = pcidb_vendor_iter_next(vend)) { 390 walk->pw_vendor = vend; 391 if (!pcidb_filter_match(walk)) 392 continue; 393 ofmt_print(walk->pw_ofmt, walk); 394 } 395 } 396 397 static void 398 pcidb_walk_devices(pcidb_walk_t *walk) 399 { 400 pcidb_hdl_t *hdl = walk->pw_hdl; 401 402 for (pcidb_vendor_t *vend = pcidb_vendor_iter(hdl); vend != NULL; 403 vend = pcidb_vendor_iter_next(vend)) { 404 walk->pw_vendor = vend; 405 for (pcidb_device_t *dev = pcidb_device_iter(vend); dev != NULL; 406 dev = pcidb_device_iter_next(dev)) { 407 walk->pw_device = dev; 408 if (!pcidb_filter_match(walk)) 409 continue; 410 ofmt_print(walk->pw_ofmt, walk); 411 } 412 } 413 } 414 415 static void 416 pcidb_walk_subsystems(pcidb_walk_t *walk) 417 { 418 pcidb_hdl_t *hdl = walk->pw_hdl; 419 420 for (pcidb_vendor_t *vend = pcidb_vendor_iter(hdl); vend != NULL; 421 vend = pcidb_vendor_iter_next(vend)) { 422 walk->pw_vendor = vend; 423 for (pcidb_device_t *dev = pcidb_device_iter(vend); dev != NULL; 424 dev = pcidb_device_iter_next(dev)) { 425 walk->pw_device = dev; 426 for (pcidb_subvd_t *sub = pcidb_subvd_iter(dev); 427 sub != NULL; sub = pcidb_subvd_iter_next(sub)) { 428 walk->pw_subvd = sub; 429 if (!pcidb_filter_match(walk)) 430 continue; 431 ofmt_print(walk->pw_ofmt, walk); 432 } 433 } 434 435 } 436 } 437 438 static void 439 pcidb_walk_classes(pcidb_walk_t *walk) 440 { 441 for (pcidb_class_t *class = pcidb_class_iter(walk->pw_hdl); 442 class != NULL; class = pcidb_class_iter_next(class)) { 443 walk->pw_class = class; 444 if (!pcidb_filter_match(walk)) 445 continue; 446 ofmt_print(walk->pw_ofmt, walk); 447 } 448 } 449 450 static void 451 pcidb_walk_subclasses(pcidb_walk_t *walk) 452 { 453 for (pcidb_class_t *class = pcidb_class_iter(walk->pw_hdl); 454 class != NULL; class = pcidb_class_iter_next(class)) { 455 walk->pw_class = class; 456 for (pcidb_subclass_t *sub = pcidb_subclass_iter(class); 457 sub != NULL; sub = pcidb_subclass_iter_next(sub)) { 458 walk->pw_subclass = sub; 459 if (!pcidb_filter_match(walk)) 460 continue; 461 ofmt_print(walk->pw_ofmt, walk); 462 } 463 } 464 } 465 466 static void 467 pcidb_walk_progifs(pcidb_walk_t *walk) 468 { 469 for (pcidb_class_t *class = pcidb_class_iter(walk->pw_hdl); 470 class != NULL; class = pcidb_class_iter_next(class)) { 471 walk->pw_class = class; 472 for (pcidb_subclass_t *sub = pcidb_subclass_iter(class); 473 sub != NULL; sub = pcidb_subclass_iter_next(sub)) { 474 walk->pw_subclass = sub; 475 for (pcidb_progif_t *progif = pcidb_progif_iter(sub); 476 progif != NULL; 477 progif = pcidb_progif_iter_next(progif)) { 478 walk->pw_progif = progif; 479 if (!pcidb_filter_match(walk)) 480 continue; 481 ofmt_print(walk->pw_ofmt, walk); 482 } 483 } 484 } 485 } 486 487 static void 488 pcidb_parse_class_filter(pcidb_filter_t *filter, char *arg, const char *orig) 489 { 490 size_t len; 491 unsigned long val; 492 char *eptr; 493 494 filter->pft_vend = filter->pft_dev = PCIDB_NOFILTER; 495 filter->pft_subven = filter->pft_subdev = PCIDB_NOFILTER; 496 497 len = strlen(arg); 498 if (len != 2 && len != 4 && len != 6) { 499 errx(EXIT_FAILURE, "invalid class filter: '%s': bad length", 500 orig); 501 } 502 503 errno = 0; 504 val = strtoul(arg, &eptr, 16); 505 if (errno != 0 || *eptr != '\0') { 506 errx(EXIT_FAILURE, "invalid class filter: '%s': failed to " 507 "parse hex string", orig); 508 } 509 510 if (len == 6) { 511 filter->pft_progif = val & 0xff; 512 val = val >> 8; 513 } else { 514 filter->pft_progif = PCIDB_NOFILTER; 515 } 516 517 if (len >= 4) { 518 filter->pft_subclass = val & 0xff; 519 val = val >> 8; 520 } else { 521 filter->pft_subclass = PCIDB_NOFILTER; 522 } 523 524 filter->pft_class = val & 0xff; 525 } 526 527 static void 528 pcidb_parse_device_filter(pcidb_filter_t *filter, char *arg, const char *orig) 529 { 530 unsigned long val; 531 uint32_t primary, secondary; 532 char *eptr; 533 534 filter->pft_vend = filter->pft_dev = PCIDB_NOFILTER; 535 filter->pft_subven = filter->pft_subdev = PCIDB_NOFILTER; 536 filter->pft_class = filter->pft_subclass = PCIDB_NOFILTER; 537 filter->pft_progif = PCIDB_NOFILTER; 538 539 errno = 0; 540 val = strtoul(arg, &eptr, 16); 541 if (errno != 0 || (*eptr != '\0' && *eptr != ',')) { 542 errx(EXIT_FAILURE, "invalid device filter: '%s': failed to " 543 "parse hex string", orig); 544 } 545 546 if (val > UINT16_MAX) { 547 errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff", 548 val); 549 } 550 551 primary = (uint32_t)val; 552 if (*eptr == '\0') { 553 filter->pft_vend = primary; 554 return; 555 } else if (strcmp(eptr, ",s") == 0) { 556 filter->pft_subven = primary; 557 return; 558 } else if (eptr[1] == '\0') { 559 errx(EXIT_FAILURE, "invalid device filter: '%s': filter " 560 "terminated early", arg); 561 } 562 563 arg = eptr + 1; 564 val = strtoul(arg, &eptr, 16); 565 if (errno != 0 || (*eptr != '\0' && *eptr != ',' && *eptr != '.')) { 566 errx(EXIT_FAILURE, "invalid device filter: '%s': failed to " 567 "parse hex string at %s", orig, arg); 568 } 569 570 if (val > UINT16_MAX) { 571 errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff", 572 val); 573 } 574 575 secondary = (uint32_t)val; 576 if (*eptr == '\0') { 577 filter->pft_vend = primary; 578 filter->pft_dev = secondary; 579 return; 580 } else if (eptr[1] == '\0') { 581 errx(EXIT_FAILURE, "invalid device filter: '%s': filter " 582 "terminated early", arg); 583 } 584 585 if (*eptr == ',') { 586 if (eptr[1] == 'p' && eptr[2] == '\0') { 587 filter->pft_vend = primary; 588 filter->pft_dev = secondary; 589 return; 590 } 591 if (eptr[1] == 's' && eptr[2] == '\0') { 592 filter->pft_subven = primary; 593 filter->pft_subdev = secondary; 594 return; 595 } 596 errx(EXIT_FAILURE, "invalid device filter: '%s': invalid " 597 "trailing comma at %s, expected either ,p or ,s", 598 orig, eptr); 599 } 600 601 filter->pft_vend = primary; 602 filter->pft_dev = secondary; 603 604 arg = eptr + 1; 605 errno = 0; 606 val = strtoul(arg, &eptr, 16); 607 if (errno != 0 || (*eptr != '\0' && *eptr != ',')) { 608 errx(EXIT_FAILURE, "invalid device filter: '%s': failed to " 609 "parse hex string at %s", orig, arg); 610 } 611 612 if (val > UINT16_MAX) { 613 errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff", 614 val); 615 } 616 617 filter->pft_subven = (uint32_t)val; 618 if (*eptr == '\0') { 619 return; 620 } else if (eptr[1] == '\0') { 621 errx(EXIT_FAILURE, "invalid device filter: '%s': filter " 622 "terminated early", arg); 623 } 624 625 arg = eptr + 1; 626 errno = 0; 627 val = strtoul(arg, &eptr, 16); 628 if (errno != 0 || *eptr != '\0') { 629 errx(EXIT_FAILURE, "invalid device filter: '%s': failed to " 630 "parse hex string at %s", orig, arg); 631 } 632 633 if (val > UINT16_MAX) { 634 errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff", 635 val); 636 } 637 638 filter->pft_subdev = (uint32_t)val; 639 } 640 641 static pcidb_table_t 642 pcidb_filter_to_table(const pcidb_filter_t *filter) 643 { 644 if (filter->pft_progif != PCIDB_NOFILTER) { 645 return (PCIDB_TABLE_PROGIF); 646 } else if (filter->pft_subclass != PCIDB_NOFILTER) { 647 return (PCIDB_TABLE_SUBCLASS); 648 } else if (filter->pft_class != PCIDB_NOFILTER) { 649 return (PCIDB_TABLE_CLASS); 650 } else if (filter->pft_subven != PCIDB_NOFILTER || 651 filter->pft_subdev != PCIDB_NOFILTER) { 652 return (PCIDB_TABLE_SUBSYSTEM); 653 } else if (filter->pft_dev != PCIDB_NOFILTER) { 654 return (PCIDB_TABLE_DEVICE); 655 } else { 656 VERIFY3U(filter->pft_vend, !=, PCIDB_NOFILTER); 657 return (PCIDB_TABLE_VENDOR); 658 } 659 } 660 661 static const char * 662 pcidb_table_to_string(pcidb_table_t table) 663 { 664 switch (table) { 665 case PCIDB_TABLE_VENDOR: 666 return ("vendor"); 667 case PCIDB_TABLE_DEVICE: 668 return ("device"); 669 case PCIDB_TABLE_SUBSYSTEM: 670 return ("subsystem"); 671 case PCIDB_TABLE_CLASS: 672 return ("class"); 673 case PCIDB_TABLE_SUBCLASS: 674 return ("subclass"); 675 case PCIDB_TABLE_PROGIF: 676 return ("programming interface"); 677 case PCIDB_TABLE_NONE: 678 return ("none"); 679 default: 680 abort(); 681 } 682 683 } 684 685 static pcidb_tgrp_t 686 pcidb_table_to_group(pcidb_table_t table) 687 { 688 switch (table) { 689 case PCIDB_TABLE_VENDOR: 690 case PCIDB_TABLE_DEVICE: 691 case PCIDB_TABLE_SUBSYSTEM: 692 return (PCIDB_TGRP_DEV); 693 case PCIDB_TABLE_CLASS: 694 case PCIDB_TABLE_SUBCLASS: 695 case PCIDB_TABLE_PROGIF: 696 return (PCIDB_TGRP_CLASS); 697 case PCIDB_TABLE_NONE: 698 return (PCIDB_TGRP_NONE); 699 default: 700 abort(); 701 } 702 } 703 704 /* 705 * PCIDB_TABLE_NONE is not in here as it should only be a sentinal value and not 706 * something that users see. 707 */ 708 static const char * 709 pcidb_table_to_grpstr(pcidb_table_t table) 710 { 711 switch (pcidb_table_to_group(table)) { 712 case PCIDB_TGRP_DEV: 713 return ("vendor/device/subsystem"); 714 case PCIDB_TGRP_CLASS: 715 return ("class/subclass/progif"); 716 default: 717 abort(); 718 } 719 } 720 721 /* 722 * Process a series of alias style ways of indicating numeric filters. Use the 723 * basic alias format for now. 724 */ 725 static void 726 pcidb_parse_filters(int argc, char *argv[], pcidb_walk_t *walkp) 727 { 728 if (argc <= 0) { 729 walkp->pw_nfilters = 0; 730 return; 731 } 732 733 walkp->pw_nfilters = argc; 734 walkp->pw_filters = calloc(walkp->pw_nfilters, sizeof (pcidb_filter_t)); 735 if (walkp->pw_filters == NULL) { 736 err(EXIT_FAILURE, "failed to allocate memory for filters"); 737 } 738 739 for (int i = 0; i < argc; i++) { 740 char *str = strdup(argv[i]); 741 742 if (str == NULL) { 743 errx(EXIT_FAILURE, "failed to duplicate string %s", 744 argv[i]); 745 } 746 747 if (strncmp(str, "pciexclass,", 11) == 0) { 748 pcidb_parse_class_filter(&walkp->pw_filters[i], 749 str + 11, argv[i]); 750 } else if (strncmp(str, "pciclass,", 9) == 0) { 751 pcidb_parse_class_filter(&walkp->pw_filters[i], str + 9, 752 argv[i]); 753 } else if (strncmp(str, "pciex", 5) == 0) { 754 pcidb_parse_device_filter(&walkp->pw_filters[i], 755 str + 5, argv[i]); 756 } else if (strncmp(str, "pci", 3) == 0) { 757 pcidb_parse_device_filter(&walkp->pw_filters[i], 758 str + 3, argv[i]); 759 } else { 760 errx(EXIT_FAILURE, "invalid filter string: %s", str); 761 } 762 763 free(str); 764 walkp->pw_filters[i].pft_raw = argv[i]; 765 walkp->pw_filters[i].pft_used = B_FALSE; 766 walkp->pw_filters[i].pft_table = 767 pcidb_filter_to_table(&walkp->pw_filters[i]); 768 walkp->pw_filters[i].pft_tgrp = 769 pcidb_table_to_group(walkp->pw_filters[i].pft_table); 770 } 771 } 772 773 /* 774 * Determine if the set of filters is mutually consistent with the filters that 775 * we have been requested. Our goal is to prevent a user from specifying 776 * something that is basically unsatisfiable. For example, if they ask for a 777 * filter related to devices but have specified the class table or vice versa. 778 * Similarly, if someone has specified no table on the command line then we 779 * should pick a default that is actually usable. As such we have a few rules 780 * that we check: 781 * 782 * - All filters must be in the same group of table. That is either in the 783 * vendor/device/subsystem group or the class/subclass/progif group. 784 * - A less specific filter for a group is always valid for a more specific 785 * table. 786 * - A more specific filter for a group is always invalid for a less specific 787 * table. 788 * - If the user did not request the table, we can automatically move the 789 * filter to the more specific value. 790 */ 791 static void 792 pcidb_validate_filters(const pcidb_walk_t *walk, pcidb_table_t *table) 793 { 794 pcidb_table_t cur = *table; 795 boolean_t tset = cur != PCIDB_TABLE_NONE; 796 797 for (uint_t i = 0; i < walk->pw_nfilters; i++) { 798 const pcidb_filter_t *filt = &walk->pw_filters[i]; 799 800 /* 801 * If we have the same table as the current one, then there is 802 * nothing to do. 803 */ 804 if (cur == filt->pft_table) { 805 continue; 806 } 807 808 /* 809 * When there is no current table, which implies tset is false, 810 * then we can always change this around. Note, if someone asks 811 * for the vendor table, we want to try to use the device table 812 * as the default like when there are no filters specified. 813 */ 814 if (cur == PCIDB_TABLE_NONE) { 815 cur = filt->pft_table; 816 if (cur == PCIDB_TABLE_VENDOR) 817 cur = PCIDB_TABLE_DEVICE; 818 continue; 819 } 820 821 if (pcidb_table_to_group(cur) != filt->pft_tgrp) { 822 errx(EXIT_FAILURE, "filter %s targets the %s table, " 823 "but other filters target the %s group of tables: " 824 "both cannot be used at the same time", 825 filt->pft_raw, 826 pcidb_table_to_string(filt->pft_table), 827 pcidb_table_to_grpstr(cur)); 828 } 829 830 /* 831 * This confirms the current filter is less than the target. The 832 * equality case was handled up above. 833 */ 834 if (filt->pft_table < cur) { 835 continue; 836 } 837 838 /* 839 * We require a more specific table than we currently have. If 840 * this hasn't been requested, then it's fine. Otherwise it's an 841 * error. We can't change an explicitly requested table out from 842 * a user. 843 */ 844 if (tset) { 845 errx(EXIT_FAILURE, "filter %s needs to match against " 846 "table %s, which is more specific than the " 847 "requested table %s", filt->pft_raw, 848 pcidb_table_to_string(filt->pft_table), 849 pcidb_table_to_string(cur)); 850 } 851 cur = filt->pft_table; 852 } 853 854 *table = cur; 855 } 856 857 static void 858 pcidb_drop_privs(void) 859 { 860 priv_set_t *curprivs, *targprivs; 861 862 if ((curprivs = priv_allocset()) == NULL) { 863 err(EXIT_FAILURE, "failed to allocate privilege set to drop " 864 "privs"); 865 } 866 867 if (getppriv(PRIV_EFFECTIVE, curprivs) != 0) { 868 err(EXIT_FAILURE, "failed to get current privileges"); 869 } 870 871 if ((targprivs = priv_allocset()) == NULL) { 872 err(EXIT_FAILURE, "failed to allocate privilege set to drop " 873 "privs"); 874 } 875 876 /* 877 * Set our privileges to the minimum required. Because stdout will have 878 * already been opened, all we need is the ability to read files from 879 * basic privileges. We opt to keep FILE_DAC_READ if the caller has it 880 * just in case there is something weird about the location of the 881 * pci.ids files. 882 */ 883 priv_basicset(targprivs); 884 VERIFY0(priv_delset(targprivs, PRIV_FILE_LINK_ANY)); 885 VERIFY0(priv_delset(targprivs, PRIV_PROC_INFO)); 886 VERIFY0(priv_delset(targprivs, PRIV_PROC_SESSION)); 887 VERIFY0(priv_delset(targprivs, PRIV_PROC_FORK)); 888 VERIFY0(priv_delset(targprivs, PRIV_NET_ACCESS)); 889 VERIFY0(priv_delset(targprivs, PRIV_FILE_WRITE)); 890 VERIFY0(priv_delset(targprivs, PRIV_PROC_EXEC)); 891 VERIFY0(priv_addset(targprivs, PRIV_FILE_DAC_READ)); 892 893 priv_intersect(curprivs, targprivs); 894 895 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, targprivs) != 0) { 896 err(EXIT_FAILURE, "failed to reduce privileges"); 897 } 898 899 priv_freeset(curprivs); 900 priv_freeset(targprivs); 901 } 902 903 static int 904 pcidb_usage(const char *fmt, ...) 905 { 906 if (fmt != NULL) { 907 va_list ap; 908 909 (void) fprintf(stderr, "%s: ", pcidb_progname); 910 va_start(ap, fmt); 911 (void) vfprintf(stderr, fmt, ap); 912 va_end(ap); 913 (void) fprintf(stderr, "\n"); 914 } 915 916 (void) fprintf(stderr, "usage: %s [-v|-d|-s|-c|-S|-i] [-H]" 917 "[[-p] [-o <field>[,...]] [<filter>]\n\n" 918 "\t-v\t\tshow vendor table\n" 919 "\t-d\t\tshow device table\n" 920 "\t-s\t\tshow subsystem table\n" 921 "\t-c\t\tshow class table\n" 922 "\t-S\t\tshow subclass table\n" 923 "\t-i\t\tshow programming interface table\n" 924 "\t-H\t\tdo not output column headers\n" 925 "\t-p\t\toutput in parsable form\n" 926 "\t-o field\toutput only specified fields\n\n" 927 "filters take the form of PCI aliases, e.g. pci8086,1522, " 928 "pci1028,1f44,s, or\n" 929 "pciex1022,1480.1462,7c37. Classes can be specified in a similar " 930 "way, e.g.\npciclass,010802 or pciclass,0403.\n\n" 931 "If no table is specified, then a table will be picked based on " 932 "the specified\nfilters. The default is to use the device table.\n", 933 pcidb_progname); 934 935 return (EXIT_USAGE); 936 } 937 938 int 939 main(int argc, char *argv[]) 940 { 941 pcidb_hdl_t *hdl; 942 int c, ret; 943 uint_t tablecnt = 0; 944 pcidb_table_t table = PCIDB_TABLE_NONE; 945 boolean_t parse = B_FALSE, strcase = B_FALSE; 946 const char *fields = NULL; 947 const char *ofmt_fields_str = NULL; 948 const ofmt_field_t *ofmt_fields = NULL; 949 ofmt_handle_t ofmt; 950 ofmt_status_t oferr; 951 uint_t flags = 0; 952 pcidb_walk_t walk; 953 954 bzero(&walk, sizeof (walk)); 955 pcidb_progname = basename(argv[0]); 956 957 pcidb_drop_privs(); 958 959 while ((c = getopt(argc, argv, ":vdscSipo:hH")) != -1) { 960 switch (c) { 961 case 'v': 962 tablecnt++; 963 table = PCIDB_TABLE_VENDOR; 964 break; 965 case 'd': 966 tablecnt++; 967 table = PCIDB_TABLE_DEVICE; 968 break; 969 case 's': 970 tablecnt++; 971 table = PCIDB_TABLE_SUBSYSTEM; 972 break; 973 case 'c': 974 tablecnt++; 975 table = PCIDB_TABLE_CLASS; 976 break; 977 case 'S': 978 tablecnt++; 979 table = PCIDB_TABLE_SUBCLASS; 980 break; 981 case 'i': 982 tablecnt++; 983 table = PCIDB_TABLE_PROGIF; 984 break; 985 case 'p': 986 parse = B_TRUE; 987 flags |= OFMT_PARSABLE; 988 break; 989 case 'o': 990 fields = optarg; 991 break; 992 case 'h': 993 return (pcidb_usage(NULL)); 994 case 'H': 995 flags |= OFMT_NOHEADER; 996 break; 997 case ':': 998 return (pcidb_usage("Option -%c requires an argument", 999 optopt)); 1000 case '?': 1001 return (pcidb_usage("unknown option: -%c", optopt)); 1002 } 1003 } 1004 1005 if (tablecnt > 1) { 1006 errx(EXIT_USAGE, "more than one table specified, only one of " 1007 "-v, -d, -s, -c, -S, and -i may be specified"); 1008 } 1009 1010 if (parse && fields == NULL) { 1011 errx(EXIT_USAGE, "-p requires fields specified with -o"); 1012 } 1013 1014 argc -= optind; 1015 argv += optind; 1016 1017 /* 1018 * Determine that the set of filters we have been asked for makes sense 1019 * with the table that's been requested. If no table has been requested, 1020 * then go ahead and adjust our table to this. If there's still no table 1021 * that's been asked for because there are no filters, then we just go 1022 * to the default of the device table. 1023 */ 1024 pcidb_parse_filters(argc, argv, &walk); 1025 pcidb_validate_filters(&walk, &table); 1026 1027 switch (table) { 1028 case PCIDB_TABLE_VENDOR: 1029 ofmt_fields = pcidb_vendor_ofmt; 1030 ofmt_fields_str = pcidb_vendor_fields; 1031 break; 1032 case PCIDB_TABLE_NONE: 1033 case PCIDB_TABLE_DEVICE: 1034 ofmt_fields = pcidb_device_ofmt; 1035 ofmt_fields_str = pcidb_device_fields; 1036 break; 1037 case PCIDB_TABLE_SUBSYSTEM: 1038 ofmt_fields = pcidb_subsystem_ofmt; 1039 ofmt_fields_str = pcidb_subsystem_fields; 1040 break; 1041 case PCIDB_TABLE_CLASS: 1042 ofmt_fields = pcidb_class_ofmt; 1043 ofmt_fields_str = pcidb_class_fields; 1044 break; 1045 case PCIDB_TABLE_SUBCLASS: 1046 ofmt_fields = pcidb_subclass_ofmt; 1047 ofmt_fields_str = pcidb_subclass_fields; 1048 break; 1049 case PCIDB_TABLE_PROGIF: 1050 ofmt_fields = pcidb_progif_ofmt; 1051 ofmt_fields_str = pcidb_progif_fields; 1052 break; 1053 } 1054 1055 if (fields == NULL) { 1056 fields = ofmt_fields_str; 1057 } 1058 1059 oferr = ofmt_open(fields, ofmt_fields, flags, 0, &ofmt); 1060 ofmt_check(oferr, parse, ofmt, pcidb_ofmt_errx, warnx); 1061 1062 hdl = pcidb_open(PCIDB_VERSION); 1063 if (hdl == NULL) { 1064 err(EXIT_FAILURE, "failed to initialize PCI IDs database"); 1065 } 1066 1067 walk.pw_hdl = hdl; 1068 walk.pw_ofmt = ofmt; 1069 walk.pw_strcase = strcase; 1070 1071 switch (table) { 1072 case PCIDB_TABLE_VENDOR: 1073 pcidb_walk_vendors(&walk); 1074 break; 1075 case PCIDB_TABLE_NONE: 1076 case PCIDB_TABLE_DEVICE: 1077 pcidb_walk_devices(&walk); 1078 break; 1079 case PCIDB_TABLE_SUBSYSTEM: 1080 pcidb_walk_subsystems(&walk); 1081 break; 1082 case PCIDB_TABLE_CLASS: 1083 pcidb_walk_classes(&walk); 1084 break; 1085 case PCIDB_TABLE_SUBCLASS: 1086 pcidb_walk_subclasses(&walk); 1087 break; 1088 case PCIDB_TABLE_PROGIF: 1089 pcidb_walk_progifs(&walk); 1090 break; 1091 } 1092 1093 ofmt_close(ofmt); 1094 pcidb_close(hdl); 1095 1096 /* 1097 * Check that all filters were used. We don't bother with checking if we 1098 * printed anything more broadly because we know that the database will 1099 * always have something in there so no filters should always print 1100 * something. 1101 */ 1102 ret = EXIT_SUCCESS; 1103 for (uint_t i = 0; i < walk.pw_nfilters; i++) { 1104 if (!walk.pw_filters[i].pft_used) { 1105 warnx("filter '%s' did not match anything", 1106 walk.pw_filters[i].pft_raw); 1107 ret = EXIT_FAILURE; 1108 } 1109 } 1110 1111 return (ret); 1112 } 1113