view usr/src/lib/fm/topo/modules/common/ipmi/ipmi_enum.c @ 25635:ce2b70e7aab0

[illumos-gate merge] commit 0a554e9f2c0d440dc40a97fae2d18f1d428ca786 13404 man page spelling errors commit 9f76c6ed5b6ee0cc0bf631daca15ac3dc5fc70c4 13400 zfs-tests: implicit conversion from 'enum dmu_objset_type' to 'enum lzc_dataset_type' commit ef96fc31fc4f4306719704352d5c3e33573c039f 13399 zfs: error: implicit conversion from 'boolean_t' to 'ds_hold_flags_t' commit 56870e8c76c2675bcef1fcee5d519585ce9c768e 13393 cheetah: case value '47616' not in enumerated type commit 8247326397b1a16f37e70cf13f5b7a4f50d06712 13403 zfs: symbol 'g_zfs' is multiply-defined commit 436b964b19ef06803ad9165542d80d9d731d6486 13402 zpool: symbol 'g_zfs' is multiply-defined commit 99308ed0417a2b8ab73c5856a8a5345ce2a7aea7 13396 PoolsExecption typo in resource pools javadoc commit 1575b751c16622553e958c1e5c45e59c86b15c6e 13392 px: case value '3' not in enumerated type commit 9b0429a10eec9313ec782d8421272aff70adbfdc 13339 Add support for Hygon Dhyana Family 18h processor commit d20422bd742384b77102bb3bd09e0dc4b7372e50 13351 loader: vbe_find_mode_xydm() is using wrong safety and iteration is buggy commit 174b8e455f9a6974e69fa4e28792580acde0892d 13311 uptime(1) dazed and confused for a minute after boot commit f816551bb187d104fbf2757703d7a5d2189a3a18 13401 eeprom: 'lv' may be used uninitialized in this function commit 5e96da73c99d9d17ff5a58b793fff2ab6dcadf25 13391 fm: build errors with gcc 7 on SPARC commit 58b55f701e285559e4799354996fd284238ed0d4 13398 libstand: xdrproc_t should return bool commit c6a28d7650029501a356f7b75b2a10a5c4430cef 13394 fhc: case value '4294967295' not in enumerated type commit 58d4b16fe601073f2408de78e3db7e9bfa9abfd2 13355 remove topo module warning gags commit 1473b8d60e902819558a8b0e8a257eb0d754c3c3 13388 ZFS list bookmark not working on zvols commit 4bba12ca5cd6f92aaf0d4c0d19d05528110bc095 13368 libbe_py should support temporary BE activation commit a92282e44f968185a6bba094d1e5fece2da819cf 13376 fm: variable may be used uninitialized commit 8b1df8bf71b7b62e7e4d46fe6b457d4d6447b2b8 13367 beadm activate -t should not promote new BE datasets commit 9704bf7fb82e71b685e194a967937ff03843e73a 13317 Decrease contention on dn_struct_rwlock commit 88a08813800ed7ba7c927986421cee437f7f2233 13363 ctfconvert could support more granular ignore for missing debug data commit 3dd4cd56e7843e01a8ab147a0d102cd4f6d732c1 13342 ctfconvert could encode _Float128 for 32-bit objects commit 73197b540cc5f0434c409b68ca9e1a514a6ce91b 13336 ctfconvert should be able to unconditionally attempt conversion commit dd4422524768709a579a2a93a10c78a88a6b0ecb 13280 CTF: provide option to truncate and continue Conflicts & other fixes (with help from Jason King <jbk@joyent.com>): usr/src/lib/fm/topo/modules/common/ipmi/ipmi_enum.c usr/src/lib/libctf/common/ctf_convert.c usr/src/lib/libctf/common/ctf_lib.c usr/src/lib/libctf/common/libctf.h usr/src/lib/libproc/common/Psymtab.c usr/src/man/man1/ld.so.1.1 usr/src/man/man4/process.4
author Dan McDonald <danmcd@joyent.com>
date Mon, 04 Jan 2021 14:49:49 -0500
parents 80b59ed2beec a87c414f8206
children
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) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2019 Joyent, Inc.
 * Copyright 2019 by Western Digital Corporation
 */

#include <assert.h>
#include <fm/libtopo.h>
#include <fm/topo_mod.h>
#include <sys/fm/protocol.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define	TOPO_PGROUP_IPMI		"ipmi"
#define	TOPO_PROP_IPMI_ENTITY_REF	"entity_ref"
#define	TOPO_PROP_IPMI_ENTITY_PRESENT	"entity_present"
#define	FAC_PROV_IPMI			"fac_prov_ipmi"

typedef struct ipmi_enum_data {
	topo_mod_t		*ed_mod;
	tnode_t			*ed_pnode;
	const char		*ed_name;
	char			*ed_label;
	uint8_t			ed_entity;
	topo_instance_t		ed_instance;
	ipmi_sdr_fru_locator_t	*ed_frusdr;
} ipmi_enum_data_t;

static int ipmi_present(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
    nvlist_t **);
static int ipmi_unusable(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
    nvlist_t **);
static int ipmi_enum(topo_mod_t *, tnode_t *, const char *,
    topo_instance_t, topo_instance_t, void *, void *);
