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