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