static int ipmi_post_process(topo_mod_t *, tnode_t *);

extern int ipmi_fru_label(topo_mod_t *mod, tnode_t *node,
    topo_version_t vers, nvlist_t *in, nvlist_t **out);

extern int ipmi_fru_fmri(topo_mod_t *mod, tnode_t *node,
    topo_version_t vers, nvlist_t *in, nvlist_t **out);

static const topo_method_t ipmi_methods[] = {
	{ TOPO_METH_PRESENT, TOPO_METH_PRESENT_DESC,
	    TOPO_METH_PRESENT_VERSION0, TOPO_STABILITY_INTERNAL, ipmi_present },
	{ TOPO_METH_UNUSABLE, TOPO_METH_UNUSABLE_DESC,
	    TOPO_METH_UNUSABLE_VERSION, TOPO_STABILITY_INTERNAL,
	    ipmi_unusable },
	{ "ipmi_fru_label", "Property method", 0,
	    TOPO_STABILITY_INTERNAL, ipmi_fru_label},
	{ "ipmi_fru_fmri", "Property method", 0,
	    TOPO_STABILITY_INTERNAL, ipmi_fru_fmri},
	{ TOPO_METH_SENSOR_FAILURE, TOPO_METH_SENSOR_FAILURE_DESC,
	    TOPO_METH_SENSOR_FAILURE_VERSION, TOPO_STABILITY_INTERNAL,
	    topo_method_sensor_failure },
	{ NULL }
};

const topo_modops_t ipmi_ops = { ipmi_enum, NULL };

const topo_modinfo_t ipmi_info =
	{ "ipmi", FM_FMRI_SCHEME_HC, TOPO_VERSION, &ipmi_ops };

/* Common code used by topo methods below to find an IPMI entity */
static int
ipmi_find_entity(topo_mod_t *mod, tnode_t *tn, ipmi_handle_t **ihpp,
    ipmi_entity_t **epp, char **namep, ipmi_sdr_t **sdrpp)
{
	ipmi_handle_t *ihp;
	ipmi_entity_t *ep;
	int err;
	char *name = NULL, **names;
	ipmi_sdr_t *sdrp = NULL;
	uint_t nelems, i;

	*ihpp = NULL;
	*epp = NULL;
	*namep = NULL;
	*sdrpp = NULL;

	if ((ihp = topo_mod_ipmi_hold(mod)) == NULL)
		return (topo_mod_seterrno(mod, ETOPO_METHOD_UNKNOWN));

	ep = topo_node_getspecific(tn);
	if (ep != NULL) {
		*ihpp = ihp;
		*epp = ep;
		return (0);
	}

	if (topo_prop_get_string(tn, TOPO_PGROUP_IPMI,
	    TOPO_PROP_IPMI_ENTITY_PRESENT, &name, &err) == 0) {
		/*
		 * Some broken IPMI implementations don't export correct
		 * entities, so referring to an entity isn't sufficient.
		 * For these platforms, we allow the XML to specify a
		 * single SDR record that represents the current present
		 * state.
		 */
		sdrp = ipmi_sdr_lookup(ihp, name);
	} else {
		if (topo_prop_get_string_array(tn, TOPO_PGROUP_IPMI,
		    TOPO_PROP_IPMI_ENTITY_REF, &names, &nelems, &err) != 0) {
			/*
			 * Not all nodes have an entity_ref attribute.
			 * For these cases, return ENOTSUP so that we
			 * fall back to the default hc presence
			 * detection.
			 */
			topo_mod_ipmi_rele(mod);
			return (topo_mod_seterrno(mod, ETOPO_METHOD_NOTSUP));
		}

		for (i = 0; i < nelems; i++) {
			if ((ep = ipmi_entity_lookup_sdr(ihp, names[i]))
			    != NULL) {
				name = names[i];
				names[i] = NULL;
				break;
			}
		}

		for (i = 0; i < nelems; i++)
			topo_mod_strfree(mod, names[i]);
		topo_mod_free(mod, names, (nelems * sizeof (char *)));

		if (ep == NULL) {
			topo_mod_dprintf(mod,
			    "Failed to get present state of %s=%d\n",
			    topo_node_name(tn), topo_node_instance(tn));
			topo_mod_ipmi_rele(mod);
			return (-1);
		}
		topo_node_setspecific(tn, ep);
	}

	*ihpp = ihp;
	*namep = name;
	*sdrpp = sdrp;
	return (0);
}

/*
 * Determine if the entity is present.
 */
