view usr/src/lib/fm/topo/libtopo/common/topo_snap.c @ 12979:ab9ae749152f

PSARC/2009/617 Software Events Notification Parameters CLI PSARC/2009/618 snmp-notify: SNMP Notification Daemon for Software Events PSARC/2009/619 smtp-notify: Email Notification Daemon for Software Events PSARC/2010/225 fmd for non-global Solaris zones PSARC/2010/226 Solaris Instance UUID PSARC/2010/227 nvlist_nvflag(3NVPAIR) PSARC/2010/228 libfmevent additions PSARC/2010/257 sysevent_evc_setpropnvl and sysevent_evc_getpropnvl PSARC/2010/265 FMRI and FMA Event Stabilty, 'ireport' category 1 event class, and the 'sw' FMRI scheme PSARC/2010/278 FMA/SMF integration: instance state transitions PSARC/2010/279 Modelling panics within FMA PSARC/2010/290 logadm.conf upgrade 6392476 fmdump needs to pretty-print 6393375 userland ereport/ireport event generation interfaces 6445732 Add email notification agent for FMA and software events 6804168 RFE: Allow an efficient means to monitor SMF services status changes 6866661 scf_values_destroy(3SCF) will segfault if is passed NULL 6884709 Add snmp notification agent for FMA and software events 6884712 Add private interface to tap into libfmd_msg macro expansion capabilities 6897919 fmd to run in a non-global zone 6897937 fmd use of non-private doors is not safe 6900081 add a UUID to Solaris kernel image for use in crashdump identification 6914884 model panic events as a defect diagnosis in FMA 6944862 fmd_case_open_uuid, fmd_case_uuisresolved, fmd_nvl_create_defect 6944866 log legacy sysevents in fmd 6944867 enumerate svc scheme in topo 6944868 software-diagnosis and software-response fmd modules 6944870 model SMF maintenance state as a defect diagnosis in FMA 6944876 savecore runs in foreground for systems with zfs root and dedicated dump 6965796 Implement notification parameters for SMF state transitions and FMA events 6968287 SUN-FM-MIB.mib needs to be updated to reflect Oracle information 6972331 logadm.conf upgrade PSARC/2010/290
author Gavin Maltby <gavin.maltby@oracle.com>
date Fri, 30 Jul 2010 17:04:17 +1000
parents 8c1d722a097a
children 87d7bfd32811
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 (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Snapshot Library Interfaces
 *
 * Consumers of topology data may use the interfaces in this file to open,
 * snapshot and close a topology exported by FMRI scheme (hc, mem and cpu)
 * builtin plugins and their helper modules.  A topology handle is obtained
 * by calling topo_open().  Upon a successful return, the caller may use this
 * handle to open a new snapshot.  Each snapshot is assigned a Universally
 * Unique Identifier that in a future enchancement to the libtopo API will be
 * used as the file locator in /var/fm/topo to persist new snapshots or lookup
 * a previously captured snapshot.  topo_snap_hold() will capture the current
 * system topology.  All consumers of the topo_hdl_t argument will be
 * blocked from accessing the topology trees until the snapshot completes.
 *
 * A snapshot may be cleared by calling topo_snap_rele().  As with
 * topo_snap_hold(), all topology accesses are blocked until the topology
 * trees have been released and deallocated.
 *
 * Walker Library Interfaces
 *
 * Once a snapshot has been taken with topo_snap_hold(), topo_hdl_t holders
 * may initiate topology tree walks on a scheme-tree basis.  topo_walk_init()
 * will initiate the data structures required to walk any one one of the
 * FMRI scheme trees.  The walker data structure, topo_walk_t, is an opaque
 * handle passed to topo_walk_step to begin the walk.  At each node in the
 * topology tree, a callback function is called with access to the node at
 * which our current walk falls.  The callback function is passed in during
 * calls to topo_walk_init() and used throughout the walk_step of the
 * scheme tree.  At any time, the callback may terminate the walk by returning
 * TOPO_WALK_TERMINATE or TOPO_WALK_ERR.  TOPO_WALK_NEXT will continue the walk.
 *
 * The type of walk through the tree may be sibling first or child first by
 * respectively passing in TOPO_WALK_SIBLING or TOPO_WALK_CHILD to
 * the topo_walk_step() function.  Topology nodes
 * associated with an outstanding walk are held in place and will not be
 * deallocated until the walk through that node completes.
 *
 * Once the walk has terminated, the walking process should call
 * topo_walk_fini() to clean-up resources created in topo_walk_init()
 * and release nodes that may be still held.
 */

#include <alloca.h>
#include <ctype.h>
#include <pthread.h>
#include <limits.h>
#include <assert.h>
#include <fcntl.h>
#include <smbios.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/systeminfo.h>
#include <sys/utsname.h>
#include <uuid/uuid.h>
#include <zone.h>

#include <fm/libtopo.h>
#include <sys/fm/protocol.h>

#include <topo_alloc.h>
#include <topo_builtin.h>
#include <topo_string.h>
#include <topo_error.h>
#include <topo_subr.h>

static void topo_snap_destroy(topo_hdl_t *);

static topo_hdl_t *
set_open_errno(topo_hdl_t *thp, int *errp, int err)
{
	if (thp != NULL) {
		topo_close(thp);
	}
	if (errp != NULL)
		*errp = err;
	return (NULL);
}

topo_hdl_t *
topo_open(int version, const char *rootdir, int *errp)
{
	topo_hdl_t *thp = NULL;
	topo_alloc_t *tap;

	char platform[MAXNAMELEN];
	char isa[MAXNAMELEN];
	struct utsname uts;
	struct stat st;

	smbios_hdl_t *shp;
	smbios_system_t s1;
	smbios_info_t s2;
	id_t id;

	char *dbflags, *dbout;

	if (version != TOPO_VERSION)
		return (set_open_errno(thp, errp, ETOPO_HDL_ABIVER));

	if (rootdir != NULL && stat(rootdir, &st) < 0)
		return (set_open_errno(thp, errp, ETOPO_HDL_INVAL));

	if ((thp = topo_zalloc(sizeof (topo_hdl_t), 0)) == NULL)
		return (set_open_errno(thp, errp, ETOPO_NOMEM));

	(void) pthread_mutex_init(&thp->th_lock, NULL);

	if ((tap = topo_zalloc(sizeof (topo_alloc_t), 0)) == NULL)
		return (set_open_errno(thp, errp, ETOPO_NOMEM));

	/*
	 * Install default allocators
	 */
	tap->ta_flags = 0;
	tap->ta_alloc = topo_alloc;
	tap->ta_zalloc = topo_zalloc;
	tap->ta_free = topo_free;
	tap->ta_nvops.nv_ao_alloc = topo_nv_alloc;
	tap->ta_nvops.nv_ao_free = topo_nv_free;
	(void) nv_alloc_init(&tap->ta_nva, &tap->ta_nvops);
	thp->th_alloc = tap;

	if ((thp->th_modhash = topo_modhash_create(thp)) == NULL)
		return (set_open_errno(thp, errp, ETOPO_NOMEM));

	/*
	 * Set-up system information and search paths for modules
	 * and topology map files
	 */
	if (rootdir == NULL) {
		rootdir = topo_hdl_strdup(thp, "/");
		thp->th_rootdir = (char *)rootdir;
	} else {
		int len;
		char *rpath;

		len = strlen(rootdir);
		if (len >= PATH_MAX)
			return (set_open_errno(thp, errp, EINVAL));

		if (rootdir[len - 1] != '/') {
			rpath = alloca(len + 2);
			(void) snprintf(rpath, len + 2, "%s/", rootdir);
		} else {
			rpath = (char *)rootdir;
		}
		thp->th_rootdir = topo_hdl_strdup(thp, rpath);
	}

	platform[0] = '\0';
	isa[0] = '\0';
	(void) sysinfo(SI_PLATFORM, platform, sizeof (platform));
	(void) sysinfo(SI_ARCHITECTURE, isa, sizeof (isa));
	(void) uname(&uts);
	thp->th_platform = topo_hdl_strdup(thp, platform);
	thp->th_isa = topo_hdl_strdup(thp, isa);
	thp->th_machine = topo_hdl_strdup(thp, uts.machine);
	if ((shp = smbios_open(NULL, SMB_VERSION, 0, NULL)) != NULL) {
		if ((id = smbios_info_system(shp, &s1)) != SMB_ERR &&
		    smbios_info_common(shp, id, &s2) != SMB_ERR) {

			if (strcmp(s2.smbi_product, SMB_DEFAULT1) != 0 &&
			    strcmp(s2.smbi_product, SMB_DEFAULT2) != 0) {
				thp->th_product = topo_cleanup_auth_str(thp,
				    (char *)s2.smbi_product);
			}
		}
		smbios_close(shp);
	} else {
		thp->th_product = topo_hdl_strdup(thp, thp->th_platform);
	}

	if (thp->th_rootdir == NULL || thp->th_platform == NULL ||
	    thp->th_machine == NULL)
		return (set_open_errno(thp, errp, ETOPO_NOMEM));

	dbflags	 = getenv("TOPO_DEBUG");
	dbout = getenv("TOPO_DEBUG_OUT");
	if (dbflags != NULL)
		topo_debug_set(thp, dbflags, dbout);

	if (topo_builtin_create(thp, thp->th_rootdir) != 0) {
		topo_dprintf(thp, TOPO_DBG_ERR,
		    "failed to load builtin modules: %s\n",
		    topo_hdl_errmsg(thp));
		return (set_open_errno(thp, errp, topo_hdl_errno(thp)));
	}

	return (thp);
}

void
topo_close(topo_hdl_t *thp)
{
	ttree_t *tp;

	topo_hdl_lock(thp);
	if (thp->th_platform != NULL)
		topo_hdl_strfree(thp, thp->th_platform);
	if (thp->th_isa != NULL)
		topo_hdl_strfree(thp, thp->th_isa);
	if (thp->th_machine != NULL)
		topo_hdl_strfree(thp, thp->th_machine);
	if (thp->th_product != NULL)
		topo_hdl_strfree(thp, thp->th_product);
	if (thp->th_rootdir != NULL)
		topo_hdl_strfree(thp, thp->th_rootdir);
	if (thp->th_ipmi != NULL)
		ipmi_close(thp->th_ipmi);
	if (thp->th_smbios != NULL)
		smbios_close(thp->th_smbios);

	/*
	 * Clean-up snapshot
	 */
	topo_snap_destroy(thp);

	/*
	 * Clean-up trees
	 */
	while ((tp = topo_list_next(&thp->th_trees)) != NULL) {
		topo_list_delete(&thp->th_trees, tp);
		topo_tree_destroy(tp);
	}

	/*
	 * Unload all plugins
	 */
	topo_modhash_unload_all(thp);

	if (thp->th_modhash != NULL)
		topo_modhash_destroy(thp);
	if (thp->th_alloc != NULL)
		topo_free(thp->th_alloc, sizeof (topo_alloc_t));

	topo_hdl_unlock(thp);

	topo_free(thp, sizeof (topo_hdl_t));
}

static char *
topo_snap_create(topo_hdl_t *thp, int *errp, boolean_t need_force)
{
	uuid_t uuid;
	char *ustr = NULL;

	topo_hdl_lock(thp);
	if (thp->th_uuid != NULL) {
		*errp = ETOPO_HDL_UUID;
		topo_hdl_unlock(thp);
		return (NULL);
	}

	if ((thp->th_uuid = topo_hdl_zalloc(thp, TOPO_UUID_SIZE)) == NULL) {
		*errp = ETOPO_NOMEM;
		topo_dprintf(thp, TOPO_DBG_ERR, "unable to allocate uuid: %s\n",
		    topo_strerror(*errp));
		topo_hdl_unlock(thp);
		return (NULL);
	}

	uuid_generate(uuid);
	uuid_unparse(uuid, thp->th_uuid);
	if ((ustr = topo_hdl_strdup(thp, thp->th_uuid)) == NULL) {
		*errp = ETOPO_NOMEM;
		topo_hdl_unlock(thp);
		return (NULL);
	}

	thp->th_di = di_init("/", (need_force ? DINFOFORCE : 0) |
	    DINFOSUBTREE | DINFOMINOR | DINFOPROP | DINFOPATH);
	thp->th_pi = di_prom_init();

	if (topo_tree_enum_all(thp) < 0) {
		topo_dprintf(thp, TOPO_DBG_ERR, "enumeration failure: %s\n",
		    topo_hdl_errmsg(thp));
		if (topo_hdl_errno(thp) == ETOPO_ENUM_FATAL) {
			*errp = thp->th_errno;

			if (thp->th_di != DI_NODE_NIL) {
				di_fini(thp->th_di);
				thp->th_di = DI_NODE_NIL;
			}
			if (thp->th_pi != DI_PROM_HANDLE_NIL) {
				di_prom_fini(thp->th_pi);
				thp->th_pi = DI_PROM_HANDLE_NIL;
			}

			topo_hdl_strfree(thp, ustr);
			topo_hdl_unlock(thp);
			return (NULL);
		}
	}

	if (thp->th_ipmi != NULL &&
	    ipmi_sdr_changed(thp->th_ipmi) &&
	    ipmi_sdr_refresh(thp->th_ipmi) != 0) {
		topo_dprintf(thp, TOPO_DBG_ERR,
		    "failed to refresh IPMI sdr repository: %s\n",
		    ipmi_errmsg(thp->th_ipmi));
	}

	topo_hdl_unlock(thp);

	return (ustr);
}

/*ARGSUSED*/
static char *
topo_snap_log_create(topo_hdl_t *thp, const char *uuid, int *errp)
{
	return ((char *)uuid);
}

/*ARGSUSED*/
static int
fac_walker(topo_hdl_t *thp, tnode_t *node, void *arg)
{
	int err;
	nvlist_t *out;

	if (topo_method_supported(node, TOPO_METH_FAC_ENUM, 0)) {
		/*
		 * If the facility enumeration method fails, note the failure,
		 * but continue on with the walk.
		 */
		if (topo_method_invoke(node, TOPO_METH_FAC_ENUM, 0, NULL, &out,
		    &err) != 0) {
			topo_dprintf(thp, TOPO_DBG_ERR,
			    "facility enumeration method failed on node %s=%d "
			    "(%s)\n", topo_node_name(node),
			    topo_node_instance(node), topo_strerror(err));
		}
	}
	return (TOPO_WALK_NEXT);
}

/*
 * Return snapshot id
 */
char *
topo_snap_hold(topo_hdl_t *thp, const char *uuid, int *errp)
{
	topo_walk_t *twp;

	if (thp == NULL)
		return (NULL);

	if (uuid == NULL) {
		char *ret;

		ret = topo_snap_create(thp, errp, B_TRUE);

		/*
		 * Now walk the tree and invoke any facility enumeration methods
		 */
		if (ret != NULL && getzoneid() == 0) {
			if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC,
			    fac_walker, (void *)0, errp)) == NULL) {
				return (ret);
			}
			(void) topo_walk_step(twp, TOPO_WALK_CHILD);
			topo_walk_fini(twp);
		}
		return (ret);
	}
	return (topo_snap_log_create(thp, uuid, errp));
}

