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 2026 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 #include <fm/libtopo.h>
23 #include <sys/fm/protocol.h>
24 #include <fm/topo_pcie.h>
25
26 #include "pcieadm.h"
27
28 typedef struct pcieadm_show_devs {
29 pcieadm_t *psd_pia;
30 ofmt_handle_t psd_ofmt;
31 boolean_t psd_funcs;
32 int psd_nfilts;
33 char **psd_filts;
34 boolean_t *psd_used;
35 uint_t psd_nprint;
36 topo_hdl_t *psd_topo;
37 } pcieadm_show_devs_t;
38
39 typedef enum pcieadm_show_devs_otype {
40 PCIEADM_SDO_VID,
41 PCIEADM_SDO_DID,
42 PCIEADM_SDO_REV,
43 PCIEADM_SDO_SUBVID,
44 PCIEADM_SDO_SUBSYS,
45 PCIEADM_SDO_BDF,
46 PCIEADM_SDO_BDF_BUS,
47 PCIEADM_SDO_BDF_DEV,
48 PCIEADM_SDO_BDF_FUNC,
49 PCIEADM_SDO_DRIVER,
50 PCIEADM_SDO_INSTANCE,
51 PCIEADM_SDO_INSTNUM,
52 PCIEADM_SDO_TYPE,
53 PCIEADM_SDO_VENDOR,
54 PCIEADM_SDO_DEVICE,
55 PCIEADM_SDO_SUBVENDOR,
56 PCIEADM_SDO_SUBSYSTEM,
57 PCIEADM_SDO_PATH,
58 PCIEADM_SDO_MAXSPEED,
59 PCIEADM_SDO_MAXWIDTH,
60 PCIEADM_SDO_CURSPEED,
61 PCIEADM_SDO_CURWIDTH,
62 PCIEADM_SDO_SUPSPEEDS,
63 PCIEADM_SDO_SLOTNO,
64 PCIEADM_SDO_AP,
65 PCIEADM_SDO_CTLAP,
66 PCIEADM_SDO_LOC
67 } pcieadm_show_devs_otype_t;
68
69 typedef struct pcieadm_show_devs_ofmt {
70 int psdo_vid;
71 int psdo_did;
72 int psdo_rev;
73 int psdo_subvid;
74 int psdo_subsys;
75 uint_t psdo_bus;
76 uint_t psdo_dev;
77 uint_t psdo_func;
78 const char *psdo_path;
79 const char *psdo_vendor;
80 const char *psdo_device;
81 const char *psdo_subvendor;
82 const char *psdo_subsystem;
83 const char *psdo_driver;
84 int psdo_instance;
85 int psdo_mwidth;
86 int psdo_cwidth;
87 int64_t psdo_mspeed;
88 int64_t psdo_cspeed;
89 int psdo_nspeeds;
90 int64_t *psdo_sspeeds;
91 int32_t psdo_slotno;
92 char *psdo_ap;
93 char *psdo_ctlap;
94 char *psdo_loc;
95 } pcieadm_show_devs_ofmt_t;
96
97 static uint_t
pcieadm_speed2gen(int64_t speed)98 pcieadm_speed2gen(int64_t speed)
99 {
100 if (speed == 2500000000LL) {
101 return (1);
102 } else if (speed == 5000000000LL) {
103 return (2);
104 } else if (speed == 8000000000LL) {
105 return (3);
106 } else if (speed == 16000000000LL) {
107 return (4);
108 } else if (speed == 32000000000LL) {
109 return (5);
110 } else {
111 return (0);
112 }
113 }
114
115 static const char *
pcieadm_speed2str(int64_t speed)116 pcieadm_speed2str(int64_t speed)
117 {
118 if (speed == 2500000000LL) {
119 return ("2.5");
120 } else if (speed == 5000000000LL) {
121 return ("5.0");
122 } else if (speed == 8000000000LL) {
123 return ("8.0");
124 } else if (speed == 16000000000LL) {
125 return ("16.0");
126 } else if (speed == 32000000000LL) {
127 return ("32.0");
128 } else {
129 return (NULL);
130 }
131 }
132
133 static boolean_t
pcieadm_show_devs_ofmt_cb(ofmt_arg_t * ofarg,char * buf,uint_t buflen)134 pcieadm_show_devs_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
135 {
136 const char *str;
137 pcieadm_show_devs_ofmt_t *psdo = ofarg->ofmt_cbarg;
138 boolean_t first = B_TRUE;
139 size_t ret;
140
141 switch (ofarg->ofmt_id) {
142 case PCIEADM_SDO_BDF:
143 ret = snprintf(buf, buflen, "%x/%x/%x", psdo->psdo_bus,
144 psdo->psdo_dev, psdo->psdo_func);
145 break;
146 case PCIEADM_SDO_BDF_BUS:
147 ret = snprintf(buf, buflen, "%x", psdo->psdo_bus);
148 break;
149 case PCIEADM_SDO_BDF_DEV:
150 ret = snprintf(buf, buflen, "%x", psdo->psdo_dev);
151 break;
152 case PCIEADM_SDO_BDF_FUNC:
153 ret = snprintf(buf, buflen, "%x", psdo->psdo_func);
154 break;
155 case PCIEADM_SDO_INSTANCE:
156 if (psdo->psdo_driver == NULL || psdo->psdo_instance == -1) {
157 ret = snprintf(buf, buflen, "--");
158 } else {
159 ret = snprintf(buf, buflen, "%s%d", psdo->psdo_driver,
160 psdo->psdo_instance);
161 }
162 break;
163 case PCIEADM_SDO_DRIVER:
164 if (psdo->psdo_driver == NULL) {
165 ret = snprintf(buf, buflen, "--");
166 } else {
167 ret = strlcpy(buf, psdo->psdo_driver, buflen);
168 }
169 break;
170 case PCIEADM_SDO_INSTNUM:
171 if (psdo->psdo_instance == -1) {
172 ret = snprintf(buf, buflen, "--");
173 } else {
174 ret = snprintf(buf, buflen, "%d", psdo->psdo_instance);
175 }
176 break;
177 case PCIEADM_SDO_PATH:
178 ret = strlcat(buf, psdo->psdo_path, buflen);
179 break;
180 case PCIEADM_SDO_VID:
181 if (psdo->psdo_vid == -1) {
182 ret = strlcat(buf, "--", buflen);
183 } else {
184 ret = snprintf(buf, buflen, "%x", psdo->psdo_vid);
185 }
186 break;
187 case PCIEADM_SDO_DID:
188 if (psdo->psdo_did == -1) {
189 ret = strlcat(buf, "--", buflen);
190 } else {
191 ret = snprintf(buf, buflen, "%x", psdo->psdo_did);
192 }
193 break;
194 case PCIEADM_SDO_REV:
195 if (psdo->psdo_rev == -1) {
196 ret = strlcat(buf, "--", buflen);
197 } else {
198 ret = snprintf(buf, buflen, "%x", psdo->psdo_rev);
199 }
200 break;
201 case PCIEADM_SDO_SUBVID:
202 if (psdo->psdo_subvid == -1) {
203 ret = strlcat(buf, "--", buflen);
204 } else {
205 ret = snprintf(buf, buflen, "%x", psdo->psdo_subvid);
206 }
207 break;
208 case PCIEADM_SDO_SUBSYS:
209 if (psdo->psdo_subsys == -1) {
210 ret = strlcat(buf, "--", buflen);
211 } else {
212 ret = snprintf(buf, buflen, "%x", psdo->psdo_subsys);
213 }
214 break;
215 case PCIEADM_SDO_VENDOR:
216 ret = strlcat(buf, psdo->psdo_vendor, buflen);
217 break;
218 case PCIEADM_SDO_DEVICE:
219 ret = strlcat(buf, psdo->psdo_device, buflen);
220 break;
221 case PCIEADM_SDO_SUBVENDOR:
222 ret = strlcat(buf, psdo->psdo_subvendor, buflen);
223 break;
224 case PCIEADM_SDO_SUBSYSTEM:
225 ret = strlcat(buf, psdo->psdo_subsystem, buflen);
226 break;
227 case PCIEADM_SDO_MAXWIDTH:
228 if (psdo->psdo_mwidth <= 0) {
229 ret = strlcat(buf, "--", buflen);
230 } else {
231 ret = snprintf(buf, buflen, "x%d", psdo->psdo_mwidth);
232 }
233 break;
234 case PCIEADM_SDO_CURWIDTH:
235 if (psdo->psdo_cwidth <= 0) {
236 ret = strlcat(buf, "--", buflen);
237 } else {
238 ret = snprintf(buf, buflen, "x%d", psdo->psdo_cwidth);
239 }
240 break;
241 case PCIEADM_SDO_MAXSPEED:
242 str = pcieadm_speed2str(psdo->psdo_mspeed);
243 if (str == NULL) {
244 ret = strlcat(buf, "--", buflen);
245 } else {
246 ret = snprintf(buf, buflen, "%s GT/s", str);
247 }
248 break;
249 case PCIEADM_SDO_CURSPEED:
250 str = pcieadm_speed2str(psdo->psdo_cspeed);
251 if (str == NULL) {
252 ret = strlcat(buf, "--", buflen);
253 } else {
254 ret = snprintf(buf, buflen, "%s GT/s", str);
255 }
256 break;
257 case PCIEADM_SDO_SUPSPEEDS:
258 buf[0] = 0;
259 ret = 0;
260 for (int i = 0; i < psdo->psdo_nspeeds; i++) {
261 const char *str;
262
263 str = pcieadm_speed2str(psdo->psdo_sspeeds[i]);
264 if (str == NULL) {
265 continue;
266 }
267
268 if (!first) {
269 ret = strlcat(buf, ",", buflen);
270 }
271
272 first = B_FALSE;
273 ret = strlcat(buf, str, buflen);
274 }
275 break;
276 case PCIEADM_SDO_TYPE:
277 /*
278 * We need to distinguish three different groups of things here:
279 *
280 * - Something is a PCI device.
281 * - We have a PCIe device where the link is down and therefore
282 * have no current width or speed.
283 * - We have a PCIe device which is up.
284 *
285 * A PCIe device should always have a maximum width value. This
286 * is required and therefore should be a good proxy for whether
287 * or not we have a PCIe device.
288 */
289 if (psdo->psdo_mwidth == -1) {
290 ret = strlcat(buf, "PCI", buflen);
291 break;
292 }
293
294 /*
295 * If we don't have a valid link up, indicate we don't know.
296 * While the link is probably down, we don't have that as a
297 * guarantee right now.
298 */
299 if (psdo->psdo_cspeed == -1 || psdo->psdo_cwidth == -1) {
300 ret = strlcat(buf, "PCIe unknown", buflen);
301 break;
302 }
303
304 ret = snprintf(buf, buflen, "PCIe Gen %ux%d",
305 pcieadm_speed2gen(psdo->psdo_cspeed), psdo->psdo_cwidth);
306 break;
307 case PCIEADM_SDO_SLOTNO:
308 if (psdo->psdo_slotno < 0) {
309 ret = strlcat(buf, "--", buflen);
310 } else {
311 ret = snprintf(buf, buflen, "0x%x", psdo->psdo_slotno);
312 }
313 break;
314 case PCIEADM_SDO_AP:
315 ret = strlcat(buf, psdo->psdo_ap != NULL ? psdo->psdo_ap : "--",
316 buflen);
317 break;
318 case PCIEADM_SDO_CTLAP:
319 ret = strlcat(buf, psdo->psdo_ctlap != NULL ? psdo->psdo_ctlap :
320 "--", buflen);
321 break;
322 case PCIEADM_SDO_LOC:
323 ret = strlcat(buf, psdo->psdo_loc != NULL ? psdo->psdo_loc :
324 "--", buflen);
325 break;
326 default:
327 abort();
328 }
329
330 /*
331 * While the str* functions can't really fail, snprintf() returns an
332 * int. If it fails, that negative value will be cast up to UINT64_MAX
333 * (e.g. (size_t)-1), which will cause the check to always fail.
334 */
335 return (ret < buflen);
336 }
337
338 static const char *pcieadm_show_dev_fields = "bdf,type,instance,device";
339 static const char *pcieadm_show_dev_speeds =
340 "bdf,driver,maxspeed,curspeed,maxwidth,curwidth,supspeeds";
341 static const ofmt_field_t pcieadm_show_dev_ofmt[] = {
342 { "VID", 6, PCIEADM_SDO_VID, pcieadm_show_devs_ofmt_cb },
343 { "DID", 6, PCIEADM_SDO_DID, pcieadm_show_devs_ofmt_cb },
344 { "REV", 6, PCIEADM_SDO_REV, pcieadm_show_devs_ofmt_cb },
345 { "SUBVID", 6, PCIEADM_SDO_SUBVID, pcieadm_show_devs_ofmt_cb },
346 { "SUBSYS", 6, PCIEADM_SDO_SUBSYS, pcieadm_show_devs_ofmt_cb },
347 { "BDF", 8, PCIEADM_SDO_BDF, pcieadm_show_devs_ofmt_cb },
348 { "DRIVER", 15, PCIEADM_SDO_DRIVER, pcieadm_show_devs_ofmt_cb },
349 { "INSTANCE", 15, PCIEADM_SDO_INSTANCE, pcieadm_show_devs_ofmt_cb },
350 { "INSTNUM", 8, PCIEADM_SDO_INSTNUM, pcieadm_show_devs_ofmt_cb },
351 { "TYPE", 15, PCIEADM_SDO_TYPE, pcieadm_show_devs_ofmt_cb },
352 { "VENDOR", 30, PCIEADM_SDO_VENDOR, pcieadm_show_devs_ofmt_cb },
353 { "DEVICE", 30, PCIEADM_SDO_DEVICE, pcieadm_show_devs_ofmt_cb },
354 { "SUBVENDOR", 30, PCIEADM_SDO_SUBVENDOR, pcieadm_show_devs_ofmt_cb },
355 { "SUBSYSTEM", 30, PCIEADM_SDO_SUBSYSTEM, pcieadm_show_devs_ofmt_cb },
356 { "PATH", 30, PCIEADM_SDO_PATH, pcieadm_show_devs_ofmt_cb },
357 { "BUS", 4, PCIEADM_SDO_BDF_BUS, pcieadm_show_devs_ofmt_cb },
358 { "DEV", 4, PCIEADM_SDO_BDF_DEV, pcieadm_show_devs_ofmt_cb },
359 { "FUNC", 4, PCIEADM_SDO_BDF_FUNC, pcieadm_show_devs_ofmt_cb },
360 { "MAXSPEED", 10, PCIEADM_SDO_MAXSPEED, pcieadm_show_devs_ofmt_cb },
361 { "MAXWIDTH", 10, PCIEADM_SDO_MAXWIDTH, pcieadm_show_devs_ofmt_cb },
362 { "CURSPEED", 10, PCIEADM_SDO_CURSPEED, pcieadm_show_devs_ofmt_cb },
363 { "CURWIDTH", 10, PCIEADM_SDO_CURWIDTH, pcieadm_show_devs_ofmt_cb },
364 { "SUPSPEEDS", 20, PCIEADM_SDO_SUPSPEEDS, pcieadm_show_devs_ofmt_cb },
365 { "SLOTNO", 8, PCIEADM_SDO_SLOTNO, pcieadm_show_devs_ofmt_cb },
366 { "AP", 10, PCIEADM_SDO_AP, pcieadm_show_devs_ofmt_cb },
367 { "CTLAP", 10, PCIEADM_SDO_CTLAP, pcieadm_show_devs_ofmt_cb },
368 { NULL, 0, 0, NULL }
369 };
370
371 /*
372 * This is a variant of the above fields when -L is in use. We don't always want
373 * to take a topo snapshot to show devices so these fields are only available
374 * when requested.
375 */
376 static const char *pcieadm_show_dev_loc =
377 "bdf,instance,location,slotno,ap,ctlap";
378 static const ofmt_field_t pcieadm_show_dev_loc_ofmt[] = {
379 { "VID", 6, PCIEADM_SDO_VID, pcieadm_show_devs_ofmt_cb },
380 { "DID", 6, PCIEADM_SDO_DID, pcieadm_show_devs_ofmt_cb },
381 { "REV", 6, PCIEADM_SDO_REV, pcieadm_show_devs_ofmt_cb },
382 { "SUBVID", 6, PCIEADM_SDO_SUBVID, pcieadm_show_devs_ofmt_cb },
383 { "SUBSYS", 6, PCIEADM_SDO_SUBSYS, pcieadm_show_devs_ofmt_cb },
384 { "BDF", 8, PCIEADM_SDO_BDF, pcieadm_show_devs_ofmt_cb },
385 { "DRIVER", 15, PCIEADM_SDO_DRIVER, pcieadm_show_devs_ofmt_cb },
386 { "INSTANCE", 15, PCIEADM_SDO_INSTANCE, pcieadm_show_devs_ofmt_cb },
387 { "INSTNUM", 8, PCIEADM_SDO_INSTNUM, pcieadm_show_devs_ofmt_cb },
388 { "TYPE", 15, PCIEADM_SDO_TYPE, pcieadm_show_devs_ofmt_cb },
389 { "VENDOR", 30, PCIEADM_SDO_VENDOR, pcieadm_show_devs_ofmt_cb },
390 { "DEVICE", 30, PCIEADM_SDO_DEVICE, pcieadm_show_devs_ofmt_cb },
391 { "SUBVENDOR", 30, PCIEADM_SDO_SUBVENDOR, pcieadm_show_devs_ofmt_cb },
392 { "SUBSYSTEM", 30, PCIEADM_SDO_SUBSYSTEM, pcieadm_show_devs_ofmt_cb },
393 { "PATH", 30, PCIEADM_SDO_PATH, pcieadm_show_devs_ofmt_cb },
394 { "BUS", 4, PCIEADM_SDO_BDF_BUS, pcieadm_show_devs_ofmt_cb },
395 { "DEV", 4, PCIEADM_SDO_BDF_DEV, pcieadm_show_devs_ofmt_cb },
396 { "FUNC", 4, PCIEADM_SDO_BDF_FUNC, pcieadm_show_devs_ofmt_cb },
397 { "MAXSPEED", 10, PCIEADM_SDO_MAXSPEED, pcieadm_show_devs_ofmt_cb },
398 { "MAXWIDTH", 10, PCIEADM_SDO_MAXWIDTH, pcieadm_show_devs_ofmt_cb },
399 { "CURSPEED", 10, PCIEADM_SDO_CURSPEED, pcieadm_show_devs_ofmt_cb },
400 { "CURWIDTH", 10, PCIEADM_SDO_CURWIDTH, pcieadm_show_devs_ofmt_cb },
401 { "SUPSPEEDS", 20, PCIEADM_SDO_SUPSPEEDS, pcieadm_show_devs_ofmt_cb },
402 { "SLOTNO", 8, PCIEADM_SDO_SLOTNO, pcieadm_show_devs_ofmt_cb },
403 { "AP", 10, PCIEADM_SDO_AP, pcieadm_show_devs_ofmt_cb },
404 { "CTLAP", 10, PCIEADM_SDO_CTLAP, pcieadm_show_devs_ofmt_cb },
405 { "LOCATION", 15, PCIEADM_SDO_LOC, pcieadm_show_devs_ofmt_cb },
406 { NULL, 0, 0, NULL }
407 };
408
409 static boolean_t
pcieadm_show_devs_match(pcieadm_show_devs_t * psd,pcieadm_show_devs_ofmt_t * psdo)410 pcieadm_show_devs_match(pcieadm_show_devs_t *psd,
411 pcieadm_show_devs_ofmt_t *psdo)
412 {
413 char dinst[128], bdf[128];
414 boolean_t match = B_FALSE;
415
416 if (psd->psd_nfilts == 0) {
417 return (B_TRUE);
418 }
419
420 if (psdo->psdo_driver != NULL && psdo->psdo_instance != -1) {
421 (void) snprintf(dinst, sizeof (dinst), "%s%d",
422 psdo->psdo_driver, psdo->psdo_instance);
423 }
424 (void) snprintf(bdf, sizeof (bdf), "%x/%x/%x", psdo->psdo_bus,
425 psdo->psdo_dev, psdo->psdo_func);
426
427 for (uint_t i = 0; i < psd->psd_nfilts; i++) {
428 const char *filt = psd->psd_filts[i];
429
430 if (strcmp(filt, psdo->psdo_path) == 0) {
431 psd->psd_used[i] = B_TRUE;
432 match = B_TRUE;
433 continue;
434 }
435
436 if (strcmp(filt, bdf) == 0) {
437 psd->psd_used[i] = B_TRUE;
438 match = B_TRUE;
439 continue;
440 }
441
442 if (psdo->psdo_driver != NULL &&
443 strcmp(filt, psdo->psdo_driver) == 0) {
444 psd->psd_used[i] = B_TRUE;
445 match = B_TRUE;
446 continue;
447 }
448
449 if (psdo->psdo_driver != NULL && psdo->psdo_instance != -1 &&
450 strcmp(filt, dinst) == 0) {
451 psd->psd_used[i] = B_TRUE;
452 match = B_TRUE;
453 continue;
454 }
455
456 if (strncmp("/devices", filt, strlen("/devices")) == 0) {
457 filt += strlen("/devices");
458 }
459
460 if (strcmp(filt, psdo->psdo_path) == 0) {
461 psd->psd_used[i] = B_TRUE;
462 match = B_TRUE;
463 continue;
464 }
465
466 if (psdo->psdo_loc != NULL && strcmp(filt, psdo->psdo_loc) ==
467 0) {
468 psd->psd_used[i] = B_TRUE;
469 match = B_TRUE;
470 continue;
471 }
472 }
473
474 return (match);
475 }
476
477 /*
478 * Check if there is an attachment point on this node that we know how to
479 * display, which today is generally in the PCIe style where there is only a
480 * single entry in the bitfield.
481 */
482 static void
pcieadm_show_devs_ap(di_node_t node,char ** outp)483 pcieadm_show_devs_ap(di_node_t node, char **outp)
484 {
485 int *ap_names, *slot_names;
486
487 /*
488 * The AP names property is a bitfield that indicates which entry in the
489 * slot-names. A given device can in theory support multiples lots, but
490 * PCIe is always going to be a value of 1.
491 */
492 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "ap-names", &ap_names) !=
493 1 || *ap_names != 1) {
494 return;
495 }
496
497 /*
498 * The slot-names property is structured with the first word as a
499 * bitfield mask and then a series of names which are guaranteed to be
500 * NULL terminated.
501 */
502 int nu32 = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "slot-names",
503 &slot_names);
504 if (nu32 <= 1 || slot_names[0] != 1) {
505 return;
506 }
507
508 *outp = calloc(nu32 - 1, sizeof (uint32_t));
509 if (*outp == NULL) {
510 err(EXIT_FAILURE, "failed to allocate memory for AP name");
511 }
512
513 (void) memcpy(*outp, &slot_names[1], sizeof (uint32_t) * (nu32 - 1));
514 }
515
516 /*
517 * Walk through the 'pcie' schema tree to try to find a matching entry. There is
518 * not a header file with node names, so we hard code these here for now. We
519 * start this by walking function nodes and matching them with our device via
520 * the b/d/f.
521 */
522 static int
pcieadm_show_devs_loc_pcie(topo_hdl_t * hp,tnode_t * tn,void * arg)523 pcieadm_show_devs_loc_pcie(topo_hdl_t *hp, tnode_t *tn, void *arg)
524 {
525 pcieadm_show_devs_ofmt_t *oarg = arg;
526 uint32_t bus, dev, func;
527 char *label, *type;
528 int err;
529
530 if (strcmp(topo_node_name(tn), "function") != 0) {
531 return (TOPO_WALK_NEXT);
532 }
533
534 if (topo_prop_get_uint32(tn, TOPO_PCIE_PGROUP_PCI_CFG,
535 TOPO_PCIE_PCI_BUS, &bus, &err) != 0 ||
536 topo_prop_get_uint32(tn, TOPO_PCIE_PGROUP_PCI_CFG,
537 TOPO_PCIE_PCI_DEVICE, &dev, &err) != 0 ||
538 topo_prop_get_uint32(tn, TOPO_PCIE_PGROUP_PCI_CFG,
539 TOPO_PCIE_PCI_FUNCTION, &func, &err) != 0 ||
540 bus != oarg->psdo_bus || dev != oarg->psdo_dev ||
541 func != oarg->psdo_func) {
542 return (TOPO_WALK_NEXT);
543 }
544
545 /*
546 * Now that we know that we have a matching device, we have the trickier
547 * part of finding a matching label. In general, ports are labeled. From
548 * a function, we know that our parent is going to either be a
549 * root-complex or a device. If it's a device, we'll look for a
550 * port/link/port pair and see if we can find a label on the upstream
551 * port. If our parent is a root-complex, then we will see if the parent
552 * CPU has a label.
553 *
554 * However, if we're a root-port, then we need to look at our port child
555 * to see what the most useful label is. Technically a root-port is
556 * really part of the root-complex/CPU and that's the more accurate
557 * label; however, most folks are looking at this so they can figure out
558 * what this corresponds to.
559 *
560 * We'll look for labels at the different hops along the way in case
561 * things change in this regard in the future.
562 */
563 if (topo_prop_get_string(tn, TOPO_PGROUP_PROTOCOL, TOPO_PROP_LABEL,
564 &label, &err) == 0) {
565 oarg->psdo_loc = label;
566 return (TOPO_WALK_TERMINATE);
567 }
568
569 if (topo_prop_get_string(tn, TOPO_PCIE_PGROUP_PCI, TOPO_PCIE_PCI_TYPE,
570 &type, &err) != 0) {
571 return (TOPO_WALK_TERMINATE);
572 }
573
574 if (strcmp(type, "root-port") == 0) {
575 tnode_t *port = topo_node_lookup(tn, "port", 0);
576 if (port != NULL) {
577 if (topo_prop_get_string(port, TOPO_PGROUP_PROTOCOL,
578 TOPO_PROP_LABEL, &label, &err) == 0) {
579 topo_hdl_strfree(hp, type);
580 oarg->psdo_loc = label;
581 return (TOPO_WALK_TERMINATE);
582 }
583 }
584 }
585 topo_hdl_strfree(hp, type);
586
587 const char *pname = topo_node_name(topo_node_parent(tn));
588 const char *final;
589 uint32_t fcount, count = 0;
590 if (strcmp(pname, "device") == 0) {
591 final = "port";
592 fcount = 2;
593 } else if (strcmp(pname, "root-complex") == 0) {
594 final = "cpu";
595 fcount = 1;
596 } else {
597 /*
598 * We don't know where we are so we're done.
599 */
600 return (TOPO_WALK_TERMINATE);
601 }
602
603 for (tnode_t *pn = topo_node_parent(tn); pn != NULL;
604 pn = topo_node_parent(pn)) {
605 if (topo_prop_get_string(pn, TOPO_PGROUP_PROTOCOL,
606 TOPO_PROP_LABEL, &label, &err) == 0) {
607 oarg->psdo_loc = label;
608 return (TOPO_WALK_TERMINATE);
609 }
610
611 if (strcmp(topo_node_name(pn), final) == 0) {
612 count++;
613 if (fcount == count) {
614 break;
615 }
616 }
617 }
618
619 return (TOPO_WALK_TERMINATE);
620 }
621
622 /*
623 * Walk the 'pcie' tree to find a matching function. From there, we walk up a
624 * bit of the tree to find the relevant label to use. We prefer the pcie tree
625 * to the 'hc' tree as some versions of the 'hc' tree don't call out every
626 * single device.
627 */
628 static void
pcieadm_show_devs_loc(topo_hdl_t * thp,di_node_t node,pcieadm_show_devs_ofmt_t * oarg)629 pcieadm_show_devs_loc(topo_hdl_t *thp, di_node_t node,
630 pcieadm_show_devs_ofmt_t *oarg)
631 {
632 topo_walk_t *wp;
633 int err;
634
635 wp = topo_walk_init(thp, FM_FMRI_SCHEME_PCIE,
636 pcieadm_show_devs_loc_pcie, oarg, &err);
637 if (wp == NULL) {
638 /*
639 * It might be nice to at some point swallow this and attempt to
640 * fall back to 'hc', but we would need to get error values into
641 * a header that makes it into the proto area.
642 */
643 errx(EXIT_FAILURE, "failed to initialize topology walk: %s",
644 topo_strerror(err));
645 }
646
647 err = topo_walk_step(wp, TOPO_WALK_SIBLING);
648 if (err == TOPO_WALK_ERR) {
649 errx(EXIT_FAILURE, "failed to perform topology walk");
650 }
651
652 topo_walk_fini(wp);
653 }
654
655 static int
pcieadm_show_devs_walk_cb(di_node_t node,void * arg)656 pcieadm_show_devs_walk_cb(di_node_t node, void *arg)
657 {
658 int nprop, *regs = NULL, *did, *vid, *mwidth, *cwidth, *rev;
659 int *subvid, *subsys, *slotno;
660 int64_t *mspeed, *cspeed, *sspeeds;
661 char *path = NULL;
662 pcieadm_show_devs_t *psd = arg;
663 int ret = DI_WALK_CONTINUE;
664 char venstr[64], devstr[64], subvenstr[64], subsysstr[64];
665 pcieadm_show_devs_ofmt_t oarg;
666 pcidb_hdl_t *pcidb = psd->psd_pia->pia_pcidb;
667
668 bzero(&oarg, sizeof (oarg));
669
670 path = di_devfs_path(node);
671 if (path == NULL) {
672 err(EXIT_FAILURE, "failed to construct devfs path for node: "
673 "%s", di_node_name(node));
674 }
675
676 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg", ®s);
677 if (nprop <= 0) {
678 errx(EXIT_FAILURE, "failed to lookup regs array for %s",
679 path);
680 }
681
682 oarg.psdo_path = path;
683 oarg.psdo_bus = PCI_REG_BUS_G(regs[0]);
684 oarg.psdo_dev = PCI_REG_DEV_G(regs[0]);
685 oarg.psdo_func = PCI_REG_FUNC_G(regs[0]);
686
687 if (oarg.psdo_func != 0 && !psd->psd_funcs) {
688 goto done;
689 }
690
691 oarg.psdo_driver = di_driver_name(node);
692 oarg.psdo_instance = di_instance(node);
693
694 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &did);
695 if (nprop != 1) {
696 oarg.psdo_did = -1;
697 } else {
698 oarg.psdo_did = (uint16_t)*did;
699 }
700
701 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vid);
702 if (nprop != 1) {
703 oarg.psdo_vid = -1;
704 } else {
705 oarg.psdo_vid = (uint16_t)*vid;
706 }
707
708 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "revision-id", &rev);
709 if (nprop != 1) {
710 oarg.psdo_rev = -1;
711 } else {
712 oarg.psdo_rev = (uint16_t)*rev;
713 }
714
715 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "subsystem-vendor-id",
716 &subvid);
717 if (nprop != 1) {
718 oarg.psdo_subvid = -1;
719 } else {
720 oarg.psdo_subvid = (uint16_t)*subvid;
721 }
722
723 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "subsystem-id",
724 &subsys);
725 if (nprop != 1) {
726 oarg.psdo_subsys = -1;
727 } else {
728 oarg.psdo_subsys = (uint16_t)*subsys;
729 }
730
731 oarg.psdo_vendor = "--";
732 if (oarg.psdo_vid != -1) {
733 pcidb_vendor_t *vend = pcidb_lookup_vendor(pcidb,
734 oarg.psdo_vid);
735 if (vend != NULL) {
736 oarg.psdo_vendor = pcidb_vendor_name(vend);
737 } else {
738 (void) snprintf(venstr, sizeof (venstr),
739 "Unknown vendor: 0x%x", oarg.psdo_vid);
740 oarg.psdo_vendor = venstr;
741 }
742 }
743
744 oarg.psdo_device = "--";
745 if (oarg.psdo_vid != -1 && oarg.psdo_did != -1) {
746 pcidb_device_t *dev = pcidb_lookup_device(pcidb,
747 oarg.psdo_vid, oarg.psdo_did);
748 if (dev != NULL) {
749 oarg.psdo_device = pcidb_device_name(dev);
750 } else {
751 (void) snprintf(devstr, sizeof (devstr),
752 "Unknown device: 0x%x", oarg.psdo_did);
753 oarg.psdo_device = devstr;
754 }
755 }
756
757 /*
758 * The pci.ids database organizes subsystems under devices. We look at
759 * the subsystem vendor separately because even if the device or other
760 * information is not known, we may still be able to figure out what it
761 * is.
762 */
763 oarg.psdo_subvendor = "--";
764 oarg.psdo_subsystem = "--";
765 if (oarg.psdo_subvid != -1) {
766 pcidb_vendor_t *vend = pcidb_lookup_vendor(pcidb,
767 oarg.psdo_subvid);
768 if (vend != NULL) {
769 oarg.psdo_subvendor = pcidb_vendor_name(vend);
770 } else {
771 (void) snprintf(subvenstr, sizeof (subvenstr),
772 "Unknown vendor: 0x%x", oarg.psdo_vid);
773 oarg.psdo_subvendor = subvenstr;
774 }
775 }
776
777 if (oarg.psdo_vid != -1 && oarg.psdo_did != -1 &&
778 oarg.psdo_subvid != -1 && oarg.psdo_subsys != -1) {
779 pcidb_subvd_t *subvd = pcidb_lookup_subvd(pcidb, oarg.psdo_vid,
780 oarg.psdo_did, oarg.psdo_subvid, oarg.psdo_subsys);
781 if (subvd != NULL) {
782 oarg.psdo_subsystem = pcidb_subvd_name(subvd);
783 } else {
784 (void) snprintf(subsysstr, sizeof (subsysstr),
785 "Unknown subsystem: 0x%x", oarg.psdo_subsys);
786 oarg.psdo_subsystem = subsysstr;
787 }
788 }
789
790
791 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node,
792 "pcie-link-maximum-width", &mwidth);
793 if (nprop != 1) {
794 oarg.psdo_mwidth = -1;
795 } else {
796 oarg.psdo_mwidth = *mwidth;
797 }
798
799 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node,
800 "pcie-link-current-width", &cwidth);
801 if (nprop != 1) {
802 oarg.psdo_cwidth = -1;
803 } else {
804 oarg.psdo_cwidth = *cwidth;
805 }
806
807 nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node,
808 "pcie-link-maximum-speed", &mspeed);
809 if (nprop != 1) {
810 oarg.psdo_mspeed = -1;
811 } else {
812 oarg.psdo_mspeed = *mspeed;
813 }
814
815 nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node,
816 "pcie-link-current-speed", &cspeed);
817 if (nprop != 1) {
818 oarg.psdo_cspeed = -1;
819 } else {
820 oarg.psdo_cspeed = *cspeed;
821 }
822
823 nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node,
824 "pcie-link-supported-speeds", &sspeeds);
825 if (nprop > 0) {
826 oarg.psdo_nspeeds = nprop;
827 oarg.psdo_sspeeds = sspeeds;
828 } else {
829 oarg.psdo_nspeeds = 0;
830 oarg.psdo_sspeeds = NULL;
831 }
832
833 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node,
834 "physical-slot#", &slotno);
835 if (nprop != 1) {
836 oarg.psdo_slotno = -1;
837 } else {
838 oarg.psdo_slotno = *slotno;
839 }
840
841 /*
842 * Look for the AP and if the parent is an instance of pcieb, check it
843 * for the controlling AP.
844 */
845 pcieadm_show_devs_ap(node, &oarg.psdo_ap);
846 di_node_t pdip = di_parent_node(node);
847 const char *pdrv = di_driver_name(pdip);
848 if (pdrv != NULL && strcmp(pdrv, "pcieb") == 0) {
849 pcieadm_show_devs_ap(pdip, &oarg.psdo_ctlap);
850 }
851
852 /*
853 * If we have a topo handle, e.g. -L was passed, look to see if we can
854 * cons up a label for this.
855 */
856 if (psd->psd_topo != NULL) {
857 pcieadm_show_devs_loc(psd->psd_topo, node, &oarg);
858 }
859
860 if (pcieadm_show_devs_match(psd, &oarg)) {
861 ofmt_print(psd->psd_ofmt, &oarg);
862 psd->psd_nprint++;
863 }
864
865 done:
866 if (psd->psd_topo != NULL) {
867 topo_hdl_strfree(psd->psd_topo, oarg.psdo_loc);
868 }
869 free(oarg.psdo_ctlap);
870 free(oarg.psdo_ap);
871 if (path != NULL) {
872 di_devfs_path_free(path);
873 }
874
875 return (ret);
876 }
877
878 void
pcieadm_show_devs_usage(FILE * f)879 pcieadm_show_devs_usage(FILE *f)
880 {
881 (void) fprintf(f, "\tshow-devs\t[-F] [-H] [-s | -o field[,...] [-p]] "
882 "[filter...]\n");
883 }
884
885 static void
pcieadm_show_devs_help(const char * fmt,...)886 pcieadm_show_devs_help(const char *fmt, ...)
887 {
888 if (fmt != NULL) {
889 va_list ap;
890
891 va_start(ap, fmt);
892 vwarnx(fmt, ap);
893 va_end(ap);
894 (void) fprintf(stderr, "\n");
895 }
896
897 (void) fprintf(stderr, "Usage: %s show-devs [-F] [-H] [-L | -s ] [-o "
898 "field[,...] [-p]] [filter...]\n", pcieadm_progname);
899
900 (void) fprintf(stderr, "\nList PCI devices and functions in the "
901 "system. Each <filter> selects a set\nof devices to show and "
902 "can be a driver name, instance, /devices path,\nb/d/f, or "
903 "location string (-L only).\n\n"
904 "\t-F\t\tdo not display PCI functions\n"
905 "\t-H\t\tomit the column header\n"
906 "\t-o field\toutput fields to print\n"
907 "\t-p\t\tparsable output (requires -o)\n"
908 "\t-s\t\tlist speeds and widths\n\n"
909 "The following fields are supported:\n"
910 "\tvid\t\tthe PCI vendor ID in hex\n"
911 "\tdid\t\tthe PCI device ID in hex\n"
912 "\trev\t\tthe PCI device revision in hex\n"
913 "\tsubvid\t\tthe PCI subsystem vendor ID in hex\n"
914 "\tsubsys\t\tthe PCI subsystem ID in hex\n"
915 "\tvendor\t\tthe name of the PCI vendor\n"
916 "\tdevice\t\tthe name of the PCI device\n"
917 "\tsubvendor\tthe name of the PCI subsystem vendor\n"
918 "\tsubsystem\tthe name of the PCI subsystem\n"
919 "\tinstance\tthe name of this particular instance, e.g. igb0\n"
920 "\tdriver\t\tthe name of the driver attached to the device\n"
921 "\tinstnum\t\tthe instance number of a device, e.g. 2 for nvme2\n"
922 "\tpath\t\tthe /devices path of the device\n"
923 "\tbdf\t\tthe PCI bus/device/function, with values in hex\n"
924 "\tbus\t\tthe PCI bus number of the device in hex\n"
925 "\tdev\t\tthe PCI device number of the device in hex\n"
926 "\tfunc\t\tthe PCI function number of the device in hex\n"
927 "\ttype\t\ta string describing the PCIe generation and width\n"
928 "\tmaxspeed\tthe maximum supported PCIe speed of the device\n"
929 "\tcurspeed\tthe current PCIe speed of the device\n"
930 "\tmaxwidth\tthe maximum supported PCIe lane count of the device\n"
931 "\tcurwidth\tthe current lane count of the PCIe device\n"
932 "\tsupspeeds\tthe list of speeds the device supports\n"
933 "\tslotno\t\tthe device's slot number\n"
934 "\tap\t\tthe attachment point provided by device\n"
935 "\tctlap\t\tthe controlling attachment point\n\n"
936 "The following fields are additionally available with -L:\n"
937 "\tlocation\tthe device's topology location string\n");
938 }
939
940 int
pcieadm_show_devs(pcieadm_t * pcip,int argc,char * argv[])941 pcieadm_show_devs(pcieadm_t *pcip, int argc, char *argv[])
942 {
943 int c, ret;
944 uint_t flags = 0;
945 const char *fields = NULL;
946 pcieadm_show_devs_t psd;
947 pcieadm_di_walk_t walk;
948 ofmt_status_t oferr;
949 const ofmt_field_t *ofmt;
950 boolean_t parse = B_FALSE;
951 boolean_t speeds = B_FALSE;
952 boolean_t loc = B_FALSE;
953
954 bzero(&psd, sizeof (psd));
955 psd.psd_pia = pcip;
956 psd.psd_funcs = B_TRUE;
957
958 while ((c = getopt(argc, argv, ":FHLo:ps")) != -1) {
959 switch (c) {
960 case 'F':
961 psd.psd_funcs = B_FALSE;
962 break;
963 case 'H':
964 flags |= OFMT_NOHEADER;
965 break;
966 case 'L':
967 loc = B_TRUE;
968 break;
969 case 'o':
970 fields = optarg;
971 break;
972 case 'p':
973 parse = B_TRUE;
974 flags |= OFMT_PARSABLE;
975 break;
976 case 's':
977 speeds = B_TRUE;
978 break;
979 case ':':
980 pcieadm_show_devs_help("option -%c requires an "
981 "argument", optopt);
982 exit(EXIT_USAGE);
983 case '?':
984 pcieadm_show_devs_help("unknown option: -%c", optopt);
985 exit(EXIT_USAGE);
986 }
987 }
988
989 if (speeds && loc) {
990 errx(EXIT_USAGE, "-L and -s cannot both be used");
991 }
992
993 if (parse && fields == NULL) {
994 errx(EXIT_USAGE, "-p requires fields specified with -o");
995 }
996
997 if (fields != NULL && speeds) {
998 errx(EXIT_USAGE, "-s cannot be used with with -o");
999 }
1000
1001 if (fields == NULL) {
1002 if (speeds) {
1003 fields = pcieadm_show_dev_speeds;
1004 } else if (loc) {
1005 fields = pcieadm_show_dev_loc;
1006 } else {
1007 fields = pcieadm_show_dev_fields;
1008 }
1009 }
1010
1011 if (loc) {
1012 ofmt = pcieadm_show_dev_loc_ofmt;
1013 } else {
1014 ofmt = pcieadm_show_dev_ofmt;
1015 }
1016
1017 argc -= optind;
1018 argv += optind;
1019
1020 if (argc > 0) {
1021 psd.psd_nfilts = argc;
1022 psd.psd_filts = argv;
1023 psd.psd_used = calloc(argc, sizeof (boolean_t));
1024 if (psd.psd_used == NULL) {
1025 err(EXIT_FAILURE, "failed to allocate filter tracking "
1026 "memory");
1027 }
1028 }
1029
1030 oferr = ofmt_open(fields, ofmt, flags, 0, &psd.psd_ofmt);
1031 ofmt_check(oferr, parse, psd.psd_ofmt, pcieadm_ofmt_errx, warnx);
1032
1033 if (loc) {
1034 /*
1035 * This helps with taking a topo snapshot reliably.
1036 */
1037 priv_fillset(pcip->pia_priv_eff);
1038 }
1039 pcieadm_init_privs(pcip);
1040
1041 /*
1042 * Before we perform our walk, if we have requested location information
1043 * raise privileges to take the snapshot.
1044 */
1045 if (loc) {
1046 int terr;
1047
1048 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_eff) !=
1049 0) {
1050 err(EXIT_FAILURE, "failed to raise privileges");
1051 }
1052
1053 psd.psd_topo = topo_open(TOPO_VERSION, NULL, &terr);
1054 if (psd.psd_topo == NULL) {
1055 errx(EXIT_FAILURE, "failed to obtain topo handle: %s",
1056 topo_strerror(terr));
1057 }
1058
1059 (void) topo_snap_hold(psd.psd_topo, NULL, &terr);
1060 if (terr != 0) {
1061 errx(EXIT_FAILURE, "failed to obtain topology "
1062 "snapshot: %s", topo_strerror(terr));
1063 }
1064
1065 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_min) !=
1066 0) {
1067 err(EXIT_FAILURE, "failed to reduce privileges");
1068 }
1069 }
1070
1071 walk.pdw_arg = &psd;
1072 walk.pdw_func = pcieadm_show_devs_walk_cb;
1073
1074 pcieadm_di_walk(pcip, &walk);
1075
1076 ret = EXIT_SUCCESS;
1077 for (int i = 0; i < psd.psd_nfilts; i++) {
1078 if (!psd.psd_used[i]) {
1079 warnx("filter '%s' did not match any devices",
1080 psd.psd_filts[i]);
1081 ret = EXIT_FAILURE;
1082 }
1083 }
1084
1085 if (psd.psd_nprint == 0) {
1086 ret = EXIT_FAILURE;
1087 }
1088
1089 if (psd.psd_topo != NULL) {
1090 topo_snap_release(psd.psd_topo);
1091 topo_close(psd.psd_topo);
1092 }
1093
1094 return (ret);
1095 }
1096