view usr/src/lib/fm/topo/modules/common/pcibus/pcibus.c @ 9921:0c3d84a756da

6768098 system panics with PCIe fabric.(0x0)(0x43) due to masked errors. 6814026 PLX disable RO algorithm is incorrect 6813298 Legacy PCI Express Endpoint is not used correctly in pcie module 6841301 PCI ECS accesses with pcitool don't work on AMD processors 6813766 faulty EPKT FMA rules need to expect ereports coming from hostbridge 6841816 PCIe Error Handling's scan_fabric doesn't handle failed IO Addresses well 6798264 PCIe error handling doesn't handle zero bdf well 6802636 fault address may not be decoded correctly during PCIe error handling 6843716 suspicious definition of PCIE_REQ_ID_DEV_MASK 6831766 coredump in pci_bridge_declare()
author Krishna Elango <Krishna.Elango@Sun.COM>
date Sat, 20 Jun 2009 15:42:11 -0700
parents a52dad0f5e0f
children be69f645ce17
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/fm/protocol.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <alloca.h>
#include <sys/param.h>
#include <sys/pci.h>
#include <sys/pcie.h>
#include <libdevinfo.h>
#include <libnvpair.h>
#include <fm/topo_mod.h>
#include <fm/topo_hc.h>

#include <hostbridge.h>
#include <pcibus.h>
#include <did.h>
#include <did_props.h>
#include <util.h>

extern txprop_t Bus_common_props[];
extern txprop_t Dev_common_props[];
extern txprop_t Fn_common_props[];
extern int Bus_propcnt;
extern int Dev_propcnt;
extern int Fn_propcnt;

extern int platform_pci_label(topo_mod_t *mod, tnode_t *, nvlist_t *,
    nvlist_t **);
extern int platform_pci_fru(topo_mod_t *mod, tnode_t *, nvlist_t *,
    nvlist_t **);
static void pci_release(topo_mod_t *, tnode_t *);
static int pci_enum(topo_mod_t *, tnode_t *, const char *, topo_instance_t,
    topo_instance_t, void *, void *);
static int pci_label(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
    nvlist_t **);
static int pci_fru(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
    nvlist_t **);

static const topo_modops_t Pci_ops =
	{ pci_enum, pci_release };
static const topo_modinfo_t Pci_info =
	{ PCI_BUS, FM_FMRI_SCHEME_HC, PCI_ENUMR_VERS, &Pci_ops };

static const topo_method_t Pci_methods[] = {
	{ TOPO_METH_LABEL, TOPO_METH_LABEL_DESC,
	    TOPO_METH_LABEL_VERSION, TOPO_STABILITY_INTERNAL, pci_label },
	{ TOPO_METH_FRU_COMPUTE, TOPO_METH_FRU_COMPUTE_DESC,
	    TOPO_METH_FRU_COMPUTE_VERSION, TOPO_STABILITY_INTERNAL, pci_fru },
	{ NULL }
};

int
_topo_init(topo_mod_t *modhdl, topo_version_t version)
{
	/*
	 * Turn on module debugging output
	 */
	if (getenv("TOPOPCIDBG") != NULL)
		topo_mod_setdebug(modhdl);
	topo_mod_dprintf(modhdl, "initializing pcibus builtin\n");

	if (version != PCI_ENUMR_VERS)
		return (topo_mod_seterrno(modhdl, EMOD_VER_NEW));

	topo_mod_register(modhdl, &Pci_info, TOPO_VERSION);
	topo_mod_dprintf(modhdl, "PCI Enumr initd\n");

	return (0);
}

void
_topo_fini(topo_mod_t *modhdl)
{
	topo_mod_unregister(modhdl);
}