/*ARGSUSED*/
static int
ipmi_present(topo_mod_t *mod, tnode_t *tn, topo_version_t version,
    nvlist_t *in, nvlist_t **out)
{
	ipmi_handle_t *ihp;
	ipmi_entity_t *ep;
	char *name;
	ipmi_sdr_t *sdrp;
	int err;
	boolean_t present = B_FALSE;
	nvlist_t *nvl;

	err = ipmi_find_entity(mod, tn, &ihp, &ep, &name, &sdrp);
	if (err != 0)
		return (err);

	if (ep != NULL) {
		if (ipmi_entity_present(ihp, ep, &present) != 0) {
			topo_mod_dprintf(mod,
			    "ipmi_entity_present() failed: %s",
			    ipmi_errmsg(ihp));
			topo_mod_strfree(mod, name);
			topo_mod_ipmi_rele(mod);
			return (-1);
		}

		topo_mod_dprintf(mod,
		    "ipmi_entity_present(%d, %d) = %d\n", ep->ie_type,
		    ep->ie_instance, present);
	} else if (sdrp != NULL) {
		if (ipmi_entity_present_sdr(ihp, sdrp, &present) != 0) {
			topo_mod_dprintf(mod,
			    "Failed to get present state of %s (%s)\n",
			    name, ipmi_errmsg(ihp));
			topo_mod_strfree(mod, name);
			topo_mod_ipmi_rele(mod);
			return (-1);
		}

		topo_mod_dprintf(mod, "ipmi_entity_present_sdr(%s) = %d\n",
		    name, present);
	}

	topo_mod_strfree(mod, name);
	topo_mod_ipmi_rele(mod);

	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0)
		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));

	if (nvlist_add_uint32(nvl, TOPO_METH_PRESENT_RET, present) != 0) {
		nvlist_free(nvl);
		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
	}

	*out = nvl;

	return (0);
}

/*
 * Check whether an IPMI entity is a sensor that is unavailable
 */
static int
ipmi_check_sensor(ipmi_handle_t *ihp, ipmi_entity_t *ep, const char *name,
    ipmi_sdr_t *sdrp, void *data)
{
	ipmi_sdr_full_sensor_t *fsp;
	ipmi_sdr_compact_sensor_t *csp;
	uint8_t sensor_number;
	ipmi_sensor_reading_t *reading;

	switch (sdrp->is_type) {
	case IPMI_SDR_TYPE_FULL_SENSOR:
		fsp = (ipmi_sdr_full_sensor_t *)sdrp->is_record;
		sensor_number = fsp->is_fs_number;
		break;

	case IPMI_SDR_TYPE_COMPACT_SENSOR:
		csp = (ipmi_sdr_compact_sensor_t *)sdrp->is_record;
		sensor_number = csp->is_cs_number;
		break;

	default:
		return (0);
	}

	reading = ipmi_get_sensor_reading(ihp, sensor_number);
	if (reading != NULL && reading->isr_state_unavailable)
		return (1);

	return (0);
}

/*
 * Determine if the entity is unusable
 */
/*ARGSUSED*/
static int
ipmi_unusable(topo_mod_t *mod, tnode_t *tn, topo_version_t version,
    nvlist_t *in, nvlist_t **out)
{
	ipmi_handle_t *ihp;
	ipmi_entity_t *ep;
	char *name;
	ipmi_sdr_t *sdrp;
	int err;
	boolean_t unusable = B_FALSE;
	nvlist_t *nvl;

	err = ipmi_find_entity(mod, tn, &ihp, &ep, &name, &sdrp);
	if (err != 0)
		return (err);

	/*
	 * Check whether the IPMI presented us with an entity for a
	 * sensor that is unavailable.
	 */
	if (ep != NULL) {
		unusable = (ipmi_entity_iter_sdr(ihp, ep, ipmi_check_sensor,
		    NULL) != 0);
	} else if (sdrp != NULL) {
		unusable = (ipmi_check_sensor(ihp, NULL, NULL, sdrp,
		    NULL) != 0);
	}

	topo_mod_strfree(mod, name);
	topo_mod_ipmi_rele(mod);

	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0)
		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));

	if (nvlist_add_uint32(nvl, TOPO_METH_UNUSABLE_RET, unusable) != 0) {
		nvlist_free(nvl);
		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
	}

	*out = nvl;

	return (0);
}

/*
 * This determines if the entity has a FRU locator record set, in which case we
 * treat this as a FRU, even if it's part of an association.
 */
/*ARGSUSED*/
static int
ipmi_check_sdr(ipmi_handle_t *ihp, ipmi_entity_t *ep, const char *name,
    ipmi_sdr_t *sdrp, void *data)
{
	ipmi_enum_data_t *edp = data;

	if (sdrp->is_type == IPMI_SDR_TYPE_FRU_LOCATOR)
		edp->ed_frusdr = (ipmi_sdr_fru_locator_t *)sdrp->is_record;

	return (0);
}

/*
 * Main entity enumerator.  If we find a matching entity type, then instantiate
 * a topo node.
 */