/*
 * Return snapshot id. This variant calls di_init() without DINFOFORCE,
 * and is intended for use on DR events where there is no need to force
 * attach of all devices.
 */
char *
topo_snap_hold_no_forceload(topo_hdl_t *thp, const char *uuid, int *errp)
{
	topo_walk_t *twp;

	if (thp == NULL)
		return (NULL);

	if (uuid == NULL) {
		char *ret;

		ret = topo_snap_create(thp, errp, B_FALSE);

		/*
		 * Now walk the tree and invoke any facility enumeration methods
		 */
		if (ret != NULL) {
			if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC,
			    fac_walker, (void *)0, errp)) == NULL) {
				return (ret);
			}
			(void) topo_walk_step(twp, TOPO_WALK_CHILD);
			topo_walk_fini(twp);
		}
		return (ret);
	}
	return (topo_snap_log_create(thp, uuid, errp));
}

/*ARGSUSED*/
static int
topo_walk_destroy(topo_hdl_t *thp, tnode_t *node, void *notused)
{
	tnode_t *cnode;

	cnode = topo_child_first(node);

	if (cnode != NULL)
		return (TOPO_WALK_NEXT);

	topo_node_unbind(node);

	return (TOPO_WALK_NEXT);
}

static void
topo_snap_destroy(topo_hdl_t *thp)
{
	int i;
	ttree_t *tp;
	topo_walk_t *twp;
	tnode_t *root;
	topo_nodehash_t *nhp;
	topo_mod_t *mod;

	for (tp = topo_list_next(&thp->th_trees); tp != NULL;
	    tp = topo_list_next(tp)) {

		root = tp->tt_root;
		twp = tp->tt_walk;
		/*
		 * Clean-up tree nodes from the bottom-up
		 */
		if ((twp->tw_node = topo_child_first(root)) != NULL) {
			twp->tw_cb = topo_walk_destroy;
			topo_node_hold(root);
			topo_node_hold(twp->tw_node); /* released at walk end */
			(void) topo_walk_bottomup(twp, TOPO_WALK_CHILD);
			topo_node_rele(root);
		}

		/*
		 * Tidy-up the root node
		 */
		while ((nhp = topo_list_next(&root->tn_children)) != NULL) {
			for (i = 0; i < nhp->th_arrlen; i++) {
				assert(nhp->th_nodearr[i] == NULL);
			}
			mod = nhp->th_enum;
			topo_mod_strfree(mod, nhp->th_name);
			topo_mod_free(mod, nhp->th_nodearr,
			    nhp->th_arrlen * sizeof (tnode_t *));
			topo_list_delete(&root->tn_children, nhp);
			topo_mod_free(mod, nhp, sizeof (topo_nodehash_t));
			topo_mod_rele(mod);
		}

	}

	/*
	 * Clean-up our cached devinfo and prom tree handles.
	 */
	if (thp->th_di != DI_NODE_NIL) {
		di_fini(thp->th_di);
		thp->th_di = DI_NODE_NIL;
	}
	if (thp->th_pi != DI_PROM_HANDLE_NIL) {
		di_prom_fini(thp->th_pi);
		thp->th_pi = DI_PROM_HANDLE_NIL;
	}


	if (thp->th_uuid != NULL) {
		topo_hdl_free(thp, thp->th_uuid, TOPO_UUID_SIZE);
		thp->th_uuid = NULL;
	}
}