static int
pci_label(topo_mod_t *mp, tnode_t *node, topo_version_t version,
    nvlist_t *in, nvlist_t **out)
{
	if (version > TOPO_METH_LABEL_VERSION)
		return (topo_mod_seterrno(mp, EMOD_VER_NEW));
	return (platform_pci_label(mp, node, in, out));
}
static int
pci_fru(topo_mod_t *mp, tnode_t *node, topo_version_t version,
    nvlist_t *in, nvlist_t **out)
{
	if (version > TOPO_METH_FRU_COMPUTE_VERSION)
		return (topo_mod_seterrno(mp, EMOD_VER_NEW));
	return (platform_pci_fru(mp, node, in, out));
}
static tnode_t *
pci_tnode_create(topo_mod_t *mod, tnode_t *parent,
    const char *name, topo_instance_t i, void *priv)
{
	tnode_t *ntn;

	if ((ntn = tnode_create(mod, parent, name, i, priv)) == NULL)
		return (NULL);
	if (topo_method_register(mod, ntn, Pci_methods) < 0) {
		topo_mod_dprintf(mod, "topo_method_register failed: %s\n",
		    topo_strerror(topo_mod_errno(mod)));
		topo_node_unbind(ntn);
		return (NULL);
	}
	return (ntn);
}

/*ARGSUSED*/
static int
hostbridge_asdevice(topo_mod_t *mod, tnode_t *bus)
{
	di_node_t di;
	tnode_t *dev32;

	di = topo_node_getspecific(bus);
	assert(di != DI_NODE_NIL);

	if ((dev32 = pcidev_declare(mod, bus, di, 32)) == NULL)
		return (-1);
	if (pcifn_declare(mod, dev32, di, 0) == NULL) {
		topo_node_unbind(dev32);
		return (-1);
	}
	return (0);
}

tnode_t *
pciexfn_declare(topo_mod_t *mod, tnode_t *parent, di_node_t dn,
    topo_instance_t i)
{
	did_t *pd;
	tnode_t *ntn, *ptn;
	di_node_t pdn;
	uint_t class, subclass;
	char *devtyp, *pdevtyp;
	int pcie_devtyp, pexcap;
	boolean_t dev_is_pcie, pdev_is_pcie;

	/* We need the parent's dev info node for some of the info */
	ptn = find_predecessor(parent, PCIEX_FUNCTION);
	/* If this is the first child under root, get root's ptn */
	if (ptn == NULL)
		ptn = find_predecessor(parent, PCIEX_ROOT);
	if (ptn == NULL)
		return (NULL);
	pdn = topo_node_getspecific(ptn);

	/* Get the required info to populate the excap */
	(void) pci_classcode_get(mod, dn, &class, &subclass);
	devtyp = pci_devtype_get(mod, dn);
	pdevtyp = pci_devtype_get(mod, pdn);
	pexcap = pciex_cap_get(mod, pdn);

	dev_is_pcie = devtyp && (strcmp(devtyp, "pciex") == 0);
	pdev_is_pcie = pdevtyp && (strcmp(pdevtyp, "pciex") == 0);

	/*
	 * Populate the excap with correct PCIe device type.
	 *
	 * Device	Parent		Device		Parent	Device
	 * excap	device-type	device-type	excap	Class Code
	 * -------------------------------------------------------------------
	 * PCI(default)	pci		N/A		N/A	!= bridge
	 * PCIe		pciex		N/A		N/A	!= bridge
	 * Root Port	Defined in hostbridge
	 * Switch Up	pciex		pciex		!= up	= bridge
	 * Switch Down	pciex		pciex		= up	= bridge
	 * PCIe-PCI	pciex		pci		N/A	= bridge
	 * PCI-PCIe	pci		pciex		N/A	= bridge
	 */
	pcie_devtyp = PCIE_PCIECAP_DEV_TYPE_PCI_DEV;
	if (class == PCI_CLASS_BRIDGE && subclass == PCI_BRIDGE_PCI) {
		if (pdev_is_pcie) {
			if (dev_is_pcie) {
				if (pexcap != PCIE_PCIECAP_DEV_TYPE_UP)
					pcie_devtyp = PCIE_PCIECAP_DEV_TYPE_UP;
				else
					pcie_devtyp =
					    PCIE_PCIECAP_DEV_TYPE_DOWN;
			} else {
				pcie_devtyp = PCIE_PCIECAP_DEV_TYPE_PCIE2PCI;
			}
		} else {
			if (dev_is_pcie)
				pcie_devtyp = PCIE_PCIECAP_DEV_TYPE_PCI2PCIE;
		}
	} else {
		if (pdev_is_pcie)
			pcie_devtyp = PCIE_PCIECAP_DEV_TYPE_PCIE_DEV;
	}

	if ((pd = did_find(mod, dn)) == NULL)
		return (NULL);
	did_excap_set(pd, pcie_devtyp);

	if ((ntn = pci_tnode_create(mod, parent, PCIEX_FUNCTION, i, dn))
	    == NULL)
		return (NULL);
	if (did_props_set(ntn, pd, Fn_common_props, Fn_propcnt) < 0) {
		topo_node_unbind(ntn);
		return (NULL);
	}
	/*
	 * We may find pci-express buses or plain-pci buses beneath a function
	 */
	if (child_range_add(mod, ntn, PCIEX_BUS, 0, MAX_HB_BUSES) < 0) {
		topo_node_unbind(ntn);
		return (NULL);
	}
	if (child_range_add(mod, ntn, PCI_BUS, 0, MAX_HB_BUSES) < 0) {
		topo_node_range_destroy(ntn, PCIEX_BUS);
		topo_node_unbind(ntn);
		return (NULL);
	}
	return (ntn);
}