static int
ipmi_check_entity(ipmi_handle_t *ihp, ipmi_entity_t *ep, void *data)
{
	ipmi_enum_data_t *edp = data;
	ipmi_enum_data_t cdata;
	tnode_t *pnode = edp->ed_pnode;
	topo_mod_t *mod = edp->ed_mod;
	topo_mod_t *fmod = topo_mod_getspecific(mod);
	nvlist_t *auth, *fmri;
	tnode_t *tn;
	topo_pgroup_info_t pgi;
	char *frudata = NULL, *part = NULL, *rev = NULL, *serial = NULL;
	ipmi_fru_prod_info_t fruprod = {0};
	ipmi_fru_brd_info_t frubrd = {0};
	int err;
	const char *labelname;
	char label[64];
	size_t len;

	/*
	 * Some questionable IPMI implementations group psu and fan entities
	 * under things like motherboard or chassis entities.  So even if this
	 * entity type isn't typically associated with fans and psus, if it has
	 * children, then regardless of the type we need to decend down and
	 * iterate over them.
	 */
	if (ep->ie_type != edp->ed_entity) {
		if (ep->ie_children != 0 &&
		    ipmi_entity_iter_children(ihp, ep, ipmi_check_entity,
		    data) != 0)
			return (1);
		return (0);
	}

	/*
	 * The purpose of power and cooling domains is to group psus and fans
	 * together.  Unfortunately, some broken IPMI implementations declare
	 * domains that don't contain other elements.  Since the end goal is to
	 * only enumerate psus and fans, we'll just ignore such elements.
	 */
	if ((ep->ie_type == IPMI_ET_POWER_DOMAIN ||
	    ep->ie_type == IPMI_ET_COOLING_DOMAIN) &&
	    ep->ie_children == 0)
		return (0);

	if ((auth = topo_mod_auth(mod, pnode)) == NULL) {
		topo_mod_dprintf(mod, "topo_mod_auth() failed: %s",
		    topo_mod_errmsg(mod));
		return (1);
	}

	/*
	 * Determine if there's a FRU record associated with this entity.  If
	 * so, then read in the FRU identity info so that it can be included
	 * in the authority portion of the FMRI.
	 *
	 * topo_mod_hcfmri() will safely except NULL values for the part,
	 * rev and serial params, so we opt to simply drive on in the face of
	 * any strdup failures.
	 */
	edp->ed_frusdr = NULL;
	(void) ipmi_entity_iter_sdr(ihp, ep, ipmi_check_sdr, edp);
	if (edp->ed_frusdr != NULL &&
	    ipmi_fru_read(ihp, edp->ed_frusdr, &frudata) != -1) {
		if (ipmi_fru_parse_product(ihp, frudata, &fruprod) == 0) {
			part = strdup(fruprod.ifpi_part_number);
			rev = strdup(fruprod.ifpi_product_version);
			serial = strdup(fruprod.ifpi_product_serial);
		} else if (ipmi_fru_parse_board(ihp, frudata, &frubrd) == 0) {
			part = strdup(frubrd.ifbi_part_number);
			serial = strdup(frubrd.ifbi_product_serial);
		}
	}
	free(frudata);

	if ((fmri = topo_mod_hcfmri(mod, pnode, FM_HC_SCHEME_VERSION,
	    edp->ed_name, edp->ed_instance, NULL, auth, part, rev,
	    serial)) == NULL) {
		nvlist_free(auth);
		free(part);
		free(rev);
		free(serial);
		topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
		    topo_mod_errmsg(mod));
		return (1);
	}
	nvlist_free(auth);
	free(part);
	free(rev);
	free(serial);

	if ((tn = topo_node_bind(mod, pnode, edp->ed_name,
	    edp->ed_instance, fmri)) == NULL) {
		nvlist_free(fmri);
		topo_mod_dprintf(mod, "topo_node_bind() failed: %s",
		    topo_mod_errmsg(mod));
		return (1);
	}

	/*
	 * We inherit our label from our parent, appending our label in the
	 * process.  This results in defaults labels of the form "FM 1 FAN 0"
	 * by default when given a hierarchy.
	 */
	if (edp->ed_label != NULL)
		(void) snprintf(label, sizeof (label), "%s ", edp->ed_label);
	else
		label[0] = '\0';

	switch (edp->ed_entity) {
	case IPMI_ET_POWER_DOMAIN:
		labelname = "PM";
		break;

	case IPMI_ET_PSU:
		labelname = "PSU";
		break;

	case IPMI_ET_COOLING_DOMAIN:
		labelname = "FM";
		break;

	case IPMI_ET_FAN:
		labelname = "FAN";
		break;

	default:
		topo_mod_dprintf(mod, "unknown entity type, %u: cannot set "
		    "label name", edp->ed_entity);
		nvlist_free(fmri);
		return (1);
	}

	len = strlen(label);
	(void) snprintf(label + len, sizeof (label) - len, "%s %d",
	    labelname, edp->ed_instance);

	nvlist_free(fmri);
	edp->ed_instance++;

	if (topo_node_label_set(tn, label, &err) != 0) {
		topo_mod_dprintf(mod, "failed to set label: %s\n",
		    topo_strerror(err));
		return (1);
	}

	/*
	 * Store IPMI entity details as properties on the node
	 */
	pgi.tpi_name = TOPO_PGROUP_IPMI;
	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
	pgi.tpi_version = TOPO_VERSION;
	if (topo_pgroup_create(tn, &pgi, &err) != 0) {
		if (err != ETOPO_PROP_DEFD) {
			topo_mod_dprintf(mod, "failed to create propgroup "
			    "%s: %s\n", TOPO_PGROUP_IPMI, topo_strerror(err));
			return (1);
		}
	}

	/*
	 * Add properties to contain the IPMI entity id and instance.  This
	 * will be used by the fac_prov_ipmi module to discover and enumerate
	 * facility nodes for any associated sensors.
	 */
	if (topo_prop_set_uint32(tn, TOPO_PGROUP_IPMI, TOPO_PROP_IPMI_ENTITY_ID,
	    TOPO_PROP_IMMUTABLE, ep->ie_type, &err) != 0 ||
	    topo_prop_set_uint32(tn, TOPO_PGROUP_IPMI,
	    TOPO_PROP_IPMI_ENTITY_INST, TOPO_PROP_IMMUTABLE, ep->ie_instance,
	    &err) != 0) {
		topo_mod_dprintf(mod, "failed to add ipmi properties (%s)",
		    topo_strerror(err));
		return (1);
	}
	if (topo_method_register(mod, tn, ipmi_methods) != 0) {
		topo_mod_dprintf(mod, "topo_method_register() failed: %s",
		    topo_mod_errmsg(mod));
		return (1);
	}

	/*
	 * Invoke the tmo_enum callback from the fac_prov_ipmi module on this
	 * node.  This will have the effect of registering a method on this node
	 * for enumerating sensors.
	 */
	if (fmod == NULL && (fmod = topo_mod_load(mod, FAC_PROV_IPMI,
	    TOPO_VERSION)) == NULL) {
		topo_mod_dprintf(mod, "failed to load %s: %s",
		    FAC_PROV_IPMI, topo_mod_errmsg(mod));
		return (-1);
	}
	topo_mod_setspecific(mod, fmod);

	if (topo_mod_enumerate(fmod, tn, FAC_PROV_IPMI, FAC_PROV_IPMI, 0, 0,
	    NULL) != 0) {
		topo_mod_dprintf(mod, "facility provider enum failed (%s)",
		    topo_mod_errmsg(mod));
		return (1);
	}

	/*
	 * If we are a child of a non-chassis node, and there isn't an explicit
	 * FRU locator record, then propagate the parent's FRU.  Otherwise, set
	 * the FRU to be the same as the resource.
	 */
	if (strcmp(topo_node_name(pnode), CHASSIS) == 0 ||
	    edp->ed_frusdr != NULL) {
		if (topo_node_resource(tn, &fmri, &err) != 0) {
			topo_mod_dprintf(mod, "topo_node_resource() failed: %s",
			    topo_strerror(err));
			(void) topo_mod_seterrno(mod, err);
			return (1);
		}
	} else {
		if (topo_node_fru(pnode, &fmri, NULL, &err) != 0) {
			topo_mod_dprintf(mod, "topo_node_fru() failed: %s",
			    topo_strerror(err));
			(void) topo_mod_seterrno(mod, err);
			return (1);
		}
	}

	if (topo_node_fru_set(tn, fmri, 0, &err) != 0) {
		nvlist_free(fmri);
		topo_mod_dprintf(mod, "topo_node_fru_set() failed: %s",
		    topo_strerror(err));
		(void) topo_mod_seterrno(mod, err);
		return (1);
	}

	topo_node_setspecific(tn, ep);

	nvlist_free(fmri);

	/*
	 * Iterate over children, once for recursive domains and once for
	 * psu/fans.
	 */
	if (ep->ie_children != 0 &&
	    (ep->ie_type == IPMI_ET_POWER_DOMAIN ||
	    ep->ie_type == IPMI_ET_COOLING_DOMAIN)) {
		cdata.ed_mod = edp->ed_mod;
		cdata.ed_pnode = tn;
		cdata.ed_instance = 0;
		cdata.ed_name = edp->ed_name;
		cdata.ed_entity = edp->ed_entity;
		cdata.ed_label = label;

		if (ipmi_entity_iter_children(ihp, ep,
		    ipmi_check_entity, &cdata) != 0)
			return (1);

		switch (cdata.ed_entity) {
		case IPMI_ET_POWER_DOMAIN:
			cdata.ed_entity = IPMI_ET_PSU;
			cdata.ed_name = PSU;
			break;

		case IPMI_ET_COOLING_DOMAIN:
			cdata.ed_entity = IPMI_ET_FAN;
			cdata.ed_name = FAN;
			break;
		}

		if (ipmi_entity_iter_children(ihp, ep,
		    ipmi_check_entity, &cdata) != 0)
			return (1);
	}

	return (0);
}