void
topo_snap_release(topo_hdl_t *thp)
{
	if (thp == NULL)
		return;

	topo_hdl_lock(thp);
	topo_snap_destroy(thp);
	topo_hdl_unlock(thp);
}

topo_walk_t *
topo_walk_init(topo_hdl_t *thp, const char *scheme, topo_walk_cb_t cb_f,
    void *pdata, int *errp)
{
	ttree_t *tp;
	topo_walk_t *wp;

	for (tp = topo_list_next(&thp->th_trees); tp != NULL;
	    tp = topo_list_next(tp)) {
		if (strcmp(scheme, tp->tt_scheme) == 0) {

			/*
			 * Hold the root node and start walk at the first
			 * child node
			 */
			assert(tp->tt_root != NULL);

			if ((wp = topo_node_walk_init(thp, NULL, tp->tt_root,
			    cb_f, pdata, errp)) == NULL) /* errp set */
				return (NULL);

			return (wp);
		}
	}

	*errp = ETOPO_WALK_NOTFOUND;
	return (NULL);
}

static int
step_child(tnode_t *cnp, topo_walk_t *wp, int flag, int bottomup)
{
	int status;
	tnode_t *nnp;

	nnp = topo_child_first(cnp);

	if (nnp == NULL) {
		topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
		    "step_child: TOPO_WALK_TERMINATE for %s=%d\n",
		    cnp->tn_name, cnp->tn_instance);
		return (TOPO_WALK_TERMINATE);
	}

	topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
	    "step_child: walk through node %s=%d to %s=%d\n",
	    cnp->tn_name, cnp->tn_instance, nnp->tn_name, nnp->tn_instance);

	topo_node_hold(nnp); /* released on return from walk_step */
	wp->tw_node = nnp;
	if (bottomup == 1)
		status = topo_walk_bottomup(wp, flag);
	else
		status = topo_walk_step(wp, flag);

	return (status);
}

