xref: /illumos-gate/usr/src/cmd/pcieadm/pcieadm_devs.c (revision a94efd7875d6cf7ca97bb9188beaed3bf1282a49)
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", &regs);
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