static const char *
ipmi2toposrc(uint8_t ipmi_ip_src)
{
	char *cfgtype;

	switch (ipmi_ip_src) {
	case (IPMI_LAN_SRC_STATIC):
	case (IPMI_LAN_SRC_BIOS):
		cfgtype = TOPO_NETCFG_TYPE_STATIC;
		break;
	case (IPMI_LAN_SRC_DHCP):
		cfgtype = TOPO_NETCFG_TYPE_DHCP;
		break;
	default:
		cfgtype = TOPO_NETCFG_TYPE_UNKNOWN;
		break;
	}
	return (cfgtype);
}

/*
 * Channel related IPMI commands reserve 4 bits for the channel number.
 */
#define	IPMI_MAX_CHANNEL	0xf

static int
ipmi_enum_sp(topo_mod_t *mod, tnode_t *pnode)
{
	ipmi_handle_t *ihp;
	ipmi_channel_info_t *chinfo;
	ipmi_lan_config_t lancfg = { 0 };
	boolean_t found_lan = B_TRUE;
	char ipv4_addr[INET_ADDRSTRLEN], subnet[INET_ADDRSTRLEN];
	char gateway[INET_ADDRSTRLEN], macaddr[18];
	char ipv6_addr[INET6_ADDRSTRLEN];
	char **ipv6_routes = NULL;
	const char *sp_rev, *ipv4_cfgtype, *ipv6_cfgtype;
	nvlist_t *auth, *fmri;
	tnode_t *sp_node;
	topo_pgroup_info_t pgi;
	topo_ufm_slot_info_t slotinfo = { 0 };
	int err, ch, i, ret = -1;

	if ((ihp = topo_mod_ipmi_hold(mod)) == NULL)
		return (0);

	/*
	 * If we're able to successfully get the service processor version by
	 * issuing a GET_DEVICE_ID IPMI command over the KCS interface, then we
	 * can say with certainty that a service processor exists.  If not,
	 * then either the SP is unresponsive or one isn't present.  In either
	 * case, we bail.
	 */
	if ((sp_rev = ipmi_firmware_version(ihp)) == NULL) {
		topo_mod_dprintf(mod, "failed to query SP");
		topo_mod_ipmi_rele(mod);
		return (0);
	}

	if ((auth = topo_mod_auth(mod, pnode)) == NULL) {
		topo_mod_dprintf(mod, "topo_mod_auth() failed: %s",
		    topo_mod_errmsg(mod));
		/* errno set */
		goto out;
	}
	if ((fmri = topo_mod_hcfmri(mod, pnode, FM_HC_SCHEME_VERSION,
	    SP, 0, NULL, auth, NULL, sp_rev, NULL)) == NULL) {
		nvlist_free(auth);
		topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
		    topo_mod_errmsg(mod));
		/* errno set */
		goto out;
	}
	nvlist_free(auth);

	if ((sp_node = topo_node_bind(mod, pnode, SP, 0, fmri)) == NULL) {
		nvlist_free(fmri);
		topo_mod_dprintf(mod, "topo_node_bind() failed: %s",
		    topo_mod_errmsg(mod));
		/* errno set */
		goto out;
	}
	nvlist_free(fmri);
	fmri = NULL;

	if (topo_node_label_set(sp_node, "service-processor", &err) != 0) {
		topo_mod_dprintf(mod, "failed to set label on %s=%d: %s", SP,
		    0, topo_strerror(err));
		(void) topo_mod_seterrno(mod, err);
		goto out;
	}

	if (topo_node_fru(pnode, &fmri, NULL, &err) != 0 ||
	    topo_node_fru_set(sp_node, fmri, 0, &err) != 0) {
		topo_mod_dprintf(mod, "failed to set FRU on %s=%d: %s", SP, 0,
		    topo_strerror(err));
		nvlist_free(fmri);
		(void) topo_mod_seterrno(mod, err);
		goto out;
	}
	nvlist_free(fmri);

	/*
	 * Create UFM node to capture the SP LOM version
	 */
	slotinfo.usi_version = sp_rev;
	slotinfo.usi_active = B_TRUE;
	slotinfo.usi_mode = TOPO_UFM_SLOT_MODE_NONE;
	if (topo_node_range_create(mod, sp_node, UFM, 0, 0) != 0 ||
	    topo_mod_create_ufm(mod, sp_node,
	    "Baseboard Management Controller firmware", &slotinfo) == NULL) {
		topo_mod_dprintf(mod, "failed to create %s node", UFM);
		goto out;
	}

	/*
	 * Iterate through the channels to find the LAN channel.
	 */
	for (ch = 0; ch <= IPMI_MAX_CHANNEL; ch++) {
		if ((chinfo = ipmi_get_channel_info(ihp, ch)) != NULL &&
		    chinfo->ici_medium == IPMI_MEDIUM_8023LAN) {
			found_lan = B_TRUE;
			break;
		}
	}
	/*
	 * If we found a LAN channel, look up its configuration so that we can
	 * expose it via node properties.
	 */
	if (found_lan != B_TRUE ||
	    ipmi_lan_get_config(ihp, ch, &lancfg) != 0) {
		(void) fprintf(stderr, "failed to get LAN config\n");
		(void) topo_mod_seterrno(mod, EMOD_UNKNOWN);
		goto out;
	}

	pgi.tpi_name = TOPO_PGROUP_NETCFG;
	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
	pgi.tpi_version = TOPO_VERSION;

	if (topo_pgroup_create(sp_node, &pgi, &err) != 0) {
		(void) topo_mod_seterrno(mod, err);
		goto out;
	}

	/* Set MAC address property */
	(void) sprintf(macaddr, "%02x:%02x:%02x:%02x:%02x:%02x",
	    lancfg.ilc_macaddr[0], lancfg.ilc_macaddr[1],
	    lancfg.ilc_macaddr[2], lancfg.ilc_macaddr[3],
	    lancfg.ilc_macaddr[4], lancfg.ilc_macaddr[5]);
	macaddr[17] = '\0';

	if (topo_prop_set_string(sp_node, TOPO_PGROUP_NETCFG,
	    TOPO_PROP_NETCFG_MACADDR, TOPO_PROP_IMMUTABLE, macaddr,
	    &err) != 0) {
		topo_mod_dprintf(mod, "failed to set properties on %s=%d: %s",
		    SP, 0, topo_strerror(err));
		(void) topo_mod_seterrno(mod, err);
		goto out;
	}

	/* Set VLAN ID property, if VLAN is enabled */
	if (lancfg.ilc_vlan_enabled == B_TRUE &&
	    topo_prop_set_uint32(sp_node, TOPO_PGROUP_NETCFG,
	    TOPO_PROP_NETCFG_VLAN_ID, TOPO_PROP_IMMUTABLE, lancfg.ilc_vlan_id,
	    &err) != 0) {
		topo_mod_dprintf(mod, "failed to set properties on %s=%d: %s",
		    SP, 0, topo_strerror(err));
		(void) topo_mod_seterrno(mod, err);
		goto out;
	}

	/* Set IPv4 configuration properties if IPv4 is enabled */
	if (lancfg.ilc_ipv4_enabled == B_TRUE &&
	    (inet_ntop(AF_INET, &lancfg.ilc_ipaddr, ipv4_addr,
	    sizeof (ipv4_addr)) == NULL ||
	    inet_ntop(AF_INET, &lancfg.ilc_subnet, subnet,
	    sizeof (subnet)) == NULL ||
	    inet_ntop(AF_INET, &lancfg.ilc_gateway_addr, gateway,
	    sizeof (gateway)) == NULL)) {
		(void) fprintf(stderr, "failed to convert IP addresses: %s\n",
		    strerror(errno));
		(void) topo_mod_seterrno(mod, EMOD_UNKNOWN);
		goto out;
	}
	ipv4_cfgtype = ipmi2toposrc(lancfg.ilc_ipaddr_source);
	if (lancfg.ilc_ipv4_enabled == B_TRUE &&
	    (topo_prop_set_string(sp_node, TOPO_PGROUP_NETCFG,
	    TOPO_PROP_NETCFG_IPV4_ADDR, TOPO_PROP_IMMUTABLE, ipv4_addr,
	    &err) != 0 ||
	    topo_prop_set_string(sp_node, TOPO_PGROUP_NETCFG,
	    TOPO_PROP_NETCFG_IPV4_SUBNET, TOPO_PROP_IMMUTABLE, subnet,
	    &err) != 0 ||
	    topo_prop_set_string(sp_node, TOPO_PGROUP_NETCFG,
	    TOPO_PROP_NETCFG_IPV4_GATEWAY, TOPO_PROP_IMMUTABLE, gateway,
	    &err) != 0 ||
	    topo_prop_set_string(sp_node, TOPO_PGROUP_NETCFG,
	    TOPO_PROP_NETCFG_IPV4_TYPE, TOPO_PROP_IMMUTABLE, ipv4_cfgtype,
	    &err) != 0)) {
		topo_mod_dprintf(mod, "failed to set properties on %s=%d: %s",
		    SP, 0, topo_strerror(err));
		(void) topo_mod_seterrno(mod, err);
		goto out;
	}

	/* Set IPv6 configuration properties if IPv6 is enabled */
	if (lancfg.ilc_ipv6_enabled == B_TRUE) {
		ipv6_cfgtype = ipmi2toposrc(lancfg.ilc_ipv6_source);

		if (inet_ntop(AF_INET6, &lancfg.ilc_ipv6_addr, ipv6_addr,
		    sizeof (ipv6_addr)) == NULL) {
			(void) fprintf(stderr, "failed to convert IPv6 "
			    "address: %s\n", strerror(errno));
			(void) topo_mod_seterrno(mod, EMOD_UNKNOWN);
			goto out;
		}

		/* allocate and populate ipv6-routes string array */
		if ((ipv6_routes = topo_mod_zalloc(mod,
		    lancfg.ilc_ipv6_nroutes * sizeof (char *))) == NULL) {
			/* errno set */
			goto out;
		}
		for (i = 0; i < lancfg.ilc_ipv6_nroutes; i++) {
			if ((ipv6_routes[i] = topo_mod_alloc(mod,
			    INET6_ADDRSTRLEN)) == NULL) {
				/* errno set */
				goto out;
			}
		}
		for (i = 0; i < lancfg.ilc_ipv6_nroutes; i++) {
			if (inet_ntop(AF_INET6, &lancfg.ilc_ipv6_routes[i],
			    ipv6_routes[i], sizeof (ipv6_routes[i])) == NULL) {
				(void) fprintf(stderr, "failed to convert "
				    "IPv6 addresses: %s\n", strerror(errno));
				(void) topo_mod_seterrno(mod, EMOD_UNKNOWN);
				goto out;
			}
		}
	}
	if (lancfg.ilc_ipv6_enabled == B_TRUE &&
	    (topo_prop_set_string(sp_node, TOPO_PGROUP_NETCFG,
	    TOPO_PROP_NETCFG_IPV6_ADDR, TOPO_PROP_IMMUTABLE, ipv6_addr,
	    &err) != 0 ||
	    topo_prop_set_string_array(sp_node, TOPO_PGROUP_NETCFG,
	    TOPO_PROP_NETCFG_IPV6_ROUTES, TOPO_PROP_IMMUTABLE,
	    (const char **)ipv6_routes, lancfg.ilc_ipv6_nroutes, &err) != 0 ||
	    topo_prop_set_string(sp_node, TOPO_PGROUP_NETCFG,
	    TOPO_PROP_NETCFG_IPV6_TYPE, TOPO_PROP_IMMUTABLE, ipv6_cfgtype,
	    &err) != 0)) {
		topo_mod_dprintf(mod, "failed to set properties on %s=%d: %s",
		    SP, 0, topo_strerror(err));
		(void) topo_mod_seterrno(mod, err);
		goto out;
	}
	ret = 0;