static int
step_sibling(tnode_t *cnp, topo_walk_t *wp, int flag, int bottomup)
{
	int status;
	tnode_t *nnp;

	nnp = topo_child_next(cnp->tn_parent, cnp);

	if (nnp == NULL) {
		topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
		    "step_sibling: TOPO_WALK_TERMINATE for %s=%d\n",
		    cnp->tn_name, cnp->tn_instance);
		return (TOPO_WALK_TERMINATE);
	}

	topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
	    "step_sibling: through sibling node %s=%d to %s=%d\n",
	    cnp->tn_name, cnp->tn_instance, nnp->tn_name, nnp->tn_instance);

	topo_node_hold(nnp); /* released on return from walk_step */
	wp->tw_node = nnp;
	if (bottomup == 1)
		status = topo_walk_bottomup(wp, flag);
	else
		status = topo_walk_step(wp, flag);

	return (status);
}

int
topo_walk_byid(topo_walk_t *wp, const char *name, topo_instance_t inst)
{
	int status;
	tnode_t *nnp, *cnp;

	cnp = wp->tw_node;
	nnp = topo_node_lookup(cnp, name, inst);
	if (nnp == NULL)
		return (TOPO_WALK_TERMINATE);

	topo_node_hold(nnp);
	wp->tw_node = nnp;
	if (wp->tw_mod != NULL)
		status = wp->tw_cb(wp->tw_mod, nnp, wp->tw_pdata);
	else
		status = wp->tw_cb(wp->tw_thp, nnp, wp->tw_pdata);
	topo_node_rele(nnp);
	wp->tw_node = cnp;

	return (status);
}