tnode_t *
pciexdev_declare(topo_mod_t *mod, tnode_t *parent, di_node_t dn,
    topo_instance_t i)
{
	did_t *pd;
	tnode_t *ntn;

	if ((pd = did_find(mod, dn)) == NULL)
		return (NULL);
	did_settnode(pd, parent);

	if ((ntn = pci_tnode_create(mod, parent, PCIEX_DEVICE, i, dn)) == NULL)
		return (NULL);
	if (did_props_set(ntn, pd, Dev_common_props, Dev_propcnt) < 0) {
		topo_node_unbind(ntn);
		return (NULL);
	}

	/*
	 * We can expect to find pci-express functions beneath the device
	 */
	if (child_range_add(mod,
	    ntn, PCIEX_FUNCTION, 0, MAX_PCIDEV_FNS) < 0) {
		topo_node_unbind(ntn);
		return (NULL);
	}
	return (ntn);
}

tnode_t *
pciexbus_declare(topo_mod_t *mod, tnode_t *parent, di_node_t dn,
    topo_instance_t i)
{
	did_t *pd;
	tnode_t *ntn;

	if ((pd = did_find(mod, dn)) == NULL)
		return (NULL);
	did_settnode(pd, parent);
	if ((ntn = pci_tnode_create(mod, parent, PCIEX_BUS, i, dn)) == NULL)
		return (NULL);
	if (did_props_set(ntn, pd, Bus_common_props, Bus_propcnt) < 0) {
		topo_node_unbind(ntn);
		return (NULL);
	}
	/*
	 * We can expect to find pci-express devices beneath the bus
	 */
	if (child_range_add(mod,
	    ntn, PCIEX_DEVICE, 0, MAX_PCIBUS_DEVS) < 0) {
		topo_node_unbind(ntn);
		return (NULL);
	}
	return (ntn);
}

tnode_t *
pcifn_declare(topo_mod_t *mod, tnode_t *parent, di_node_t dn,
    topo_instance_t i)
{
	did_t *pd;
	tnode_t *ntn;

	if ((pd = did_find(mod, dn)) == NULL)
		return (NULL);
	did_excap_set(pd, PCIE_PCIECAP_DEV_TYPE_PCI_DEV);

	if ((ntn = pci_tnode_create(mod, parent, PCI_FUNCTION, i, dn)) == NULL)
		return (NULL);
	if (did_props_set(ntn, pd, Fn_common_props, Fn_propcnt) < 0) {
		topo_node_unbind(ntn);
		return (NULL);
	}
	/*
	 * We may find pci buses beneath a function
	 */
	if (child_range_add(mod, ntn, PCI_BUS, 0, MAX_HB_BUSES) < 0) {
		topo_node_unbind(ntn);
		return (NULL);
	}
	return (ntn);
}

tnode_t *
pcidev_declare(topo_mod_t *mod, tnode_t *parent, di_node_t dn,
    topo_instance_t i)
{
	did_t *pd;
	tnode_t *ntn;

	if ((pd = did_find(mod, dn)) == NULL)
		return (NULL);
	/* remember parent tnode */
	did_settnode(pd, parent);

	if ((ntn = pci_tnode_create(mod, parent, PCI_DEVICE, i, dn)) == NULL)
		return (NULL);
	if (did_props_set(ntn, pd, Dev_common_props, Dev_propcnt) < 0) {
		topo_node_unbind(ntn);
		return (NULL);
	}

	/*
	 * We can expect to find pci functions beneath the device
	 */
	if (child_range_add(mod, ntn, PCI_FUNCTION, 0, MAX_PCIDEV_FNS) < 0) {
		topo_node_unbind(ntn);
		return (NULL);
	}
	return (ntn);
}

