xref: /illumos-gate/usr/src/cmd/pcieadm/pcieadm_devs.c (revision c778cbeb4bc8b9609f43b41183c6c774f574a9c2)
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 	pcieadm_show_devs_ofmt_t oarg;
331 	pcidb_hdl_t *pcidb = psd->psd_pia->pia_pcidb;
332 
333 	bzero(&oarg, sizeof (oarg));
334 
335 	path = di_devfs_path(node);
336 	if (path == NULL) {
337 		err(EXIT_FAILURE, "failed to construct devfs path for node: "
338 		    "%s (%s)", di_node_name(node));
339 	}
340 
341 	nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg", &regs);
342 	if (nprop <= 0) {
343 		errx(EXIT_FAILURE, "failed to lookup regs array for %s",
344 		    path);
345 	}
346 
347 	oarg.psdo_path = path;
348 	oarg.psdo_bus = PCI_REG_BUS_G(regs[0]);
349 	oarg.psdo_dev = PCI_REG_DEV_G(regs[0]);
350 	oarg.psdo_func = PCI_REG_FUNC_G(regs[0]);
351 
352 	if (oarg.psdo_func != 0 && !psd->psd_funcs) {
353 		goto done;
354 	}
355 
356 	oarg.psdo_driver = di_driver_name(node);
357 	oarg.psdo_instance = di_instance(node);
358 
359 	nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &did);
360 	if (nprop != 1) {
361 		oarg.psdo_did = -1;
362 	} else {
363 		oarg.psdo_did = (uint16_t)*did;
364 	}
365 
366 	nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vid);
367 	if (nprop != 1) {
368 		oarg.psdo_vid = -1;
369 	} else {
370 		oarg.psdo_vid = (uint16_t)*vid;
371 	}
372 
373 	oarg.psdo_vendor = "--";
374 	if (oarg.psdo_vid != -1) {
375 		pcidb_vendor_t *vend = pcidb_lookup_vendor(pcidb,
376 		    oarg.psdo_vid);
377 		if (vend != NULL) {
378 			oarg.psdo_vendor = pcidb_vendor_name(vend);
379 		}
380 	}
381 
382 	oarg.psdo_device = "--";
383 	if (oarg.psdo_vid != -1 && oarg.psdo_did != -1) {
384 		pcidb_device_t *dev = pcidb_lookup_device(pcidb,
385 		    oarg.psdo_vid, oarg.psdo_did);
386 		if (dev != NULL) {
387 			oarg.psdo_device = pcidb_device_name(dev);
388 
389 		}
390 	}
391 
392 	nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node,
393 	    "pcie-link-maximum-width", &mwidth);
394 	if (nprop != 1) {
395 		oarg.psdo_mwidth = -1;
396 	} else {
397 		oarg.psdo_mwidth = *mwidth;
398 	}
399 
400 	nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node,
401 	    "pcie-link-current-width", &cwidth);
402 	if (nprop != 1) {
403 		oarg.psdo_cwidth = -1;
404 	} else {
405 		oarg.psdo_cwidth = *cwidth;
406 	}
407 
408 	nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node,
409 	    "pcie-link-maximum-speed", &mspeed);
410 	if (nprop != 1) {
411 		oarg.psdo_mspeed = -1;
412 	} else {
413 		oarg.psdo_mspeed = *mspeed;
414 	}
415 
416 	nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node,
417 	    "pcie-link-current-speed", &cspeed);
418 	if (nprop != 1) {
419 		oarg.psdo_cspeed = -1;
420 	} else {
421 		oarg.psdo_cspeed = *cspeed;
422 	}
423 
424 	nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node,
425 	    "pcie-link-supported-speeds", &sspeeds);
426 	if (nprop > 0) {
427 		oarg.psdo_nspeeds = nprop;
428 		oarg.psdo_sspeeds = sspeeds;
429 	} else {
430 		oarg.psdo_nspeeds = 0;
431 		oarg.psdo_sspeeds = NULL;
432 	}
433 
434 	if (pcieadm_show_devs_match(psd, &oarg)) {
435 		ofmt_print(psd->psd_ofmt, &oarg);
436 		psd->psd_nprint++;
437 	}
438 
439 done:
440 	if (path != NULL) {
441 		di_devfs_path_free(path);
442 	}
443 
444 	return (ret);
445 }
446 
447 void
448 pcieadm_show_devs_usage(FILE *f)
449 {
450 	(void) fprintf(f, "\tshow-devs\t[-F] [-H] [-s | -o field[,...] [-p]] "
451 	    "[filter...]\n");
452 }
453 
454 static void
455 pcieadm_show_devs_help(const char *fmt, ...)
456 {
457 	if (fmt != NULL) {
458 		va_list ap;
459 
460 		va_start(ap, fmt);
461 		vwarnx(fmt, ap);
462 		va_end(ap);
463 		(void) fprintf(stderr, "\n");
464 	}
465 
466 	(void) fprintf(stderr, "Usage:  %s show-devs [-F] [-H] [-s | -o "
467 	    "field[,...] [-p]] [filter...]\n", pcieadm_progname);
468 
469 	(void) fprintf(stderr, "\nList PCI devices and functions in the "
470 	    "system. Each <filter> selects a set\nof devices to show and "
471 	    "can be a driver name, instance, /devices path, or\nb/d/f.\n\n"
472 	    "\t-F\t\tdo not display PCI functions\n"
473 	    "\t-H\t\tomit the column header\n"
474 	    "\t-o field\toutput fields to print\n"
475 	    "\t-p\t\tparsable output (requires -o)\n"
476 	    "\t-s\t\tlist speeds and widths\n");
477 
478 }
479 
480 int
481 pcieadm_show_devs(pcieadm_t *pcip, int argc, char *argv[])
482 {
483 	int c;
484 	uint_t flags = 0;
485 	const char *fields = NULL;
486 	pcieadm_show_devs_t psd;
487 	pcieadm_di_walk_t walk;
488 	ofmt_status_t oferr;
489 	boolean_t parse = B_FALSE;
490 	boolean_t speeds = B_FALSE;
491 
492 	/*
493 	 * show-devs relies solely on the devinfo snapshot we already took.
494 	 * Formalize our privs immediately.
495 	 */
496 	pcieadm_init_privs(pcip);
497 
498 	bzero(&psd, sizeof (psd));
499 	psd.psd_pia = pcip;
500 	psd.psd_funcs = B_TRUE;
501 
502 	while ((c = getopt(argc, argv, ":FHo:ps")) != -1) {
503 		switch (c) {
504 		case 'F':
505 			psd.psd_funcs = B_FALSE;
506 			break;
507 		case 'p':
508 			parse = B_TRUE;
509 			flags |= OFMT_PARSABLE;
510 			break;
511 		case 'H':
512 			flags |= OFMT_NOHEADER;
513 			break;
514 		case 's':
515 			speeds = B_TRUE;
516 			break;
517 		case 'o':
518 			fields = optarg;
519 			break;
520 		case ':':
521 			pcieadm_show_devs_help("option -%c requires an "
522 			    "argument", optopt);
523 			exit(EXIT_USAGE);
524 		case '?':
525 			pcieadm_show_devs_help("unknown option: -%c", optopt);
526 			exit(EXIT_USAGE);
527 		}
528 	}
529 
530 	if (parse && fields == NULL) {
531 		errx(EXIT_USAGE, "-p requires fields specified with -o");
532 	}
533 
534 	if (fields != NULL && speeds) {
535 		errx(EXIT_USAGE, "-s cannot be used with with -o");
536 	}
537 
538 	if (fields == NULL) {
539 		if (speeds) {
540 			fields = pcieadm_show_dev_speeds;
541 		} else {
542 			fields = pcieadm_show_dev_fields;
543 		}
544 	}
545 
546 	argc -= optind;
547 	argv += optind;
548 
549 	if (argc > 0) {
550 		psd.psd_nfilts = argc;
551 		psd.psd_filts = argv;
552 	}
553 
554 	oferr = ofmt_open(fields, pcieadm_show_dev_ofmt, flags, 0,
555 	    &psd.psd_ofmt);
556 	ofmt_check(oferr, parse, psd.psd_ofmt, pcieadm_ofmt_errx, warnx);
557 
558 	walk.pdw_arg = &psd;
559 	walk.pdw_func = pcieadm_show_devs_walk_cb;
560 
561 	pcieadm_di_walk(pcip, &walk);
562 
563 	if (psd.psd_nprint > 0) {
564 		return (EXIT_SUCCESS);
565 	} else {
566 		return (EXIT_FAILURE);
567 	}
568 }
569