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