tnode_t *
pcibus_declare(topo_mod_t *mod, tnode_t *parent, di_node_t dn,
    topo_instance_t i)
{
	did_t *pd;
	tnode_t *ntn;
	int hbchild = 0;

	if ((pd = did_find(mod, dn)) == NULL)
		return (NULL);
	did_settnode(pd, parent);
	if ((ntn = pci_tnode_create(mod, parent, PCI_BUS, i, dn)) == NULL)
		return (NULL);
	/*
	 * If our devinfo node is lacking certain information of its
	 * own, and our parent topology node is a hostbridge, we may
	 * need/want to inherit information available in the
	 * hostbridge node's private data.
	 */
	if (strcmp(topo_node_name(parent), HOSTBRIDGE) == 0)
		hbchild = 1;
	if (did_props_set(ntn, pd, Bus_common_props, Bus_propcnt) < 0) {
		topo_node_unbind(ntn);
		return (NULL);
	}
	/*
	 * We can expect to find pci devices beneath the bus
	 */
	if (child_range_add(mod, ntn, PCI_DEVICE, 0, MAX_PCIBUS_DEVS) < 0) {
		topo_node_unbind(ntn);
		return (NULL);
	}
	/*
	 * On each bus child of the hostbridge, we represent the
	 * hostbridge as a device outside the range of legal device
	 * numbers.
	 */
	if (hbchild == 1) {
		if (hostbridge_asdevice(mod, ntn) < 0) {
			topo_node_range_destroy(ntn, PCI_DEVICE);
			topo_node_unbind(ntn);
			return (NULL);
		}
	}
	return (ntn);
}

static int
pci_bridge_declare(topo_mod_t *mod, tnode_t *fn, di_node_t din, int board,
    int bridge, int rc, int depth)
{
	int err;
	char *devtyp;

	devtyp = pci_devtype_get(mod, din);
	/* Check if the children are PCI or PCIe */
	if (devtyp && (strcmp(devtyp, "pciex") == 0))
		err = pci_children_instantiate(mod, fn, din, board, bridge,
		    rc, TRUST_BDF, depth + 1);
	else
		err = pci_children_instantiate(mod, fn, din, board, bridge,
		    rc - TO_PCI, TRUST_BDF, depth + 1);
	return (err);
}