out:
	if (lancfg.ilc_ipv6_nroutes > 0) {
		for (i = 0; i < lancfg.ilc_ipv6_nroutes; i++)
			topo_mod_free(mod, ipv6_routes[i], INET6_ADDRSTRLEN);
		topo_mod_free(mod, ipv6_routes,
		    lancfg.ilc_ipv6_nroutes * sizeof (char *));
	}
	topo_mod_ipmi_rele(mod);
	return (ret);
}

/*
 * libtopo enumeration point.  This simply iterates over entities looking for
 * the appropriate type.
 */
/*ARGSUSED*/
static int
ipmi_enum(topo_mod_t *mod, tnode_t *rnode, const char *name,
    topo_instance_t min, topo_instance_t max, void *arg, void *unused)
{
	ipmi_handle_t *ihp;
	ipmi_enum_data_t data;
	int ret;

	/*
	 * If the node being passed in ISN'T the chassis or motherboard node,
	 * then we're being asked to post-process a statically defined node.
	 */
	if (strcmp(topo_node_name(rnode), CHASSIS) != 0 &&
	    strcmp(topo_node_name(rnode), MOTHERBOARD) != 0) {
		if (ipmi_post_process(mod, rnode) != 0) {
			topo_mod_dprintf(mod, "post processing of node %s=%d "
			    "failed!", topo_node_name(rnode),
			    topo_node_instance(rnode));
			return (-1);
		}
		return (0);
	}

	/*
	 * For service processor enumeration we vector off into a special code
	 * path.
	 */
	if (strcmp(name, SP) == 0) {
		if (ipmi_enum_sp(mod, rnode) != 0) {
			topo_mod_dprintf(mod, "failed to enumerate the "
			    "service-processor");
			return (-1);
		}
		return (0);
	}

	if (strcmp(name, POWERMODULE) == 0) {
		data.ed_entity = IPMI_ET_POWER_DOMAIN;
	} else if (strcmp(name, PSU) == 0) {
		data.ed_entity = IPMI_ET_PSU;
	} else if (strcmp(name, FANMODULE) == 0) {
		data.ed_entity = IPMI_ET_COOLING_DOMAIN;
	} else if (strcmp(name, FAN) == 0) {
		data.ed_entity = IPMI_ET_FAN;
	} else {
		topo_mod_dprintf(mod, "unknown enumeration type '%s'",
		    name);
		return (-1);
	}

	if ((ihp = topo_mod_ipmi_hold(mod)) == NULL)
		return (0);

	data.ed_mod = mod;
	data.ed_pnode = rnode;
	data.ed_name = name;
	data.ed_instance = 0;
	data.ed_label = NULL;

	if ((ret = ipmi_entity_iter(ihp, ipmi_check_entity, &data)) != 0) {
		/*
		 * We don't return failure if IPMI enumeration fails.  This may
		 * be due to the SP being unavailable or an otherwise transient
		 * event.
		 */
		if (ret < 0) {
			topo_mod_dprintf(mod,
			    "failed to enumerate entities: %s",
			    ipmi_errmsg(ihp));
		} else {
			topo_mod_ipmi_rele(mod);
			return (-1);
		}
	}

	topo_mod_ipmi_rele(mod);
	return (0);
}

