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
pcidb_write_vendor(ofmt_arg_t * ofarg,char * buf,uint_t buflen)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
pcidb_write_device(ofmt_arg_t * ofarg,char * buf,uint_t buflen)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
pcidb_write_subsystem(ofmt_arg_t * ofarg,char * buf,uint_t buflen)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
pcidb_write_class(ofmt_arg_t * ofarg,char * buf,uint_t buflen)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
pcidb_write_subclass(ofmt_arg_t * ofarg,char * buf,uint_t buflen)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
pcidb_write_progif(ofmt_arg_t * ofarg,char * buf,uint_t buflen)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
pcidb_ofmt_errx(const char * fmt,...)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
pcidb_filter_match(pcidb_walk_t * walk)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
pcidb_walk_vendors(pcidb_walk_t * walk)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
pcidb_walk_devices(pcidb_walk_t * walk)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
pcidb_walk_subsystems(pcidb_walk_t * walk)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
pcidb_walk_classes(pcidb_walk_t * walk)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
pcidb_walk_subclasses(pcidb_walk_t * walk)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
pcidb_walk_progifs(pcidb_walk_t * walk)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
pcidb_parse_class_filter(pcidb_filter_t * filter,char * arg,const char * orig)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
pcidb_parse_device_filter(pcidb_filter_t * filter,char * arg,const char * orig)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
pcidb_filter_to_table(const pcidb_filter_t * filter)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 *
pcidb_table_to_string(pcidb_table_t table)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
pcidb_table_to_group(pcidb_table_t table)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 *
pcidb_table_to_grpstr(pcidb_table_t table)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
pcidb_parse_filters(int argc,char * argv[],pcidb_walk_t * walkp)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
pcidb_validate_filters(const pcidb_walk_t * walk,pcidb_table_t * table)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
pcidb_drop_privs(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
pcidb_usage(const char * fmt,...)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
main(int argc,char * argv[])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