static void
declare_dev_and_fn(topo_mod_t *mod, tnode_t *bus, tnode_t **dev, di_node_t din,
    int board, int bridge, int rc, int devno, int fnno, int depth)
{
	int dcnt = 0;
	tnode_t *fn;
	uint_t class, subclass;
	uint_t vid, did;
	did_t *dp = NULL;

	if (*dev == NULL) {
		if (rc >= 0)
			*dev = pciexdev_declare(mod, bus, din, devno);
		else
			*dev = pcidev_declare(mod, bus, din, devno);
		if (*dev == NULL)
			return;
		++dcnt;
	}
	if (rc >= 0)
		fn = pciexfn_declare(mod, *dev, din, fnno);
	else
		fn = pcifn_declare(mod, *dev, din, fnno);

	if (fn == NULL) {
		if (dcnt) {
			topo_node_unbind(*dev);
			*dev = NULL;
		}
		return;
	}

	if (pci_classcode_get(mod, din, &class, &subclass) < 0) {
		topo_node_unbind(fn);
		if (dcnt)
			topo_node_unbind(*dev);
		return;
	}

	/*
	 * This function may be a bridge.  If not, check for a possible
	 * topology map file and kick off its enumeration of lower-level
	 * devices.
	 */
	if (class == PCI_CLASS_BRIDGE && subclass == PCI_BRIDGE_PCI) {
		(void) pci_bridge_declare(mod, fn, din, board, bridge, rc,
		    depth);
	}

	/*
	 * Check for a Neptune-based NIC. This could either be a Neptune
	 * adapter card or an Neptune ASIC on a board (e.g. motherboard)
	 *
	 * For Netpune adapter cards, use xfp-hc-topology.xml to expand
	 * topology to include the XFP optical module, which is a FRU on
	 * the Neptune based 10giga fiber NICs.
	 *
	 * For Neptune ASICs, use the XAUI enumerator to expand topology.
	 * The 10giga ports are externalized by a XAUI cards, which
	 * are FRUs. The XAUI enumerator in turn instantiates the XFP
	 * optical module FRUs.
	 */
	else if (class == PCI_CLASS_NET &&
	    di_uintprop_get(mod, din, DI_VENDIDPROP, &vid) >= 0 &&
	    di_uintprop_get(mod, din, DI_DEVIDPROP, &did) >= 0) {
		if (vid == SUN_VENDOR_ID && did == NEPTUNE_DEVICE_ID) {
			/*
			 * Is this an adapter card? Check the bus's physlot
			 */
			dp = did_find(mod, topo_node_getspecific(bus));
			if (did_physlot(dp) >= 0) {
				topo_mod_dprintf(mod, "Found Neptune slot\n");
				(void) topo_mod_enummap(mod, fn,
				    "xfp", FM_FMRI_SCHEME_HC);
			} else {
				topo_mod_dprintf(mod, "Found Neptune ASIC\n");
				if (topo_mod_load(mod, XAUI, TOPO_VERSION) ==
				    NULL) {
					topo_mod_dprintf(mod, "pcibus enum "
					    "could not load xaui enum\n");
					topo_mod_seterrno(mod,
					    EMOD_PARTIAL_ENUM);
					return;
				} else {
					if (topo_node_range_create(mod, fn,
					    XAUI, 0, 1) < 0) {
						topo_mod_dprintf(mod,
						    "child_range_add for "
						    "XAUI failed: %s\n",
						    topo_strerror(
						    topo_mod_errno(mod)));
						return;
					}
					(void) topo_mod_enumerate(mod, fn,
					    XAUI, XAUI, fnno, fnno, fn);
				}
			}
		}
	}
}

int
pci_children_instantiate(topo_mod_t *mod, tnode_t *parent, di_node_t pn,
    int board, int bridge, int rc, int bover, int depth)
{
	did_t *pps[MAX_PCIBUS_DEVS][MAX_PCIDEV_FNS];
	did_t *bp = NULL;
	did_t *np;
	di_node_t sib;
	di_node_t din;
	tnode_t *bn = NULL;
	tnode_t *dn = NULL;
	int pb = -1;
	int b, d, f;

	for (d = 0; d < MAX_PCIBUS_DEVS; d++)
		for (f = 0; f < MAX_PCIDEV_FNS; f++)
			pps[d][f] = NULL;

	/* start at the parent's first sibling */
	sib = di_child_node(pn);
	while (sib != DI_NODE_NIL) {
		np = did_create(mod, sib, board, bridge, rc, bover);
		if (np == NULL)
			return (-1);
		did_BDF(np, &b, &d, &f);
		pps[d][f] = np;
		if (bp == NULL)
			bp = np;
		if (pb < 0)
			pb = ((bover == TRUST_BDF) ? b : bover);
		sib = di_sibling_node(sib);
	}
	if (pb < 0 && bover < 0)
		return (0);
	if (rc >= 0)
		bn = pciexbus_declare(mod, parent, pn, ((pb < 0) ? bover : pb));
	else
		bn = pcibus_declare(mod, parent, pn, ((pb < 0) ? bover : pb));
	if (bn == NULL)
		return (-1);
	if (pb < 0)
		return (0);

	for (d = 0; d < MAX_PCIBUS_DEVS; d++) {
		for (f = 0; f < MAX_PCIDEV_FNS; f++) {
			if (pps[d][f] == NULL)
				continue;
			din = did_dinode(pps[d][f]);

			/*
			 * Try to enumerate as many devices and functions as
			 * possible.  If we fail to declare a device, break
			 * out of the function loop.
			 */
			declare_dev_and_fn(mod, bn,
			    &dn, din, board, bridge, rc, d, f, depth);
			did_rele(pps[d][f]);

			if (dn == NULL)
				break;
		}
		dn = NULL;
	}
	return (0);
}