static int
ipmi_post_process(topo_mod_t *mod, tnode_t *tn)
{
	if (topo_method_register(mod, tn, ipmi_methods) != 0) {
		topo_mod_dprintf(mod, "ipmi_post_process() failed: %s",
		    topo_mod_errmsg(mod));
		return (1);
	}
	return (0);
}

/*ARGSUSED*/
int
_topo_init(topo_mod_t *mod, topo_version_t version)
{
	if (getenv("TOPOIPMIDEBUG") != NULL)
		topo_mod_setdebug(mod);

	if (topo_mod_register(mod, &ipmi_info, TOPO_VERSION) != 0) {
		topo_mod_dprintf(mod, "module registration failed: %s\n",
		    topo_mod_errmsg(mod));
		return (-1); /* mod errno already set */
	}

	topo_mod_dprintf(mod, "IPMI enumerator initialized\n");
	return (0);
}

void
_topo_fini(topo_mod_t *mod)
{
	/*
	 * This is the logical, and probably only safe spot where we could
	 * unload fac_prov_ipmi.  But unfortunately, calling topo_mod_unload()
	 * in the context of a module's _topo_fini entry point would result
	 * in recursively grabbing the modhash lock and we'd deadlock.
	 *
	 * Unfortunately, libtopo doesn't currently have a mechanism for
	 * expressing and handling intermodule dependencies, so we're left
	 * with this situation where once a module loads another module,
	 * it's going to be with us until we teardown the process.
	 */
	topo_mod_unregister(mod);
}