int
topo_walk_bysibling(topo_walk_t *wp, const char *name, topo_instance_t inst)
{
	int status;
	tnode_t *cnp, *pnp;

	cnp = wp->tw_node;
	pnp = topo_node_parent(cnp);
	assert(pnp != NULL);

	topo_node_hold(pnp);
	wp->tw_node = pnp;
	status = topo_walk_byid(wp, name, inst);
	topo_node_rele(pnp);
	wp->tw_node = cnp;

	return (status);
}

int
topo_walk_step(topo_walk_t *wp, int flag)
{
	int status;
	tnode_t *cnp = wp->tw_node;

	if (flag != TOPO_WALK_CHILD && flag != TOPO_WALK_SIBLING) {
		topo_node_rele(cnp);
		return (TOPO_WALK_ERR);
	}

	/*
	 * No more nodes to walk
	 */
	if (cnp == NULL) {
		topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
		    "walk_step terminated\n");
		topo_node_rele(cnp);
		return (TOPO_WALK_TERMINATE);
	}


	if (wp->tw_mod != NULL)
		status = wp->tw_cb(wp->tw_mod, cnp, wp->tw_pdata);
	else
		status = wp->tw_cb(wp->tw_thp, cnp, wp->tw_pdata);

	/*
	 * Walker callback says we're done
	 */
	if (status != TOPO_WALK_NEXT) {
		topo_node_rele(cnp);
		return (status);
	}

	if (flag == TOPO_WALK_CHILD)
		status = step_child(cnp, wp, flag, 0);
	else
		status = step_sibling(cnp, wp, flag, 0);

	/*
	 * No more nodes in this hash, skip to next node hash by stepping
	 * to next sibling (child-first walk) or next child (sibling-first
	 * walk).
	 */
	if (status == TOPO_WALK_TERMINATE) {
		if (flag == TOPO_WALK_CHILD)
			status = step_sibling(cnp, wp, flag, 0);
		else
			status = step_child(cnp, wp, flag, 0);
	}

	topo_node_rele(cnp); /* done with current node */

	return (status);
}

