xref: /illumos-gate/usr/src/lib/fm/topo/modules/common/pcibus/did.c (revision 106e8bd44b02f4b8cd3c825790276c1c7081e67a)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2019 Joyent, Inc.
25  */
26 
27 /*
28  * did.c
29  *	The acronym did means "Dev-Info-Data".  Many properties and
30  *	characteristics of topology nodes are, with a bit of coaxing
31  *	derived from devinfo nodes.  These routines do some of the
32  *	derivation and also encapsulate the discoveries in did_t
33  *	structures that get associated with topology nodes as their
34  *	"private" data.
35  */
36 #include <alloca.h>
37 #include <assert.h>
38 #include <string.h>
39 #include <strings.h>
40 #include <sys/types.h>
41 #include <fm/topo_mod.h>
42 #include <libnvpair.h>
43 #include <libdevinfo.h>
44 #include <sys/pcie.h>
45 
46 #include <hostbridge.h>
47 #include <pcibus.h>
48 #include <did_props.h>
49 
50 #include "did_impl.h"
51 
52 static void slotnm_destroy(slotnm_t *);
53 
54 static slotnm_t *
slotnm_create(topo_mod_t * mp,int dev,char * str)55 slotnm_create(topo_mod_t *mp, int dev, char *str)
56 {
57 	slotnm_t *p;
58 
59 	if ((p = topo_mod_alloc(mp, sizeof (slotnm_t))) == NULL)
60 		return (NULL);
61 	p->snm_mod = mp;
62 	p->snm_next = NULL;
63 	p->snm_dev = dev;
64 	p->snm_name = topo_mod_strdup(mp, str);
65 	if (p->snm_name == NULL) {
66 		slotnm_destroy(p);
67 		return (NULL);
68 	}
69 	return (p);
70 }
71 
72 static void
slotnm_destroy(slotnm_t * p)73 slotnm_destroy(slotnm_t *p)
74 {
75 	if (p == NULL)
76 		return;
77 	slotnm_destroy(p->snm_next);
78 	if (p->snm_name != NULL)
79 		topo_mod_strfree(p->snm_mod, p->snm_name);
80 	topo_mod_free(p->snm_mod, p, sizeof (slotnm_t));
81 }
82 
83 static int
di_devtype_get(topo_mod_t * mp,di_node_t src,char ** devtype)84 di_devtype_get(topo_mod_t *mp, di_node_t src, char **devtype)
85 {
86 	int sz;
87 	uchar_t *buf;
88 
89 	/*
90 	 * For PCI the device type defined the type of device directly below.
91 	 * For PCIe RP and Switches, the device-type should be "pciex".  For
92 	 * PCIe-PCI and PCI-PCI bridges it should be "pci".  NICs = "network",
93 	 * Graphics = "display", etc..
94 	 */
95 	if (di_bytes_get(mp, src, DI_DEVTYPPROP, &sz, &buf) == 0) {
96 		*devtype = topo_mod_strdup(mp, (char *)buf);
97 	} else {
98 		*devtype = NULL;
99 	}
100 
101 	if (*devtype != NULL)
102 		return (0);
103 	return (-1);
104 }
105 
106 typedef struct smbios_slot_cb {
107 	int		cb_slotnum;
108 	int		cb_bdf;
109 	const char	*cb_label;
110 } smbios_slot_cb_t;
111 
112 static int
di_smbios_find_slot_by_bdf(smbios_hdl_t * shp,const smbios_struct_t * strp,void * data)113 di_smbios_find_slot_by_bdf(smbios_hdl_t *shp, const smbios_struct_t *strp,
114     void *data)
115 {
116 	smbios_slot_cb_t *cbp = data;
117 	smbios_slot_t slot;
118 	int bus, df;
119 
120 	bus = (cbp->cb_bdf & 0xFF00) >> 8;
121 	df = cbp->cb_bdf & 0xFF;
122 
123 	if (strp->smbstr_type != SMB_TYPE_SLOT ||
124 	    smbios_info_slot(shp, strp->smbstr_id, &slot) != 0)
125 		return (0);
126 
127 	if (slot.smbl_bus == bus && slot.smbl_df == df) {
128 		cbp->cb_label = slot.smbl_name;
129 		cbp->cb_slotnum = slot.smbl_id;
130 		return (1);
131 	}
132 
133 	return (0);
134 }
135 
136 static int
di_smbios_find_slot_by_id(smbios_hdl_t * shp,const smbios_struct_t * strp,void * data)137 di_smbios_find_slot_by_id(smbios_hdl_t *shp, const smbios_struct_t *strp,
138     void *data)
139 {
140 	smbios_slot_cb_t *cbp = data;
141 	smbios_slot_t slot;
142 
143 	if (strp->smbstr_type != SMB_TYPE_SLOT ||
144 	    smbios_info_slot(shp, strp->smbstr_id, &slot) != 0)
145 		return (0);
146 
147 	if (slot.smbl_id == cbp->cb_slotnum) {
148 		cbp->cb_label = slot.smbl_name;
149 		return (1);
150 	}
151 
152 	return (0);
153 }
154 
155 static int
di_physlotinfo_get(topo_mod_t * mp,di_node_t src,int bdf,int * slotnum,char ** slotname)156 di_physlotinfo_get(topo_mod_t *mp, di_node_t src, int bdf, int *slotnum,
157     char **slotname)
158 {
159 	char *slotbuf = NULL;
160 	int sz;
161 	uchar_t *buf;
162 	smbios_hdl_t *shp;
163 	boolean_t got_slotprop = B_FALSE;
164 
165 	*slotnum = -1;
166 
167 	(void) di_uintprop_get(mp, src, DI_PHYSPROP, (uint_t *)slotnum);
168 
169 	/*
170 	 * For PCI-Express, there is only one downstream device, so check for
171 	 * a slot-names property, and if it exists, ignore the slotmask value
172 	 * and use the string as the label.
173 	 */
174 	if (di_bytes_get(mp, src, DI_SLOTPROP, &sz, &buf) == 0 &&
175 	    sz > 4) {
176 		/*
177 		 * If there is a DI_SLOTPROP of the form SlotX (ie set up from
178 		 * the IRQ routing table) then trust that in preference to
179 		 * DI_PHYSPROP (which is set up from the PCIe slotcap reg).
180 		 */
181 		got_slotprop = B_TRUE;
182 		(void) sscanf((char *)&buf[4], "Slot%d", slotnum);
183 	}
184 
185 	/*
186 	 * If the system supports SMBIOS (virtual certainty on X86) then we will
187 	 * source the label from the Type 9 (Slot) records.  If we're unable
188 	 * to correlate the device with a slot record (as would happen with
189 	 * onboard PCIe devices), we return without setting slotname, which will
190 	 * ultimately result in the node inheriting the FRU label from its
191 	 * parent node.
192 	 *
193 	 * In the absence of any SMBIOS support (i.e. SPARC) then we will use
194 	 * the slot-names property, if available.  Otherwise we'll fall back
195 	 * to fabricating a label based on the slot number.
196 	 */
197 	if ((shp = topo_mod_smbios(mp)) != NULL) {
198 		/*
199 		 * The PCI spec describes slot number 0 as reserved for
200 		 * internal PCI devices.  Unfortunately, not all platforms
201 		 * respect this.  For that reason, we prefer to lookup the slot
202 		 * record using the device's BDF.  However, SMBIOS
203 		 * implementations prior to 2.6 don't encode the BDF in the
204 		 * slot record.  In that case we resort to looking up the
205 		 * slot record using the slot number.
206 		 */
207 		smbios_slot_cb_t cbdata;
208 		smbios_version_t smbv;
209 		boolean_t bdf_supp = B_TRUE;
210 
211 		cbdata.cb_slotnum = *slotnum;
212 		cbdata.cb_bdf = bdf;
213 		cbdata.cb_label = NULL;
214 
215 		/*
216 		 * The bus and device/fn payload members of the SMBIOS slot
217 		 * record were added in SMBIOS 2.6.
218 		 */
219 		smbios_info_smbios_version(shp, &smbv);
220 		if (smbv.smbv_major < 2 ||
221 		    (smbv.smbv_major == 2 && smbv.smbv_minor < 6)) {
222 			bdf_supp = B_FALSE;
223 		}
224 
225 		/*
226 		 * If the SMBIOS implementation is too old to look up the slot
227 		 * records by BDF and we weren't able to derive a slotnum then
228 		 * there is nothing we can do here.
229 		 */
230 		if (!bdf_supp && *slotnum == -1)
231 			return (0);
232 
233 		if (bdf_supp)
234 			(void) smbios_iter(shp, di_smbios_find_slot_by_bdf,
235 			    &cbdata);
236 		else
237 			(void) smbios_iter(shp, di_smbios_find_slot_by_id,
238 			    &cbdata);
239 
240 		if (cbdata.cb_label == NULL)
241 			return (0);
242 
243 		slotbuf = (char *)cbdata.cb_label;
244 		topo_mod_dprintf(mp, "%s: di_node=%p: using smbios name: %s\n",
245 		    __func__, src, slotbuf);
246 	} else if (got_slotprop) {
247 		slotbuf = (char *)&buf[4];
248 		topo_mod_dprintf(mp, "%s: di_node=%p: using %s property: %s\n",
249 		    __func__, src, DI_SLOTPROP, slotbuf);
250 	} else {
251 		/*
252 		 * Make generic description string "SLOT <num>", allow up to
253 		 * 10 digits for number.  Bail, if we weren't able to derive
254 		 * a slotnum.
255 		 */
256 		if (*slotnum == -1)
257 			return (0);
258 
259 		slotbuf = alloca(16);
260 		(void) snprintf(slotbuf, 16, "SLOT %d", *slotnum);
261 		topo_mod_dprintf(mp, "%s: di_node=%p: using fabricated slot "
262 		    "name: %s\n", __func__, src, slotbuf);
263 	}
264 
265 	if ((*slotname = topo_mod_strdup(mp, slotbuf)) == NULL) {
266 		/* topo errno set */
267 		return (-1);
268 	}
269 	return (0);
270 }
271 
272 static int
di_slotinfo_get(topo_mod_t * mp,di_node_t src,int * nslots,slotnm_t ** slotnames)273 di_slotinfo_get(topo_mod_t *mp, di_node_t src, int *nslots,
274     slotnm_t **slotnames)
275 {
276 	slotnm_t *lastslot = NULL;
277 	slotnm_t *newslot;
278 	uchar_t *slotbuf;
279 	uint_t slotmap = 0;
280 	char *slotname;
281 	int andbit;
282 	int sz = -1;
283 
284 	*slotnames = NULL;
285 	*nslots = 0;
286 	if (di_bytes_get(mp, src, DI_SLOTPROP, &sz, &slotbuf) < 0)
287 		return (0);
288 	if (sz < sizeof (uint_t))
289 		return (0);
290 	bcopy(slotbuf, &slotmap, sizeof (uint_t));
291 	if (slotmap == 0)
292 		return (0);
293 
294 	slotname = (char *)&slotbuf[4];
295 	for (andbit = 0; andbit < 32; andbit++) {
296 		if (slotmap & (1 << andbit)) {
297 			char *s = slotname;
298 			slotname += strlen(s) + 1;
299 			if ((newslot = slotnm_create(mp, andbit, s)) == NULL) {
300 				slotnm_destroy(*slotnames);
301 				*slotnames = NULL;
302 				*nslots = 0;
303 				return (-1);
304 			}
305 			if (lastslot == NULL)
306 				*slotnames = lastslot = newslot;
307 			else {
308 				lastslot->snm_next = newslot;
309 				lastslot = newslot;
310 			}
311 			(*nslots)++;
312 		}
313 	}
314 	return (0);
315 }
316 
317 int
did_physlot(did_t * did)318 did_physlot(did_t *did)
319 {
320 	assert(did != NULL);
321 	return (did->dp_physlot);
322 }
323 
324 int
did_physlot_exists(did_t * did)325 did_physlot_exists(did_t *did)
326 {
327 	assert(did != NULL);
328 	return ((did->dp_physlot >= 0) || (did->dp_nslots > 0));
329 }
330 
331 did_t *
did_create(topo_mod_t * mp,di_node_t src,int ibrd,int ibrdge,int irc,int ibus)332 did_create(topo_mod_t *mp, di_node_t src,
333     int ibrd, int ibrdge, int irc, int ibus)
334 {
335 	did_t *np;
336 	did_t *pd;
337 	uint_t code;
338 	uint_t reg;
339 
340 	if ((pd = did_hash_lookup(mp, src)) != NULL) {
341 		topo_mod_dprintf(mp, "Attempt to create existing did_t.\n");
342 		assert(ibus == TRUST_BDF || (pd->dp_bus == ibus));
343 		return (pd);
344 	}
345 
346 	if ((np = topo_mod_zalloc(mp, sizeof (did_t))) == NULL)
347 		return (NULL);
348 	np->dp_mod = mp;
349 	np->dp_src = src;
350 	np->dp_hash = (did_hash_t *)topo_mod_getspecific(mp);
351 	np->dp_tnode = NULL;
352 
353 	/*
354 	 * We must have a reg prop and from it we extract the bus #,
355 	 * device #, and function #.
356 	 */
357 	if (di_uintprop_get(mp, src, DI_REGPROP, &reg) < 0) {
358 		topo_mod_free(mp, np, sizeof (did_t));
359 		return (NULL);
360 	}
361 	np->dp_board = ibrd;
362 	np->dp_bridge = ibrdge;
363 	np->dp_rc = irc;
364 	if (ibus == TRUST_BDF)
365 		np->dp_bus = PCI_REG_BUS_G(reg);
366 	else
367 		np->dp_bus = ibus;
368 	np->dp_dev = PCI_REG_DEV_G(reg);
369 	np->dp_fn = PCI_REG_FUNC_G(reg);
370 	np->dp_bdf = (PCI_REG_BUS_G(reg) << 8) | (PCI_REG_DEV_G(reg) << 3) |
371 	    PCI_REG_FUNC_G(reg);
372 	/*
373 	 * There *may* be a class code we can capture.  If there wasn't
374 	 * one, capture that fact by setting the class value to -1.
375 	 */
376 	if (di_uintprop_get(mp, src, DI_CCPROP, &code) == 0) {
377 		np->dp_class = GETCLASS(code);
378 		np->dp_subclass = GETSUBCLASS(code);
379 	} else {
380 		np->dp_class = -1;
381 	}
382 	/*
383 	 * There *may* be a device type we can capture.
384 	 */
385 	(void) di_devtype_get(mp, src, &np->dp_devtype);
386 
387 	if (irc >= 0) {
388 		/*
389 		 * This is a pciex node.
390 		 */
391 		if (di_physlotinfo_get(mp, src, np->dp_bdf, &np->dp_physlot,
392 		    &np->dp_physlot_name) < 0) {
393 			if (np->dp_devtype != NULL)
394 				topo_mod_strfree(mp, np->dp_devtype);
395 			topo_mod_free(mp, np, sizeof (did_t));
396 			return (NULL);
397 		}
398 	} else {
399 		/*
400 		 * This is a pci node.
401 		 */
402 		np->dp_physlot = -1;
403 		if (di_slotinfo_get(mp, src, &np->dp_nslots,
404 		    &np->dp_slotnames) < 0) {
405 			if (np->dp_devtype != NULL)
406 				topo_mod_strfree(mp, np->dp_devtype);
407 			topo_mod_free(mp, np, sizeof (did_t));
408 			return (NULL);
409 		}
410 	}
411 	did_hash_insert(mp, src, np);
412 	did_hold(np);
413 	return (np);
414 }
415 
416 did_t *
did_link_get(did_t * dp)417 did_link_get(did_t *dp)
418 {
419 	assert(dp != NULL);
420 	return (dp->dp_link);
421 }
422 
423 did_t *
did_chain_get(did_t * dp)424 did_chain_get(did_t *dp)
425 {
426 	assert(dp != NULL);
427 	return (dp->dp_chain);
428 }
429 
430 void
did_link_set(topo_mod_t * mod,tnode_t * head,did_t * tail)431 did_link_set(topo_mod_t *mod, tnode_t *head, did_t *tail)
432 {
433 	did_t *hd, *pd;
434 
435 	assert(head != NULL);
436 	pd = hd = did_find(mod, topo_node_getspecific(head));
437 	assert(hd != NULL);
438 	while ((hd = did_link_get(hd)) != NULL)
439 		pd = hd;
440 	pd->dp_link = tail;
441 	tail->dp_link = NULL;
442 }
443 
444 void
did_did_link_set(did_t * from,did_t * to)445 did_did_link_set(did_t *from, did_t *to)
446 {
447 	assert(from != NULL && to != NULL);
448 	from->dp_link = to;
449 }
450 
451 void
did_did_chain_set(did_t * from,did_t * to)452 did_did_chain_set(did_t *from, did_t *to)
453 {
454 	assert(from != NULL && to != NULL);
455 	from->dp_chain = to;
456 }
457 
458 void
did_destroy(did_t * dp)459 did_destroy(did_t *dp)
460 {
461 	assert(dp != NULL);
462 
463 	/*
464 	 * did_destroy() is called only from did_hash_destroy() when
465 	 * all references to the did_t have been released.  We can
466 	 * safely destroy the did_t.  If at some later time, more
467 	 * fine-grained reference count control is desired, this
468 	 * code will need to change
469 	 */
470 
471 	if (dp->dp_devtype != NULL)
472 		topo_mod_strfree(dp->dp_mod, dp->dp_devtype);
473 	if (dp->dp_physlot_name != NULL)
474 		topo_mod_strfree(dp->dp_mod, dp->dp_physlot_name);
475 	if (dp->dp_slot_label != NULL)
476 		topo_mod_strfree(dp->dp_mod, dp->dp_slot_label);
477 	slotnm_destroy(dp->dp_slotnames);
478 	topo_mod_free(dp->dp_mod, dp, sizeof (did_t));
479 }
480 
481 void
did_hold(did_t * dp)482 did_hold(did_t *dp)
483 {
484 	assert(dp != NULL);
485 	dp->dp_refcnt++;
486 }
487 
488 void
did_rele(did_t * dp)489 did_rele(did_t *dp)
490 {
491 	assert(dp != NULL);
492 	assert(dp->dp_refcnt > 0);
493 	dp->dp_refcnt--;
494 }
495 
496 di_node_t
did_dinode(did_t * dp)497 did_dinode(did_t *dp)
498 {
499 	assert(dp != NULL);
500 	assert(dp->dp_src != NULL);
501 	return (dp->dp_src);
502 }
503 
504 topo_mod_t *
did_mod(did_t * dp)505 did_mod(did_t *dp)
506 {
507 	assert(dp != NULL);
508 	return (dp->dp_mod);
509 }
510 
511 void
did_markrc(did_t * dp)512 did_markrc(did_t *dp)
513 {
514 	assert(dp != NULL);
515 	dp->dp_excap |= PCIE_PCIECAP_DEV_TYPE_ROOT;
516 }
517 
518 void
did_BDF(did_t * dp,int * bus,int * dev,int * fn)519 did_BDF(did_t *dp, int *bus, int *dev, int *fn)
520 {
521 	assert(dp != NULL);
522 	if (bus != NULL)
523 		*bus = dp->dp_bus;
524 	if (dev != NULL)
525 		*dev = dp->dp_dev;
526 	if (fn != NULL)
527 		*fn = dp->dp_fn;
528 }
529 
530 int
did_board(did_t * did)531 did_board(did_t *did)
532 {
533 	assert(did != NULL);
534 	return (did->dp_board);
535 }
536 
537 int
did_bridge(did_t * did)538 did_bridge(did_t *did)
539 {
540 	assert(did != NULL);
541 	return (did->dp_bridge);
542 }
543 
544 int
did_rc(did_t * did)545 did_rc(did_t *did)
546 {
547 	assert(did != NULL);
548 	return (did->dp_rc);
549 }
550 
551 int
did_excap(did_t * dp)552 did_excap(did_t *dp)
553 {
554 	assert(dp != NULL);
555 	return ((int)dp->dp_excap);
556 }
557 
558 void
did_excap_set(did_t * dp,int type)559 did_excap_set(did_t *dp, int type)
560 {
561 	dp->dp_excap = type;
562 }
563 
564 int
did_bdf(did_t * dp)565 did_bdf(did_t *dp)
566 {
567 	assert(dp != NULL);
568 	return ((int)dp->dp_bdf);
569 }
570 
571 const char *
did_physlot_name(did_t * dp,int dev)572 did_physlot_name(did_t *dp, int dev)
573 {
574 	slotnm_t *slot;
575 
576 	assert(dp != NULL);
577 
578 	/*
579 	 * For pciex, name will be in dp_physlot_name
580 	 */
581 	if (dp->dp_physlot_name != NULL)
582 		return (dp->dp_physlot_name);
583 
584 	/*
585 	 * For pci, name will be in dp_slotnames
586 	 */
587 	for (slot = dp->dp_slotnames; slot != NULL; slot = slot->snm_next)
588 		if (slot->snm_dev == dev)
589 			break;
590 	if (slot != NULL)
591 		return (slot->snm_name);
592 	return (NULL);
593 }
594 
595 char *
did_slot_label_get(did_t * did)596 did_slot_label_get(did_t *did)
597 {
598 	assert(did != NULL);
599 	return (did->dp_slot_label);
600 }
601 
602 void
did_slot_label_set(did_t * did,char * l)603 did_slot_label_set(did_t *did, char *l)
604 {
605 	assert(did != NULL);
606 	did->dp_slot_label = l;
607 }
608 
609 did_t *
did_find(topo_mod_t * mp,di_node_t dn)610 did_find(topo_mod_t *mp, di_node_t dn)
611 {
612 	return (did_hash_lookup(mp, dn));
613 }
614 
615 int
pci_BDF_get(topo_mod_t * mp,di_node_t dn,int * bus,int * dev,int * fn)616 pci_BDF_get(topo_mod_t *mp, di_node_t dn, int *bus, int *dev, int *fn)
617 {
618 	did_t *dp;
619 
620 	if ((dp = did_find(mp, dn)) == NULL)
621 		return (-1);
622 	*bus = dp->dp_bus;
623 	*dev = dp->dp_dev;
624 	*fn = dp->dp_fn;
625 	did_rele(dp);
626 	return (0);
627 }
628 
629 int
pci_classcode_get(topo_mod_t * mp,di_node_t dn,uint_t * class,uint_t * sub)630 pci_classcode_get(topo_mod_t *mp, di_node_t dn, uint_t *class, uint_t *sub)
631 {
632 	did_t *dp;
633 
634 	if ((dp = did_find(mp, dn)) == NULL)
635 		return (-1);
636 	if (dp->dp_class < 0) {
637 		did_rele(dp);
638 		return (-1);
639 	}
640 	*class = dp->dp_class;
641 	*sub = dp->dp_subclass;
642 	did_rele(dp);
643 	return (0);
644 }
645 
646 char *
pci_devtype_get(topo_mod_t * mp,di_node_t dn)647 pci_devtype_get(topo_mod_t *mp, di_node_t dn)
648 {
649 	did_t *dp;
650 
651 	if ((dp = did_find(mp, dn)) == NULL)
652 		return (NULL);
653 	did_rele(dp);
654 	return (dp->dp_devtype);
655 }
656 
657 int
pciex_cap_get(topo_mod_t * mp,di_node_t dn)658 pciex_cap_get(topo_mod_t *mp, di_node_t dn)
659 {
660 	did_t *dp;
661 
662 	if ((dp = did_find(mp, dn)) == NULL)
663 		return (-1);
664 	did_rele(dp);
665 	return (dp->dp_excap);
666 }
667 
668 void
did_setspecific(topo_mod_t * mp,void * data)669 did_setspecific(topo_mod_t *mp, void *data)
670 {
671 	did_t *hbdid;
672 
673 	hbdid = (did_t *)data;
674 	topo_mod_setspecific(mp, hbdid->dp_hash);
675 }
676 
677 void
did_settnode(did_t * pd,tnode_t * tn)678 did_settnode(did_t *pd, tnode_t *tn)
679 {
680 	assert(tn != NULL);
681 	pd->dp_tnode = tn;
682 }
683 
684 tnode_t *
did_gettnode(did_t * pd)685 did_gettnode(did_t *pd)
686 {
687 	return (pd->dp_tnode);
688 }
689