static int
pciexbus_enum(topo_mod_t *mp, tnode_t *ptn, char *pnm, topo_instance_t min,
    topo_instance_t max)
{
	di_node_t pdn;
	int rc;
	int retval;

	/*
	 * PCI-Express; root complex shares the hostbridge's instance
	 * number.  Parent node's private data is a simple di_node_t
	 * and we have to construct our own did hash and did_t.
	 */
	rc = topo_node_instance(ptn);

	if ((pdn = topo_node_getspecific(ptn)) == DI_NODE_NIL) {
		topo_mod_dprintf(mp,
		    "Parent %s node missing private data.\n"
		    "Unable to proceed with %s enumeration.\n", pnm, PCIEX_BUS);
		return (0);
	}
	if (did_hash_init(mp) != 0)
		return (-1);
	if ((did_create(mp, pdn, 0, 0, rc, TRUST_BDF)) == NULL)
		return (-1);	/* errno already set */

	retval = pci_children_instantiate(mp, ptn, pdn, 0, 0, rc,
	    (min == max) ? min : TRUST_BDF, 0);
	did_hash_fini(mp);

	return (retval);
}

static int
pcibus_enum(topo_mod_t *mp, tnode_t *ptn, char *pnm, topo_instance_t min,
    topo_instance_t max, void *data)
{
	did_t *didp, *hbdid = (did_t *)data;
	int retval;

	/*
	 * XXTOPO: we should not be sharing private node data with another
	 * module. PCI Bus; Parent node's private data is a did_t.  We'll
	 * use the did hash established by the parent.
	 */
	did_setspecific(mp, data);

	/*
	 * If we're looking for a specific bus-instance, find the right
	 * did_t in the chain, otherwise, there should be only one did_t.
	 */
	if (min == max) {
		int b;
		didp = hbdid;
		while (didp != NULL) {
			did_BDF(didp, &b, NULL, NULL);
			if (b == min)
				break;
			didp = did_link_get(didp);
		}
		if (didp == NULL) {
			topo_mod_dprintf(mp,
			    "Parent %s node missing private data related\n"
			    "to %s instance %d.\n", pnm, PCI_BUS, min);
			topo_mod_setspecific(mp, NULL);
			return (0);
		}
	} else {
		assert(did_link_get(hbdid) == NULL);
		didp = hbdid;
	}
	retval = pci_children_instantiate(mp, ptn, did_dinode(didp),
	    did_board(didp), did_bridge(didp), did_rc(didp),
	    (min == max) ? min : TRUST_BDF, 0);

	topo_mod_setspecific(mp, NULL);

	return (retval);
}

/*ARGSUSED*/
static int
pci_enum(topo_mod_t *mod, tnode_t *ptn, const char *name,
    topo_instance_t min, topo_instance_t max, void *notused, void *data)
{
	int retval;
	char *pname;

	topo_mod_dprintf(mod, "Enumerating pci!\n");

	if (strcmp(name, PCI_BUS) != 0 && strcmp(name, PCIEX_BUS) != 0) {
		topo_mod_dprintf(mod,
		    "Currently only know how to enumerate %s or %s.\n",
		    PCI_BUS, PCIEX_BUS);
		return (0);
	}
	pname = topo_node_name(ptn);
	if (strcmp(pname, HOSTBRIDGE) != 0 && strcmp(pname, PCIEX_ROOT) != 0) {
		topo_mod_dprintf(mod,
		    "Currently can only enumerate a %s or %s directly\n",
		    PCI_BUS, PCIEX_BUS);
		topo_mod_dprintf(mod,
		    "descended from a %s or %s node.\n",
		    HOSTBRIDGE, PCIEX_ROOT);
		return (0);
	}

	if (strcmp(name, PCI_BUS) == 0) {
		retval = pcibus_enum(mod, ptn, pname, min, max, data);
	} else if (strcmp(name, PCIEX_BUS) == 0) {
		retval = pciexbus_enum(mod, ptn, pname, min, max);
	} else {
		topo_mod_dprintf(mod,
		    "Currently only know how to enumerate %s or %s not %s.\n",
		    PCI_BUS, PCIEX_BUS, name);
		return (0);
	}

	return (retval);
}

/*ARGSUSED*/
static void
pci_release(topo_mod_t *mp, tnode_t *node)
{
	topo_method_unregister_all(mp, node);

	/*
	 * node private data (did_t) for this node is destroyed in
	 * did_hash_destroy()
	 */

	topo_node_unbind(node);
}