void
topo_walk_fini(topo_walk_t *wp)
{
	if (wp == NULL)
		return;

	topo_node_rele(wp->tw_root);

	topo_hdl_free(wp->tw_thp, wp, sizeof (topo_walk_t));
}

int
topo_walk_bottomup(topo_walk_t *wp, int flag)
{
	int status;
	tnode_t *cnp;

	if (wp == NULL)
		return (TOPO_WALK_ERR);

	cnp = wp->tw_node;
	if (flag != TOPO_WALK_CHILD && flag != TOPO_WALK_SIBLING) {
		topo_node_rele(cnp);
		return (TOPO_WALK_ERR);
	}

	/*
	 * End of the line
	 */
	if (cnp == NULL) {
		topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
		    "walk_bottomup terminated\n");
		topo_node_rele(cnp);
		return (TOPO_WALK_TERMINATE);
	}

	topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
	    "%s walk_bottomup through node %s=%d\n",
	    (flag == TOPO_WALK_CHILD ? "TOPO_WALK_CHILD" : "TOPO_WALK_SIBLING"),
	    cnp->tn_name, cnp->tn_instance);

	if (flag == TOPO_WALK_CHILD)
		status = step_child(cnp, wp, flag, 1);
	else
		status = step_sibling(cnp, wp, flag, 1);

	/*
	 * At a leaf, run the callback
	 */
	if (status == TOPO_WALK_TERMINATE) {
		if ((status = wp->tw_cb(wp->tw_thp, cnp, wp->tw_pdata))
		    != TOPO_WALK_NEXT) {
			topo_node_rele(cnp);
			return (status);
		}
	}

	/*
	 * Try next child or sibling
	 */
	if (status == TOPO_WALK_NEXT) {
		if (flag == TOPO_WALK_CHILD)
			status = step_sibling(cnp, wp, flag, 1);
		else
			status = step_child(cnp, wp, flag, 1);
	}

	topo_node_rele(cnp); /* done with current node */

	return (status);
}

di_node_t
topo_hdl_devinfo(topo_hdl_t *thp)
{
	return (thp == NULL ? DI_NODE_NIL : thp->th_di);
}

di_prom_handle_t
topo_hdl_prominfo(topo_hdl_t *thp)
{
	return (thp == NULL ? DI_PROM_HANDLE_NIL : thp->th_pi);
}