root/usr/src/lib/fm/topo/modules/common/fac_prov_ipmi/fac_prov_ipmi.c
/*
 * 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.
 */
/*
 * Copyright 2019 Joyent, Inc.
 * Copyright 2022 Tintri by DDN, Inc. All rights reserved.
 */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <limits.h>
#include <alloca.h>
#include <errno.h>
#include <libnvpair.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/fm/protocol.h>
#include <fm/libtopo.h>
#include <fm/topo_mod.h>
#include <libipmi.h>

#define BUFSZ   128

#define BAY_PRESENT_LED_MASK    0x01

/*
 * The largest possible SDR ID length is 2^5+1
 */
#define MAX_ID_LEN      33

#define TOPO_METH_IPMI_PLATFORM_MESSAGE_VERSION 0
#define TOPO_METH_IPMI_READING_VERSION          0
#define TOPO_METH_IPMI_STATE_VERSION            0
#define TOPO_METH_IPMI_MODE_VERSION             0
#define TOPO_METH_X4500_MODE_VERSION            0
#define TOPO_METH_BAY_LOCATE_VERSION            0
#define TOPO_METH_BAY_MODE_VERSION              0
#define TOPO_METH_CHASSIS_SERVICE_VERSION       0
#define TOPO_METH_IPMI_ENTITY_VERSION           0
#define TOPO_METH_DIMM_IPMI_ENTITY_VERSION      0
#define TOPO_METH_CHASSIS_IDENT_VERSION         0

static int fac_prov_ipmi_enum(topo_mod_t *, tnode_t *, const char *,
    topo_instance_t, topo_instance_t, void *, void *);

/*
 * IPMI facility provider methods
 */
static int ipmi_sensor_enum(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);
static int ipmi_entity(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
    nvlist_t **);
static int dimm_ipmi_entity(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
    nvlist_t **);
static int cs_ipmi_entity(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
    nvlist_t **);
static int ipmi_platform_message(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);
static int ipmi_sensor_reading(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);
static int ipmi_sensor_state(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);
static int ipmi_indicator_mode(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);
static int bay_locate_mode(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);
static int x4500_present_mode(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);
static int bay_indicator_mode(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);
static int chassis_service_mode(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);
static int chassis_ident_mode(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);

const topo_modops_t ipmi_ops = { fac_prov_ipmi_enum, NULL };

const topo_modinfo_t ipmi_info =
        { "IPMI facility provider", FM_FMRI_SCHEME_HC, TOPO_VERSION,
        &ipmi_ops };

static const topo_method_t ipmi_node_methods[] = {
        { TOPO_METH_FAC_ENUM, TOPO_METH_FAC_ENUM_DESC, 0,
            TOPO_STABILITY_INTERNAL, ipmi_sensor_enum },
        { TOPO_METH_IPMI_ENTITY, TOPO_PROP_METH_DESC,
            TOPO_METH_IPMI_ENTITY_VERSION,
            TOPO_STABILITY_INTERNAL, ipmi_entity },
        { "dimm_ipmi_entity", TOPO_PROP_METH_DESC,
            TOPO_METH_DIMM_IPMI_ENTITY_VERSION,
            TOPO_STABILITY_INTERNAL, dimm_ipmi_entity },
        { "cs_ipmi_entity", TOPO_PROP_METH_DESC,
            TOPO_METH_DIMM_IPMI_ENTITY_VERSION,
            TOPO_STABILITY_INTERNAL, cs_ipmi_entity },
        { TOPO_METH_SENSOR_FAILURE, TOPO_METH_SENSOR_FAILURE_DESC,
            TOPO_METH_SENSOR_FAILURE_VERSION, TOPO_STABILITY_INTERNAL,
            topo_method_sensor_failure },
        { NULL }
};

static const topo_method_t ipmi_fac_methods[] = {
        { "ipmi_platform_message", TOPO_PROP_METH_DESC,
            TOPO_METH_IPMI_PLATFORM_MESSAGE_VERSION,
            TOPO_STABILITY_INTERNAL, ipmi_platform_message },
        { "ipmi_sensor_reading", TOPO_PROP_METH_DESC,
            TOPO_METH_IPMI_READING_VERSION,
            TOPO_STABILITY_INTERNAL, ipmi_sensor_reading },
        { "ipmi_sensor_state", TOPO_PROP_METH_DESC,
            TOPO_METH_IPMI_STATE_VERSION,
            TOPO_STABILITY_INTERNAL, ipmi_sensor_state },
        { "ipmi_indicator_mode", TOPO_PROP_METH_DESC,
            TOPO_METH_IPMI_MODE_VERSION,
            TOPO_STABILITY_INTERNAL, ipmi_indicator_mode },
        { "bay_locate_mode", TOPO_PROP_METH_DESC,
            TOPO_METH_BAY_LOCATE_VERSION,
            TOPO_STABILITY_INTERNAL, bay_locate_mode },
        { "bay_indicator_mode", TOPO_PROP_METH_DESC,
            TOPO_METH_BAY_MODE_VERSION,
            TOPO_STABILITY_INTERNAL, bay_indicator_mode },
        { "chassis_service_mode", TOPO_PROP_METH_DESC,
            TOPO_METH_CHASSIS_SERVICE_VERSION,
            TOPO_STABILITY_INTERNAL, chassis_service_mode },
        { "chassis_ident_mode", TOPO_PROP_METH_DESC,
            TOPO_METH_CHASSIS_SERVICE_VERSION,
            TOPO_STABILITY_INTERNAL, chassis_ident_mode },
        { "x4500_present_mode", TOPO_PROP_METH_DESC,
            TOPO_METH_CHASSIS_SERVICE_VERSION,
            TOPO_STABILITY_INTERNAL, x4500_present_mode },
        { TOPO_METH_IPMI_ENTITY, TOPO_PROP_METH_DESC,
            TOPO_METH_IPMI_ENTITY_VERSION,
            TOPO_STABILITY_INTERNAL, ipmi_entity },
        { "dimm_ipmi_entity", TOPO_PROP_METH_DESC,
            TOPO_METH_DIMM_IPMI_ENTITY_VERSION,
            TOPO_STABILITY_INTERNAL, dimm_ipmi_entity },
        { "cs_ipmi_entity", TOPO_PROP_METH_DESC,
            TOPO_METH_DIMM_IPMI_ENTITY_VERSION,
            TOPO_STABILITY_INTERNAL, dimm_ipmi_entity },
        { NULL }
};

struct entity_info {
        uint32_t ei_id;
        uint32_t ei_inst;
        topo_mod_t *ei_mod;
        tnode_t *ei_node;
        char **ei_list;
        uint_t ei_listsz;
};

struct sensor_data {
        char sd_entity_ref[MAX_ID_LEN];
        uint8_t sd_units;
        uint32_t sd_stype;
        uint32_t sd_rtype;
        char *sd_class;
        ipmi_sdr_full_sensor_t *sd_fs_sdr;
};

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

        return (topo_mod_register(mod, &ipmi_info, TOPO_VERSION));
}

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

/*
 * Some platforms (most notably G1/2N) use the 'platform event message' command
 * to manipulate disk fault LEDs over IPMI, but uses the standard sensor
 * reading to read the value.  This method implements this alternative
 * interface for these platforms.
 */
/*ARGSUSED*/
static int
ipmi_platform_message(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        char *entity_ref;
        ipmi_sdr_compact_sensor_t *csp;
        ipmi_handle_t *hdl;
        int err, ret;
        uint32_t mode;
        nvlist_t *pargs, *nvl;
        ipmi_platform_event_message_t pem;
        ipmi_sensor_reading_t *reading;

        if (vers > TOPO_METH_IPMI_PLATFORM_MESSAGE_VERSION)
                return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));

        /*
         * Get an IPMI handle and then lookup the generic device locator sensor
         * data record referenced by the entity_ref prop val
         */
        if ((hdl = topo_mod_ipmi_hold(mod)) == NULL) {
                topo_mod_dprintf(mod, "Failed to get IPMI handle\n");
                return (-1);
        }

        if (topo_prop_get_string(node, TOPO_PGROUP_FACILITY, "entity_ref",
            &entity_ref, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup entity_ref property "
                    "(%s)", topo_strerror(err));
                topo_mod_ipmi_rele(mod);
                return (-1);
        }

        if ((csp = ipmi_sdr_lookup_compact_sensor(hdl, entity_ref)) == NULL) {
                topo_mod_dprintf(mod, "Failed to lookup SDR for %s (%s)\n",
                    entity_ref, ipmi_errmsg(hdl));
                topo_mod_strfree(mod, entity_ref);
                topo_mod_ipmi_rele(mod);
                return (-1);
        }

        /*
         * Now look for a private argument list to figure out whether we're
         * doing a get or a set operation, and then do it.
         */
        if ((nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs) == 0) &&
            nvlist_exists(pargs, TOPO_PROP_VAL_VAL)) {
                /*
                 * Set the LED mode
                 */
                if ((ret = nvlist_lookup_uint32(pargs, TOPO_PROP_VAL_VAL,
                    &mode)) != 0) {
                        topo_mod_dprintf(mod, "Failed to lookup %s nvpair "
                            "(%s)\n", TOPO_PROP_VAL_VAL, strerror(ret));
                        topo_mod_strfree(mod, entity_ref);
                        (void) topo_mod_seterrno(mod, EMOD_NVL_INVAL);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }

                if (mode != TOPO_LED_STATE_OFF &&
                    mode != TOPO_LED_STATE_ON) {
                        topo_mod_dprintf(mod, "Invalid property value: %d\n",
                            mode);
                        topo_mod_strfree(mod, entity_ref);
                        (void) topo_mod_seterrno(mod, EMOD_NVL_INVAL);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }

                pem.ipem_sensor_type = csp->is_cs_type;
                pem.ipem_sensor_num = csp->is_cs_number;
                pem.ipem_event_type = csp->is_cs_reading_type;

                /*
                 * The spec states that any values between 0x20 and 0x29 are
                 * legitimate for "system software".  However, some versions of
                 * Sun's ILOM rejects messages over /dev/ipmi0 with a generator
                 * of 0x20, so we use 0x21 instead.
                 */
                pem.ipem_generator = 0x21;
                pem.ipem_event_dir = 0;
                pem.ipem_rev = 0x04;
                if (mode == TOPO_LED_STATE_ON)
                        pem.ipem_event_data[0] = 1;
                else
                        pem.ipem_event_data[0] = 0;
                pem.ipem_event_data[1] = 0xff;
                pem.ipem_event_data[2] = 0xff;

                if (ipmi_event_platform_message(hdl, &pem) < 0) {
                        topo_mod_dprintf(mod, "Failed to set LED mode for %s "
                            "(%s)\n", entity_ref, ipmi_errmsg(hdl));
                        topo_mod_strfree(mod, entity_ref);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }
        } else {
                /*
                 * Get the LED mode
                 */
                if ((reading = ipmi_get_sensor_reading(hdl, csp->is_cs_number))
                    == NULL) {
                        topo_mod_dprintf(mod, "Failed to get sensor reading "
                            "for sensor %s: %s\n", entity_ref,
                            ipmi_errmsg(hdl));
                        topo_mod_strfree(mod, entity_ref);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }

                if (reading->isr_state &
                    TOPO_SENSOR_STATE_GENERIC_STATE_ASSERTED)
                        mode = TOPO_LED_STATE_ON;
                else
                        mode = TOPO_LED_STATE_OFF;
        }
        topo_mod_strfree(mod, entity_ref);

        topo_mod_ipmi_rele(mod);

        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, TOPO_LED_MODE) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, mode) != 0) {
                topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }
        *out = nvl;

        return (0);
}

/*ARGSUSED*/
static int
ipmi_sensor_state(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        char **entity_refs, *sensor_class;
        uint_t nelems;
        ipmi_sdr_t *sdr = NULL;
        ipmi_sensor_reading_t *reading;
        ipmi_handle_t *hdl;
        int err, i;
        uint8_t sensor_num;
        uint32_t e_id, e_inst, state;
        ipmi_sdr_full_sensor_t *fsensor;
        ipmi_sdr_compact_sensor_t *csensor;
        nvlist_t *nvl;
        boolean_t found_sdr = B_FALSE;
        tnode_t *pnode;

        if (vers > TOPO_METH_IPMI_STATE_VERSION)
                return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));

        if (topo_prop_get_string_array(node, TOPO_PGROUP_FACILITY, "entity_ref",
            &entity_refs, &nelems, &err) != 0) {
                topo_mod_dprintf(mod, "%s: Failed to lookup entity_ref "
                    "property (%s)", __func__, topo_strerror(err));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        if ((hdl = topo_mod_ipmi_hold(mod)) == NULL) {
                topo_mod_dprintf(mod, "Failed to get IPMI handle\n");
                topo_mod_strfreev(mod, entity_refs, nelems);
                return (-1);
        }

        pnode = topo_node_parent(node);
        if (topo_prop_get_uint32(pnode, TOPO_PGROUP_IPMI,
            TOPO_PROP_IPMI_ENTITY_ID, &e_id, &err) != 0 ||
            topo_prop_get_uint32(pnode, TOPO_PGROUP_IPMI,
            TOPO_PROP_IPMI_ENTITY_INST, &e_inst, &err) != 0) {
                e_id = IPMI_ET_UNSPECIFIED;
                e_inst = 0;
        }

        for (i = 0; i < nelems; i++) {
                if ((sdr = ipmi_sdr_lookup_precise(hdl, entity_refs[i],
                    (uint8_t)e_id, (uint8_t)e_inst)) != NULL) {
                        found_sdr = B_TRUE;
                        break;
                } else
                        topo_mod_dprintf(mod, "Failed to lookup SDR for %s "
                            "(%s)\n", entity_refs[i], ipmi_errmsg(hdl));
        }

        if (! found_sdr) {
                topo_mod_strfreev(mod, entity_refs, nelems);
                topo_mod_ipmi_rele(mod);
                return (-1);
        }

        switch (sdr->is_type) {
                case IPMI_SDR_TYPE_FULL_SENSOR:
                        fsensor = (ipmi_sdr_full_sensor_t *)sdr->is_record;
                        sensor_num = fsensor->is_fs_number;
                        break;
                case IPMI_SDR_TYPE_COMPACT_SENSOR:
                        csensor = (ipmi_sdr_compact_sensor_t *)sdr->is_record;
                        sensor_num = csensor->is_cs_number;
                        break;
                default:
                        topo_mod_dprintf(mod, "%s does not refer to a full or "
                            "compact SDR\n", entity_refs[i]);
                        topo_mod_ipmi_rele(mod);
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        return (-1);
        }
        if ((reading = ipmi_get_sensor_reading(hdl, sensor_num))
            == NULL) {
                topo_mod_dprintf(mod, "Failed to get sensor reading for sensor "
                    "%s, sensor_num=%d (%s)\n", entity_refs[i], sensor_num,
                    ipmi_errmsg(hdl));
                topo_mod_strfreev(mod, entity_refs, nelems);
                topo_mod_ipmi_rele(mod);
                return (-1);
        }
        if (reading->isr_state_unavailable) {
                topo_mod_dprintf(mod, "Unavailable sensor %s, sensor_num=%d\n",
                    entity_refs[i], sensor_num);
                topo_mod_strfreev(mod, entity_refs, nelems);
                topo_mod_ipmi_rele(mod);
                return (-1);
        }
        topo_mod_strfreev(mod, entity_refs, nelems);
        topo_mod_ipmi_rele(mod);

        if (topo_prop_get_string(node, TOPO_PGROUP_FACILITY, TOPO_SENSOR_CLASS,
            &sensor_class, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup prop %s/%s on node %s "
                    "(%s)", TOPO_PGROUP_FACILITY, TOPO_SENSOR_CLASS,
                    topo_node_name(node), topo_strerror(err));
                return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM));
        }
        /*
         * Mask off bits that are marked as reserved in the IPMI spec.
         * For threshold sensors, bits 6:7 are reserved.
         * For discrete sensors, bit 15 is reserved.
         */
        state = reading->isr_state;
        if (strcmp(sensor_class, TOPO_SENSOR_CLASS_THRESHOLD) == 0)
                state = state & 0x3F;
        else
                state = state & 0x7FFF;

        topo_mod_strfree(mod, sensor_class);

        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_PROP_VAL_NAME,
            TOPO_SENSOR_STATE) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, state) != 0) {
                topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }
        *out = nvl;

        return (0);
}

/*ARGSUSED*/
static int
ipmi_sensor_reading(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        char **entity_refs, reading_str[BUFSZ];
        uint_t nelems;
        int err = 0, i;
        ipmi_sdr_t *sdr = NULL;
        ipmi_sdr_full_sensor_t *fsensor;
        ipmi_sensor_reading_t  *reading;
        double conv_reading;
        ipmi_handle_t *hdl;
        nvlist_t *nvl;
        boolean_t found_sdr = B_FALSE;
        uint8_t sensor_num;
        uint32_t e_id, e_inst;
        tnode_t *pnode;

        if (vers > TOPO_METH_IPMI_READING_VERSION)
                return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));

        if (topo_prop_get_string_array(node, TOPO_PGROUP_FACILITY, "entity_ref",
            &entity_refs, &nelems, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup entity_ref property "
                    "(%s)", topo_strerror(err));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        if ((hdl = topo_mod_ipmi_hold(mod)) == NULL) {
                topo_mod_dprintf(mod, "Failed to get IPMI handle\n");
                topo_mod_strfreev(mod, entity_refs, nelems);
                return (-1);
        }

        pnode = topo_node_parent(node);
        if (topo_prop_get_uint32(pnode, TOPO_PGROUP_IPMI,
            TOPO_PROP_IPMI_ENTITY_ID, &e_id, &err) != 0 ||
            topo_prop_get_uint32(pnode, TOPO_PGROUP_IPMI,
            TOPO_PROP_IPMI_ENTITY_INST, &e_inst, &err) != 0) {
                e_id = IPMI_ET_UNSPECIFIED;
                e_inst = 0;
        }

        for (i = 0; i < nelems; i++) {
                if ((sdr = ipmi_sdr_lookup_precise(hdl, entity_refs[i],
                    (uint8_t)e_id, (uint8_t)e_inst)) != NULL) {
                        found_sdr = B_TRUE;
                        break;
                } else
                        topo_mod_dprintf(mod, "Failed to lookup SDR for %s "
                            "(%s)\n", entity_refs[i], ipmi_errmsg(hdl));
        }

        if (! found_sdr) {
                topo_mod_strfreev(mod, entity_refs, nelems);
                topo_mod_ipmi_rele(mod);
                return (-1);
        }
        switch (sdr->is_type) {
                case IPMI_SDR_TYPE_FULL_SENSOR:
                        fsensor = (ipmi_sdr_full_sensor_t *)sdr->is_record;
                        sensor_num = fsensor->is_fs_number;
                        break;
                default:
                        topo_mod_dprintf(mod, "%s does not refer to a full "
                            "sensor SDR\n", entity_refs[i]);
                        topo_mod_ipmi_rele(mod);
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        return (-1);
        }

        if ((reading = ipmi_get_sensor_reading(hdl, sensor_num)) == NULL) {
                topo_mod_dprintf(mod, "Failed to get sensor reading for sensor "
                    "%s, sensor_num=%d (%s)\n", entity_refs[i],
                    sensor_num, ipmi_errmsg(hdl));
                topo_mod_strfreev(mod, entity_refs, nelems);
                topo_mod_ipmi_rele(mod);
                return (-1);
        }
        topo_mod_ipmi_rele(mod);

        if (ipmi_sdr_conv_reading(fsensor, reading->isr_reading, &conv_reading)
            != 0) {
                topo_mod_dprintf(mod, "Failed to convert sensor reading for "
                    "sensor %s (%s)\n", entity_refs[i], ipmi_errmsg(hdl));
                topo_mod_strfreev(mod, entity_refs, nelems);
                return (-1);
        }
        topo_mod_strfreev(mod, entity_refs, nelems);

        (void) snprintf(reading_str, BUFSZ, "%f", conv_reading);
        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_PROP_VAL_NAME,
            TOPO_SENSOR_READING) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_DOUBLE) != 0 ||
            nvlist_add_double(nvl, TOPO_PROP_VAL_VAL, conv_reading) != 0) {
                topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }
        *out = nvl;

        return (0);
}

static int
ipmi_indicator_mode(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        char **entity_refs;
        uint_t nelems;
        ipmi_sdr_generic_locator_t *gdl = NULL;
        ipmi_handle_t *hdl;
        int err, ret, i;
        uint8_t ledmode;
        uint32_t mode_in;
        nvlist_t *pargs, *nvl;
        boolean_t found_sdr = B_FALSE;

        if (vers > TOPO_METH_IPMI_MODE_VERSION)
                return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));

        /*
         * Get an IPMI handle and then lookup the generic device locator sensor
         * data record referenced by the entity_ref prop val
         */
        if ((hdl = topo_mod_ipmi_hold(mod)) == NULL) {
                topo_mod_dprintf(mod, "Failed to get IPMI handle\n");
                return (-1);
        }

        if (topo_prop_get_string_array(node, TOPO_PGROUP_FACILITY, "entity_ref",
            &entity_refs, &nelems, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup entity_ref property "
                    "(%s)", topo_strerror(err));
                topo_mod_ipmi_rele(mod);
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        for (i = 0; i < nelems; i++) {
                if ((gdl = ipmi_sdr_lookup_generic(hdl, entity_refs[i]))
                    != NULL) {
                        found_sdr = B_TRUE;
                        break;
                } else
                        topo_mod_dprintf(mod, "Failed to lookup SDR for %s "
                            "(%s)\n", entity_refs[i], ipmi_errmsg(hdl));
        }

        if (! found_sdr) {
                topo_mod_strfreev(mod, entity_refs, nelems);
                topo_mod_ipmi_rele(mod);
                return (-1);
        }

        /*
         * Now look for a private argument list to figure out whether we're
         * doing a get or a set operation, and then do it.
         */
        if ((nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs) == 0) &&
            nvlist_exists(pargs, TOPO_PROP_VAL_VAL)) {
                /*
                 * Set the LED mode
                 */
                if ((ret = nvlist_lookup_uint32(pargs, TOPO_PROP_VAL_VAL,
                    &mode_in)) != 0) {
                        topo_mod_dprintf(mod, "Failed to lookup %s nvpair "
                            "(%s)\n", TOPO_PROP_VAL_VAL, strerror(ret));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
                }
                if (mode_in != TOPO_LED_STATE_OFF &&
                    mode_in != TOPO_LED_STATE_ON) {
                        topo_mod_dprintf(mod, "Invalid property value: %d\n",
                            mode_in);
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
                }
                ledmode = (uint8_t)mode_in;
                if (ipmi_sunoem_led_set(hdl, gdl, ledmode) < 0) {
                        topo_mod_dprintf(mod, "%s: Failed to set LED mode for "
                            "%s (%s) to %s\n", __func__, entity_refs[i],
                            ipmi_errmsg(hdl), ledmode ? "ON" : "OFF");
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }
        } else {
                /*
                 * Get the LED mode
                 */
                if (ipmi_sunoem_led_get(hdl, gdl, &ledmode) < 0) {
                        topo_mod_dprintf(mod, "%s: Failed to get LED mode for "
                            "%s (%s)\n", __func__, entity_refs[i],
                            ipmi_errmsg(hdl));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }
        }
        topo_mod_strfreev(mod, entity_refs, nelems);
        topo_mod_ipmi_rele(mod);

        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, TOPO_LED_MODE) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, ledmode) != 0) {
                topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }
        *out = nvl;

        return (0);
}

/*
 * On most Sun platforms there is no seperate locate LED for the drive bays.
 * This propmethod simulates a locate LED by blinking the ok2rm LED.
 *
 * LED control is through a the Sun OEM led/get commands.  This propmethod can
 * work on X4500/X4540 with ILOM 2.x and on
 * X4140/X4240/X4440/X4500/X4540/X4150/X4250 and X4450 platforms with ILOM 3.x.
 */
static int
bay_locate_mode(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        char **entity_refs;
        uint_t nelems;
        ipmi_sdr_generic_locator_t *gdl = NULL;
        ipmi_handle_t *hdl;
        int err, ret, i;
        uint8_t ledmode;
        uint32_t mode_in;
        nvlist_t *pargs, *nvl;
        boolean_t found_sdr = B_FALSE;

        if (vers > TOPO_METH_BAY_LOCATE_VERSION)
                return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));

        /*
         * Get an IPMI handle and then lookup the generic device locator sensor
         * data record referenced by the entity_ref prop val
         */
        if ((hdl = topo_mod_ipmi_hold(mod)) == NULL) {
                topo_mod_dprintf(mod, "Failed to get IPMI handle\n");
                return (-1);
        }

        if (topo_prop_get_string_array(node, TOPO_PGROUP_FACILITY, "entity_ref",
            &entity_refs, &nelems, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup entity_ref property "
                    "(%s)", topo_strerror(err));
                topo_mod_ipmi_rele(mod);
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        for (i = 0; i < nelems; i++) {
                if ((gdl = ipmi_sdr_lookup_generic(hdl, entity_refs[i]))
                    != NULL) {
                        found_sdr = B_TRUE;
                        break;
                } else
                        topo_mod_dprintf(mod, "Failed to lookup SDR for %s "
                            "(%s)\n", entity_refs[i], ipmi_errmsg(hdl));
        }

        if (! found_sdr) {
                topo_mod_strfreev(mod, entity_refs, nelems);
                topo_mod_ipmi_rele(mod);
                return (-1);
        }

        /*
         * Now look for a private argument list to figure out whether we're
         * doing a get or a set operation, and then do it.
         */
        if ((nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs) == 0) &&
            nvlist_exists(pargs, TOPO_PROP_VAL_VAL)) {
                /*
                 * Set the LED mode
                 */
                if ((ret = nvlist_lookup_uint32(pargs, TOPO_PROP_VAL_VAL,
                    &mode_in)) != 0) {
                        topo_mod_dprintf(mod, "Failed to lookup %s nvpair "
                            "(%s)\n", TOPO_PROP_VAL_VAL, strerror(ret));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
                }
                if (mode_in != TOPO_LED_STATE_OFF &&
                    mode_in != TOPO_LED_STATE_ON) {
                        topo_mod_dprintf(mod, "Invalid property value: %d\n",
                            mode_in);
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
                }
                if (mode_in == TOPO_LED_STATE_ON)
                        ledmode = IPMI_SUNOEM_LED_MODE_FAST;
                else
                        ledmode = IPMI_SUNOEM_LED_MODE_OFF;
                if (ipmi_sunoem_led_set(hdl, gdl, ledmode) < 0) {
                        topo_mod_dprintf(mod, "Failed to set LED mode for %s "
                            "(%s)\n", entity_refs[i], ipmi_errmsg(hdl));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }
        } else {
                /*
                 * Get the LED mode
                 */
                if (ipmi_sunoem_led_get(hdl, gdl, &ledmode) < 0) {
                        topo_mod_dprintf(mod, "Failed to get LED mode for %s "
                            "(%s)\n", entity_refs[i], ipmi_errmsg(hdl));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }
        }
        topo_mod_strfreev(mod, entity_refs, nelems);
        topo_mod_ipmi_rele(mod);

        if (ledmode == IPMI_SUNOEM_LED_MODE_SLOW ||
            ledmode == IPMI_SUNOEM_LED_MODE_FAST)
                ledmode = TOPO_LED_STATE_ON;
        else
                ledmode = TOPO_LED_STATE_OFF;

        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, TOPO_LED_MODE) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, ledmode) != 0) {
                topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }
        *out = nvl;

        return (0);
}

/*
 * This is a method for the "mode" property that is specific for the ok2rm and
 * service drive bay LED's on the X4500/X4540 platforms running ILOM 2.x and
 * for X4140/X4240/X4440/X4500/X4540/X4150/X4250 and X4450 platforms running
 * ILOM 3.x.
 *
 * For ILOM 2.x, the LED's are controlled by a Sun OEM led set command
 *
 * For ILOM 3.x platforms the LED's are controlled by sending a platform event
 * message for the appropriate DBP/HDD##/STATE compact SDR.
 *
 * For both ILOM 2 and ILOM 3, the current LED mode can be obtained by a
 * Sun OEM led get command.
 */
static int
bay_indicator_mode(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        char **entity_refs;
        uint_t nelems;
        ipmi_sdr_compact_sensor_t *cs = NULL;
        ipmi_sdr_generic_locator_t *gdl = NULL;
        ipmi_deviceid_t *sp_devid;
        ipmi_platform_event_message_t pem;
        ipmi_handle_t *hdl;
        int err, ret, i;
        uint32_t type, ledmode;
        uint8_t mode_in, ev_off;
        nvlist_t *pargs, *nvl;
        boolean_t found_sdr = B_FALSE;

        if (vers > TOPO_METH_BAY_MODE_VERSION)
                return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));

        if (topo_prop_get_uint32(node, TOPO_PGROUP_FACILITY, TOPO_FACILITY_TYPE,
            &type, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup %s property "
                    "(%s)", TOPO_FACILITY_TYPE, topo_strerror(err));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }
        switch (type) {
        case (TOPO_LED_TYPE_SERVICE):
                ev_off = 0x01;
                break;
        case (TOPO_LED_TYPE_OK2RM):
                ev_off = 0x03;
                break;
        default:
                topo_mod_dprintf(mod, "Invalid LED type: 0x%x\n", type);
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        if (topo_prop_get_string_array(node, TOPO_PGROUP_FACILITY, "entity_ref",
            &entity_refs, &nelems, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup entity_ref property "
                    "(%s)", topo_strerror(err));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        /*
         * Figure out whether the SP is running ILOM 2.x or ILOM 3.x
         */
        if ((hdl = topo_mod_ipmi_hold(mod)) == NULL) {
                topo_mod_dprintf(mod, "Failed to get IPMI handle\n");
                topo_mod_strfreev(mod, entity_refs, nelems);
                return (-1);
        }

        if ((sp_devid = ipmi_get_deviceid(hdl)) == NULL) {
                topo_mod_dprintf(mod, "%s: GET DEVICEID command failed (%s)\n",
                    __func__, ipmi_errmsg(hdl));
                topo_mod_strfreev(mod, entity_refs, nelems);
                topo_mod_ipmi_rele(mod);
                return (-1);
        }

        /*
         * Now lookup the propmethod argument list and figure out whether we're
         * doing a get or a set operation, and then do it.
         */
        if ((nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs) == 0) &&
            nvlist_exists(pargs, TOPO_PROP_VAL_VAL)) {
                /*
                 * Set the LED mode
                 */
                if ((ret = nvlist_lookup_uint32(pargs, TOPO_PROP_VAL_VAL,
                    &ledmode)) != 0) {
                        topo_mod_dprintf(mod, "Failed to lookup %s nvpair "
                            "(%s)\n", TOPO_PROP_VAL_VAL, strerror(ret));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
                }

                topo_mod_dprintf(mod, "%s: Setting LED mode to %s\n", __func__,
                    ledmode ? "ON" : "OFF");

                if (sp_devid->id_firm_major == 2) {
                        for (i = 0; i < nelems; i++) {
                                if ((gdl = ipmi_sdr_lookup_generic(hdl,
                                    entity_refs[i])) != NULL) {
                                        found_sdr = B_TRUE;
                                        break;
                                } else
                                        topo_mod_dprintf(mod,
                                            "Failed to lookup SDR for %s(%s)\n",
                                            entity_refs[i], ipmi_errmsg(hdl));
                        }

                        if (! found_sdr) {
                                topo_mod_strfreev(mod, entity_refs, nelems);
                                topo_mod_ipmi_rele(mod);
                                return (-1);
                        }

                        if (ipmi_sunoem_led_set(hdl, gdl, (uint8_t)ledmode)
                            < 0) {
                                topo_mod_dprintf(mod,
                                    "Failed to set LED mode for %s (%s)\n",
                                    entity_refs[i], ipmi_errmsg(hdl));
                                topo_mod_strfreev(mod, entity_refs, nelems);
                                topo_mod_ipmi_rele(mod);
                                return (-1);
                        }
                } else {
                        for (i = 0; i < nelems; i++) {
                                if ((cs = ipmi_sdr_lookup_compact_sensor(hdl,
                                    entity_refs[i])) != NULL) {
                                        found_sdr = B_TRUE;
                                        break;
                                } else
                                        topo_mod_dprintf(mod,
                                            "Failed to lookup SDR for %s(%s)\n",
                                            entity_refs[i], ipmi_errmsg(hdl));
                        }

                        if (! found_sdr) {
                                topo_mod_strfreev(mod, entity_refs, nelems);
                                topo_mod_ipmi_rele(mod);
                                return (-1);
                        }

                        pem.ipem_generator = IPMI_SEL_SYSTEM;
                        pem.ipem_rev = IPMI_EV_REV15;
                        pem.ipem_sensor_type = IPMI_ST_BAY;
                        pem.ipem_sensor_num = cs->is_cs_number;
                        pem.ipem_event_type =  IPMI_RT_SPECIFIC;
                        if (ledmode == TOPO_LED_STATE_ON)
                                pem.ipem_event_dir = 0;
                        else
                                pem.ipem_event_dir = 1;

                        pem.ipem_event_data[0] = ev_off;
                        pem.ipem_event_data[1] = 0xff;
                        pem.ipem_event_data[2] = 0xff;

                        if (ipmi_event_platform_message(hdl, &pem) != 0) {
                                topo_mod_dprintf(mod, "%s: Failed to send "
                                    "platform event mesg for %s (%s)\n",
                                    __func__, entity_refs[i], ipmi_errmsg(hdl));
                                topo_mod_strfreev(mod, entity_refs, nelems);
                                topo_mod_ipmi_rele(mod);
                                return (-1);
                        }
                }
        } else {
                /*
                 * Get the LED mode
                 */
                for (i = 0; i < nelems; i++) {
                        if ((gdl = ipmi_sdr_lookup_generic(hdl, entity_refs[i]))
                            != NULL) {
                                found_sdr = B_TRUE;
                                break;
                        } else
                                topo_mod_dprintf(mod, "%s: Failed to lookup "
                                    "SDR for %s (%s)\n", __func__,
                                    entity_refs[i], ipmi_errmsg(hdl));
                }

                if (! found_sdr) {
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }
                if (ipmi_sunoem_led_get(hdl, gdl, &mode_in) < 0) {
                        topo_mod_dprintf(mod, "%s: Failed to get LED mode for "
                            "%s (%s)\n", __func__, entity_refs[i],
                            ipmi_errmsg(hdl));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }
                ledmode = mode_in;
        }
        topo_mod_strfreev(mod, entity_refs, nelems);
        topo_mod_ipmi_rele(mod);

        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, TOPO_LED_MODE) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, ledmode) != 0) {
                topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }
        *out = nvl;
        return (0);
}

/*
 * This propmethod is for controlling the present LED on the drive bays for
 * the X4500 platform.
 */
static int
x4500_present_mode(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        char **entity_refs;
        uint_t nelems;
        ipmi_sdr_compact_sensor_t *cs = NULL;
        ipmi_set_sensor_reading_t sr_out = { 0 };
        ipmi_handle_t *hdl;
        int err, ret, i;
        uint32_t ledmode;
        nvlist_t *pargs, *nvl;
        boolean_t found_sdr = B_FALSE;

        if (vers > TOPO_METH_X4500_MODE_VERSION)
                return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));

        if (topo_prop_get_string_array(node, TOPO_PGROUP_FACILITY, "entity_ref",
            &entity_refs, &nelems, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup entity_ref property "
                    "(%s)", topo_strerror(err));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        if ((hdl = topo_mod_ipmi_hold(mod)) == NULL) {
                topo_mod_dprintf(mod, "Failed to get IPMI handle\n");
                topo_mod_strfreev(mod, entity_refs, nelems);
                return (-1);
        }
        for (i = 0; i < nelems; i++) {
                if ((cs = ipmi_sdr_lookup_compact_sensor(hdl, entity_refs[i]))
                    != NULL) {
                        found_sdr = B_TRUE;
                        break;
                } else
                        topo_mod_dprintf(mod, "Failed to lookup SDR for %s "
                            "(%s)\n", entity_refs[i],
                            ipmi_errmsg(hdl));
        }

        if (! found_sdr) {
                topo_mod_strfreev(mod, entity_refs, nelems);
                topo_mod_ipmi_rele(mod);
                return (-1);
        }

        /*
         * Now lookup the propmethod argument list and figure out whether we're
         * doing a get or a set operation, and then do it.
         */
        if ((nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs) == 0) &&
            nvlist_exists(pargs, TOPO_PROP_VAL_VAL)) {
                /*
                 * Set the LED mode
                 */
                if ((ret = nvlist_lookup_uint32(pargs, TOPO_PROP_VAL_VAL,
                    &ledmode)) != 0) {
                        topo_mod_dprintf(mod, "Failed to lookup %s nvpair "
                            "(%s)\n", TOPO_PROP_VAL_VAL, strerror(ret));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
                }

                topo_mod_dprintf(mod, "%s: Setting LED mode to %s\n", __func__,
                    ledmode ? "ON" : "OFF");

                if (ledmode == TOPO_LED_STATE_OFF) {
                        sr_out.iss_deassert_state = BAY_PRESENT_LED_MASK;
                        sr_out.iss_deassrt_op = IPMI_SENSOR_OP_SET;
                } else if (ledmode == TOPO_LED_STATE_ON) {
                        sr_out.iss_assert_state = BAY_PRESENT_LED_MASK;
                        sr_out.iss_assert_op = IPMI_SENSOR_OP_SET;
                } else {
                        topo_mod_dprintf(mod, "%s: Invalid LED mode: "
                            "%d\n", __func__, ledmode);
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }
                sr_out.iss_id = cs->is_cs_number;
                topo_mod_dprintf(mod, "Setting LED mode (mask=0x%x)\n",
                    BAY_PRESENT_LED_MASK);
                if (ipmi_set_sensor_reading(hdl, &sr_out) != 0) {
                        topo_mod_dprintf(mod, "%s: Failed to set "
                            "sensor reading for %s (%s)\n", __func__,
                            entity_refs[i], ipmi_errmsg(hdl));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }
        } else {
                /*
                 * Get the LED mode
                 */
                ipmi_sensor_reading_t *sr_in;

                topo_mod_dprintf(mod, "Getting LED mode\n");
                if ((sr_in = ipmi_get_sensor_reading(hdl, cs->is_cs_number))
                    == NULL) {
                        topo_mod_dprintf(mod, "Failed to get sensor reading "
                            "for sensor %s (sensor num: %d) (error: %s)\n",
                            entity_refs[i], cs->is_cs_number, ipmi_errmsg(hdl));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }
                if (sr_in->isr_state & (uint16_t)BAY_PRESENT_LED_MASK)
                        ledmode = TOPO_LED_STATE_ON;
                else
                        ledmode = TOPO_LED_STATE_OFF;
        }
        topo_mod_strfreev(mod, entity_refs, nelems);
        topo_mod_ipmi_rele(mod);

        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, TOPO_LED_MODE) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, ledmode) != 0) {
                topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }
        *out = nvl;
        return (0);
}

/*
 * This is a property method for controlling the chassis service LED on
 * ILOM 3.x based platforms.
 */
static int
chassis_service_mode(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        char **entity_refs;
        uint_t nelems;
        ipmi_sdr_generic_locator_t *gdl = NULL;
        ipmi_deviceid_t *sp_devid;
        ipmi_platform_event_message_t pem;
        ipmi_handle_t *hdl;
        int err, ret, i;
        uint8_t ledmode;
        uint32_t mode_in;
        nvlist_t *pargs, *nvl;
        boolean_t found_sdr = B_FALSE;

        if (vers > TOPO_METH_CHASSIS_SERVICE_VERSION)
                return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));

        /*
         * Get an IPMI handle and then lookup the generic device locator record
         * referenced by the entity_ref prop val
         */
        if ((hdl = topo_mod_ipmi_hold(mod)) == NULL) {
                topo_mod_dprintf(mod, "Failed to get IPMI handle\n");
                return (-1);
        }

        if (topo_prop_get_string_array(node, TOPO_PGROUP_FACILITY, "entity_ref",
            &entity_refs, &nelems, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup entity_ref property "
                    "(%s)", topo_strerror(err));
                topo_mod_ipmi_rele(mod);
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        for (i = 0; i < nelems; i++) {
                if ((gdl = ipmi_sdr_lookup_generic(hdl, entity_refs[i]))
                    != NULL) {
                        found_sdr = B_TRUE;
                        break;
                } else
                        topo_mod_dprintf(mod, "Failed to lookup SDR for %s "
                            "(%s)\n", entity_refs[i], ipmi_errmsg(hdl));
        }

        if (! found_sdr) {
                topo_mod_strfreev(mod, entity_refs, nelems);
                topo_mod_ipmi_rele(mod);
                return (-1);
        }

        /*
         * Now lookup the propmethod argument list and figure out whether we're
         * doing a get or a set operation, and then do it.
         */
        if ((nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs) == 0) &&
            nvlist_exists(pargs, TOPO_PROP_VAL_VAL)) {
                /*
                 * Set the LED mode
                 */
                if ((ret = nvlist_lookup_uint32(pargs, TOPO_PROP_VAL_VAL,
                    &mode_in)) != 0) {
                        topo_mod_dprintf(mod, "Failed to lookup %s nvpair "
                            "(%s)\n", TOPO_PROP_VAL_VAL, strerror(ret));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
                }

                /*
                 * Determine which IPMI mechanism to use to set the LED mode
                 * based on whether the SP is running ILOM 2 or later.
                 */
                if ((sp_devid = ipmi_get_deviceid(hdl)) == NULL) {
                        topo_mod_dprintf(mod, "%s: GET DEVICEID command failed "
                        "(%s)\n", __func__, ipmi_errmsg(hdl));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }

                topo_mod_dprintf(mod, "%s: Setting LED mode to %s\n", __func__,
                    mode_in ? "ON" : "OFF");

                if (sp_devid->id_firm_major == 2) {
                        if (mode_in != TOPO_LED_STATE_OFF &&
                            mode_in != TOPO_LED_STATE_ON) {
                                topo_mod_dprintf(mod, "Invalid property value: "
                                    "%d\n", mode_in);
                                topo_mod_strfreev(mod, entity_refs, nelems);
                                topo_mod_ipmi_rele(mod);
                                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
                        }
                        if (ipmi_sunoem_led_set(hdl, gdl, (uint8_t)mode_in)
                            < 0) {
                                topo_mod_dprintf(mod, "Failed to set LED mode "
                                    "for %s (%s)\n", entity_refs[i],
                                    ipmi_errmsg(hdl));
                                topo_mod_strfreev(mod, entity_refs, nelems);
                                topo_mod_ipmi_rele(mod);
                                return (-1);
                        }
                } else {
                        pem.ipem_generator = IPMI_SEL_SYSTEM;
                        pem.ipem_rev = IPMI_EV_REV15;
                        pem.ipem_sensor_type = IPMI_ST_SYSTEM;
                        pem.ipem_sensor_num = 0x00;
                        pem.ipem_event_type =  IPMI_RT_SPECIFIC;
                        if (mode_in == TOPO_LED_STATE_ON)
                                pem.ipem_event_dir = 0;
                        else
                                pem.ipem_event_dir = 1;

                        pem.ipem_event_data[0] = 0x02;
                        pem.ipem_event_data[1] = 0xff;
                        pem.ipem_event_data[2] = 0xff;

                        topo_mod_dprintf(mod, "Sending platform event\n");
                        if (ipmi_event_platform_message(hdl, &pem) != 0) {
                                topo_mod_dprintf(mod, "%s: Failed to send "
                                    "platform event mesg for sensor 0 (%s)\n",
                                    __func__, ipmi_errmsg(hdl));
                                topo_mod_strfreev(mod, entity_refs, nelems);
                                topo_mod_ipmi_rele(mod);
                                return (-1);
                        }
                }
        } else {
                /*
                 * Get the LED mode
                 */
                if (ipmi_sunoem_led_get(hdl, gdl, &ledmode) < 0) {
                        topo_mod_dprintf(mod, "%s: Failed to get LED mode for "
                            "%s (%s)\n", __func__, entity_refs[i],
                            ipmi_errmsg(hdl));
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        topo_mod_ipmi_rele(mod);
                        return (-1);
                }
        }
        topo_mod_strfreev(mod, entity_refs, nelems);
        topo_mod_ipmi_rele(mod);

        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, TOPO_LED_MODE) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, ledmode) != 0) {
                topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }
        *out = nvl;
        return (0);
}

/*
 * This is a property method for controlling the chassis identify LED using
 * generic IPMI mechanisms.
 */
/*ARGSUSED*/
static int
chassis_ident_mode(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        ipmi_handle_t *hdl;
        int ret;
        uint32_t modeval;
        boolean_t assert_ident;
        nvlist_t *pargs, *nvl;
        ipmi_chassis_status_t *chs;

        if (vers > TOPO_METH_CHASSIS_IDENT_VERSION)
                return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));

        if ((hdl = topo_mod_ipmi_hold(mod)) == NULL) {
                topo_mod_dprintf(mod, "Failed to get IPMI handle\n");
                return (-1);
        }

        /*
         * Now lookup the propmethod argument list and figure out whether we're
         * doing a get or a set operation, and then do it.
         */
        if ((nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs) == 0) &&
            nvlist_exists(pargs, TOPO_PROP_VAL_VAL)) {
                /*
                 * Set the LED mode
                 */
                if ((ret = nvlist_lookup_uint32(pargs, TOPO_PROP_VAL_VAL,
                    &modeval)) != 0) {
                        topo_mod_dprintf(mod, "Failed to lookup %s nvpair "
                            "(%s)\n", TOPO_PROP_VAL_VAL, strerror(ret));
                        topo_mod_ipmi_rele(mod);
                        return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
                }

                assert_ident = modeval ? B_TRUE : B_FALSE;
                topo_mod_dprintf(mod, "%s: Setting LED mode to %s\n", __func__,
                    assert_ident ? "ON" : "OFF");
                if (ipmi_chassis_identify(hdl, assert_ident) != 0) {
                        topo_mod_ipmi_rele(mod);
                        return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
                }

        } else {
                /*
                 * Get the LED mode
                 */
                if ((chs = ipmi_chassis_status(hdl)) == NULL ||
                    !chs->ichs_identify_supported) {
                        free(chs);
                        topo_mod_ipmi_rele(mod);
                        return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
                }
                /*
                 * ichs_identify_state is a 2-bit value with the following
                 * semantics:
                 * 0 - ident is off
                 * 1 - ident is temporarily on
                 * 2 - ident is indefinitely on
                 * 3 - reserved
                 */
                switch (chs->ichs_identify_state) {
                case 0:
                        modeval = TOPO_LED_STATE_OFF;
                        break;
                case 1:
                case 2:
                        modeval = TOPO_LED_STATE_ON;
                        break;
                default:
                        free(chs);
                        topo_mod_ipmi_rele(mod);
                        return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
                }
                free(chs);
        }
        topo_mod_ipmi_rele(mod);

        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, TOPO_LED_MODE) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, modeval) != 0) {
                topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }
        *out = nvl;
        return (0);
}

#define ISBITSET(MASK, BIT)     ((MASK & BIT) == BIT)

struct sensor_thresh {
        uint8_t sthr_threshbit;
        const char *sthr_propname;
        uint8_t sthr_threshoff;
};

static const struct sensor_thresh threshset[] = {
        { IPMI_SENSOR_THRESHOLD_LOWER_NONCRIT, TOPO_PROP_THRESHOLD_LNC,
            offsetof(ipmi_sensor_thresholds_t, ithr_lower_noncrit) },
        { IPMI_SENSOR_THRESHOLD_LOWER_CRIT, TOPO_PROP_THRESHOLD_LCR,
            offsetof(ipmi_sensor_thresholds_t, ithr_lower_crit) },
        { IPMI_SENSOR_THRESHOLD_LOWER_NONRECOV, TOPO_PROP_THRESHOLD_LNR,
            offsetof(ipmi_sensor_thresholds_t, ithr_lower_nonrec) },
        { IPMI_SENSOR_THRESHOLD_UPPER_NONCRIT, TOPO_PROP_THRESHOLD_UNC,
            offsetof(ipmi_sensor_thresholds_t, ithr_upper_noncrit) },
        { IPMI_SENSOR_THRESHOLD_UPPER_CRIT, TOPO_PROP_THRESHOLD_UCR,
            offsetof(ipmi_sensor_thresholds_t, ithr_upper_crit) },
        { IPMI_SENSOR_THRESHOLD_UPPER_NONRECOV, TOPO_PROP_THRESHOLD_UNR,
            offsetof(ipmi_sensor_thresholds_t, ithr_upper_nonrec) }
};

static uint_t num_thresholds =
    sizeof (threshset) / sizeof (struct sensor_thresh);

static int
set_thresh_prop(topo_mod_t *mod, tnode_t *fnode, ipmi_sdr_full_sensor_t *fs,
    uint8_t raw_thresh, const struct sensor_thresh *thresh)
{
        int err;
        double conv_thresh;

        if (ipmi_sdr_conv_reading(fs, raw_thresh, &conv_thresh) != 0) {
                topo_mod_dprintf(mod, "Failed to convert threshold %s on node "
                    "%s", thresh->sthr_propname, topo_node_name(fnode));
                return (topo_mod_seterrno(mod, EMOD_PARTIAL_ENUM));
        }
        if (topo_prop_set_double(fnode, TOPO_PGROUP_FACILITY,
            thresh->sthr_propname, TOPO_PROP_IMMUTABLE, conv_thresh, &err) !=
            0) {
                topo_mod_dprintf(mod, "Failed to set property %s on node %s "
                    "(%s)", thresh->sthr_propname, topo_node_name(fnode),
                    topo_strerror(err));
                return (topo_mod_seterrno(mod, err));
        }
        return (0);
}

static int
make_sensor_node(topo_mod_t *mod, tnode_t *pnode, struct sensor_data *sd,
    ipmi_handle_t *hdl)
{
        int err, ret, i;
        tnode_t *fnode;
        char *ftype = "sensor", facname[MAX_ID_LEN], **entity_refs;
        topo_pgroup_info_t pgi;
        nvlist_t *arg_nvl = NULL;
        ipmi_sensor_thresholds_t thresh = { 0 };
        uint8_t mask;

        /*
         * Some platforms have '/' characters in the IPMI entity name, but '/'
         * has a special meaning for FMRI's so we change them to '.' before
         * binding the node into the topology.
         */
        (void) strcpy(facname, sd->sd_entity_ref);
        for (i = 0; facname[i]; i++)
                if (facname[i] == '/')
                        facname[i] = '.';

        if ((fnode = topo_node_facbind(mod, pnode, facname, ftype)) == NULL) {
                topo_mod_dprintf(mod, "Failed to bind facility node: %s\n",
                    facname);
                /* errno set */
                return (-1);
        }

        pgi.tpi_name = TOPO_PGROUP_FACILITY;
        pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
        pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
        pgi.tpi_version = 1;
        if (topo_pgroup_create(fnode, &pgi, &err) != 0) {
                if (err != ETOPO_PROP_DEFD) {
                        topo_mod_dprintf(mod,  "pgroups create failure: %s\n",
                            topo_strerror(err));
                        topo_node_unbind(fnode);
                        return (topo_mod_seterrno(mod, err));
                }
        }
        if (topo_method_register(mod, fnode, ipmi_fac_methods) < 0) {
                topo_mod_dprintf(mod, "make_fac_node: "
                    "failed to register facility methods");
                topo_node_unbind(fnode);
                /* errno set */
                return (-1);
        }
        /*
         * For both threshold and discrete sensors we set up a propmethod for
         * getting the sensor state and properties to hold the entity ref,
         * sensor class and sensor type.
         */
        if ((entity_refs = topo_mod_alloc(mod, sizeof (char *))) == NULL)
                return (topo_mod_seterrno(mod, EMOD_NOMEM));

        entity_refs[0] = topo_mod_strdup(mod, sd->sd_entity_ref);

        if (topo_prop_set_string_array(fnode, TOPO_PGROUP_FACILITY,
            "entity_ref", TOPO_PROP_IMMUTABLE, (const char **)entity_refs, 1,
            &err) != 0) {
                topo_mod_dprintf(mod, "%s: Failed to set entity_ref property "
                    "on node: %s=%" PRIu64 " (%s)\n", __func__,
                    topo_node_name(fnode), topo_node_instance(fnode),
                    topo_strerror(err));
                topo_mod_strfreev(mod, entity_refs, 1);
                return (topo_mod_seterrno(mod, err));
        }
        topo_mod_strfreev(mod, entity_refs, 1);

        if (topo_prop_set_string(fnode, TOPO_PGROUP_FACILITY, TOPO_SENSOR_CLASS,
            TOPO_PROP_IMMUTABLE, sd->sd_class, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to set %s property on node: "
                    "%s=%" PRIu64 " (%s)\n", TOPO_SENSOR_CLASS,
                    topo_node_name(fnode), topo_node_instance(fnode),
                    topo_strerror(err));
                return (topo_mod_seterrno(mod, err));
        }
        if (topo_prop_set_uint32(fnode, TOPO_PGROUP_FACILITY,
            TOPO_FACILITY_TYPE, TOPO_PROP_IMMUTABLE, sd->sd_stype, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to set %s property on node: "
                    "%s=%" PRIu64 " (%s)\n", TOPO_FACILITY_TYPE,
                    topo_node_name(fnode), topo_node_instance(fnode),
                    topo_strerror(err));
                return (topo_mod_seterrno(mod, err));
        }
        if (topo_mod_nvalloc(mod, &arg_nvl, NV_UNIQUE_NAME) < 0) {
                topo_node_unbind(fnode);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }

        if ((ret = nvlist_add_string(arg_nvl, "ipmi_entity", sd->sd_entity_ref))
            != 0) {
                topo_mod_dprintf(mod, "Failed build arg nvlist (%s)\n",
                    strerror(ret));
                nvlist_free(arg_nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }

        if (topo_prop_method_register(fnode, TOPO_PGROUP_FACILITY,
            TOPO_SENSOR_STATE, TOPO_TYPE_UINT32, "ipmi_sensor_state", arg_nvl,
            &err) != 0) {
                topo_mod_dprintf(mod, "Failed to register %s propmeth on fac "
                    "node %s (%s)\n", TOPO_SENSOR_STATE, topo_node_name(fnode),
                    topo_strerror(err));
                nvlist_free(arg_nvl);
                return (topo_mod_seterrno(mod, err));
        }

        /*
         * If it's a discrete sensor then we're done.  For threshold sensors,
         * there are additional properties to set up.
         */
        if (strcmp(sd->sd_class, TOPO_SENSOR_CLASS_THRESHOLD) != 0) {
                nvlist_free(arg_nvl);
                return (0);
        }

        /*
         * Create properties to expose the analog sensor reading, the unit
         * type and the upper and lower thresholds, if available.
         */
        if (topo_prop_method_register(fnode, TOPO_PGROUP_FACILITY,
            TOPO_SENSOR_READING, TOPO_TYPE_DOUBLE, "ipmi_sensor_reading",
            arg_nvl, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to register %s propmeth on fac "
                    "node %s (%s)\n", TOPO_SENSOR_READING,
                    topo_node_name(fnode), topo_strerror(err));
                nvlist_free(arg_nvl);
                return (topo_mod_seterrno(mod, err));
        }
        if (topo_prop_set_uint32(fnode, TOPO_PGROUP_FACILITY,
            TOPO_SENSOR_UNITS, TOPO_PROP_IMMUTABLE, sd->sd_units, &err) != 0) {
                topo_mod_dprintf(mod, "Failed to set units property on node "
                    "%s (%s)\n", topo_node_name(fnode), topo_strerror(err));
                nvlist_free(arg_nvl);
                return (topo_mod_seterrno(mod, err));
        }
        nvlist_free(arg_nvl);

        /*
         * It is possible (though unusual) for a compact sensor record to
         * represent a threshold sensor.  However, due to how
         * ipmi_sdr_conv_reading() is currently implemented, we only support
         * gathering threshold readings on sensors enumerated from Full Sensor
         * Records.
         */
        if (sd->sd_fs_sdr == NULL)
                return (0);

        if (ipmi_get_sensor_thresholds(hdl, &thresh,
            sd->sd_fs_sdr->is_fs_number) != 0) {
                /*
                 * Some sensors report supporting reading thresholds, but Get
                 * Sensor Thresholds returns Invalid Command.  Do not consider
                 * this an error so we could continue enumerating sensors for
                 * the entity.
                 */
                if (ipmi_errno(hdl) == EIPMI_INVALID_COMMAND)
                        return (0);

                topo_mod_dprintf(mod, "Failed to get sensor thresholds for "
                    "node %s (%s)\n", topo_node_name(fnode), ipmi_errmsg(hdl));
                return (topo_mod_seterrno(mod, EMOD_PARTIAL_ENUM));
        }

        /*
         * The IPMI Get Sensor Thresholds command returns a bitmask describing
         * which of the 3 upper and lower thresholds are readable.  Iterate
         * through those and create a topo property for each threshold that is
         * readable.
         */
        mask = thresh.ithr_readable_mask;
        for (i = 0; i < num_thresholds; i++) {
                if (!ISBITSET(mask, threshset[i].sthr_threshbit))
                        continue;

                if (set_thresh_prop(mod, fnode, sd->sd_fs_sdr,
                    *(uint8_t *)((char *)&thresh +
                    threshset[i].sthr_threshoff), &threshset[i]) != 0) {
                        /* errno set */
                        return (-1);
                }
        }
        return (0);
}

static boolean_t
seq_search(char *key, char **list, uint_t nelem)
{
        for (int i = 0; i < nelem; i++)
                if (strcmp(key, list[i]) == 0)
                        return (B_TRUE);
        return (B_FALSE);
}

/* ARGSUSED */
static int
sdr_callback(ipmi_handle_t *hdl, const char *id, ipmi_sdr_t *sdr, void *data)
{
        uint8_t sensor_entity, sensor_inst;
        int sensor_idlen;
        ipmi_sdr_full_sensor_t *f_sensor = NULL;
        ipmi_sdr_compact_sensor_t *c_sensor = NULL;
        struct sensor_data sd;
        struct entity_info *ei = (struct entity_info *)data;

        switch (sdr->is_type) {
                case IPMI_SDR_TYPE_FULL_SENSOR:
                        f_sensor =
                            (ipmi_sdr_full_sensor_t *)sdr->is_record;
                        sensor_entity = f_sensor->is_fs_entity_id;
                        sensor_inst = f_sensor->is_fs_entity_instance;
                        sensor_idlen = f_sensor->is_fs_idlen;
                        (void) strncpy(sd.sd_entity_ref,
                            f_sensor->is_fs_idstring,
                            f_sensor->is_fs_idlen);
                        sd.sd_entity_ref[sensor_idlen] = '\0';
                        sd.sd_units = f_sensor->is_fs_unit2;
                        sd.sd_stype = f_sensor->is_fs_type;
                        sd.sd_rtype = f_sensor->is_fs_reading_type;
                        sd.sd_fs_sdr = f_sensor;
                        break;
                case IPMI_SDR_TYPE_COMPACT_SENSOR:
                        c_sensor =
                            (ipmi_sdr_compact_sensor_t *)sdr->is_record;
                        sensor_entity = c_sensor->is_cs_entity_id;
                        sensor_inst = c_sensor->is_cs_entity_instance;
                        sensor_idlen = c_sensor->is_cs_idlen;
                        (void) strncpy(sd.sd_entity_ref,
                            c_sensor->is_cs_idstring,
                            sensor_idlen);
                        sd.sd_entity_ref[sensor_idlen] = '\0';
                        sd.sd_units = c_sensor->is_cs_unit2;
                        sd.sd_stype = c_sensor->is_cs_type;
                        sd.sd_rtype = c_sensor->is_cs_reading_type;
                        sd.sd_fs_sdr = NULL;
                        break;
                default:
                        return (0);
        }
        if (sd.sd_rtype == IPMI_RT_THRESHOLD)
                sd.sd_class = TOPO_SENSOR_CLASS_THRESHOLD;
        else
                sd.sd_class = TOPO_SENSOR_CLASS_DISCRETE;

        /*
         * We offset the threshold and generic sensor reading types by 0x100
         */
        if (sd.sd_rtype >= 0x1 && sd.sd_rtype <= 0xc)
                sd.sd_stype = sd.sd_rtype + 0x100;

        if ((ei->ei_list != NULL && seq_search(sd.sd_entity_ref,
            ei->ei_list, ei->ei_listsz) == B_TRUE) ||
            (sensor_entity == ei->ei_id && sensor_inst == ei->ei_inst)) {

                if (make_sensor_node(ei->ei_mod, ei->ei_node, &sd, hdl) != 0) {
                        topo_mod_dprintf(ei->ei_mod, "Failed to create sensor "
                            "node for %s\n", sd.sd_entity_ref);
                        if (topo_mod_errno(ei->ei_mod) != EMOD_NODE_DUP)
                                return (-1);
                }
        }
        return (0);
}

static int
get_entity_info(topo_mod_t *mod, tnode_t *node, ipmi_handle_t *hdl,
    struct entity_info *ei)
{
        char **entity_refs;
        int err;
        uint_t nelems;
        ipmi_sdr_t *ref_sdr;
        ipmi_sdr_full_sensor_t *fsensor;
        ipmi_sdr_compact_sensor_t *csensor;
        ipmi_sdr_fru_locator_t *floc;
        ipmi_sdr_generic_locator_t *gloc;
        boolean_t found_sdr = B_FALSE;

        /*
         * Use the entity ref to lookup the SDR, which will have the entity ID
         * and instance.
         */
        if (topo_prop_get_string_array(node, TOPO_PGROUP_IPMI,
            "entity_ref", &entity_refs, &nelems, &err) != 0) {
                topo_mod_dprintf(mod, "%s: Failed to lookup entity_ref "
                    "property on %s=%" PRIu64 " (%s)\n", __func__,
                    topo_node_name(node), topo_node_instance(node),
                    topo_strerror(err));
                topo_mod_ipmi_rele(mod);
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        for (int i = 0; i < nelems; i++) {
                if ((ref_sdr = ipmi_sdr_lookup(hdl, entity_refs[i])) != NULL) {
                        found_sdr = B_TRUE;
                        break;
                } else
                        topo_mod_dprintf(mod, "%s: Failed to lookup SDR for %s "
                            "(%s)\n", __func__, entity_refs[i],
                            ipmi_errmsg(hdl));
        }
        topo_mod_strfreev(mod, entity_refs, nelems);
        if (! found_sdr) {
                topo_mod_ipmi_rele(mod);
                return (-1);
        }

        switch (ref_sdr->is_type) {
                case IPMI_SDR_TYPE_FULL_SENSOR:
                        fsensor = (ipmi_sdr_full_sensor_t *)ref_sdr->is_record;
                        ei->ei_id = fsensor->is_fs_entity_id;
                        ei->ei_inst = fsensor->is_fs_entity_instance;
                        break;
                case IPMI_SDR_TYPE_COMPACT_SENSOR:
                        csensor
                            = (ipmi_sdr_compact_sensor_t *)ref_sdr->is_record;
                        ei->ei_id = csensor->is_cs_entity_id;
                        ei->ei_inst = csensor->is_cs_entity_instance;
                        break;
                case IPMI_SDR_TYPE_FRU_LOCATOR:
                        floc = (ipmi_sdr_fru_locator_t *)ref_sdr->is_record;
                        ei->ei_id = floc->is_fl_entity;
                        ei->ei_inst = floc->is_fl_instance;
                        break;
                case IPMI_SDR_TYPE_GENERIC_LOCATOR:
                        gloc = (ipmi_sdr_generic_locator_t *)ref_sdr->is_record;
                        ei->ei_id = gloc->is_gl_entity;
                        ei->ei_inst = gloc->is_gl_instance;
                        break;
                default:
                        topo_mod_dprintf(mod, "Failed to determine entity id "
                            "and instance: %s\n", ipmi_errmsg(hdl));
                        topo_mod_ipmi_rele(mod);
                        return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }
        return (0);
}

/* ARGSUSED */
static int
ipmi_sensor_enum(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        int err, ret = -1;
        struct entity_info ei = {0};
        ipmi_handle_t *hdl;

        if ((hdl = topo_mod_ipmi_hold(mod)) == NULL) {
                topo_mod_dprintf(mod, "Failed to get IPMI handle\n");
                return (-1);
        }

        /*
         * For cases where the records in the SDR are hopelessly broken, then
         * we'll resort to hardcoding a list of sensor entities that should be
         * bound to this particular node.  Otherwise, we'll first check if the
         * properties for the associated IPMI entity id and instance exist.  If
         * not, we check for a property referencing an IPMI entity name on which
         * we can lookup the entity ID and instance.  If none of the above pans
         * out, then we bail out.
         */
        if (topo_prop_get_string_array(node, TOPO_PGROUP_IPMI,
            TOPO_PROP_IPMI_ENTITY_LIST, &ei.ei_list, &ei.ei_listsz, &err)
            != 0 && (topo_prop_get_uint32(node, TOPO_PGROUP_IPMI,
            TOPO_PROP_IPMI_ENTITY_ID, &ei.ei_id, &err) != 0 ||
            topo_prop_get_uint32(node, TOPO_PGROUP_IPMI,
            TOPO_PROP_IPMI_ENTITY_INST, &ei.ei_inst, &err) != 0)) {
                if (get_entity_info(mod, node, hdl, &ei) != 0)
                        goto out;
        }
        ei.ei_node = node;
        ei.ei_mod = mod;

        /*
         * Now iterate through all of the full and compact sensor data records
         * and create a sensor facility node for each record that matches our
         * entity ID and instance
         */
        if ((ret = ipmi_sdr_iter(hdl, sdr_callback, &ei)) != 0) {
                topo_mod_dprintf(mod, "ipmi_sdr_iter() failed\n");
        }
out:
        topo_mod_ipmi_rele(mod);
        if (ei.ei_list != NULL)
                topo_mod_strfreev(mod, ei.ei_list, ei.ei_listsz);

        return (ret);
}

static int
ipmi_entity(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        char **fmtarr, **entity_refs, buf[BUFSZ];
        tnode_t *refnode;
        uint_t nelems;
        int ret, inst1, inst2;
        uint32_t offset, nparams;
        nvlist_t *args, *nvl;

        if (vers > TOPO_METH_IPMI_ENTITY_VERSION)
                return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));

        if ((ret = nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args)) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup 'args' list (%s)\n",
                    strerror(ret));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }
        if ((ret = nvlist_lookup_uint32(args, "offset", &offset)) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup 'offset' arg (%s)\n",
                    strerror(ret));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }
        if ((ret = nvlist_lookup_uint32(args, "nparams", &nparams)) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup 'nparams' arg (%s)\n",
                    strerror(ret));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }
        if (nvlist_lookup_string_array(args, "format", &fmtarr, &nelems) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup 'format' arg (%s)\n",
                    strerror(errno));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        if ((entity_refs = topo_mod_alloc(mod, (nelems * sizeof (char *))))
            == NULL)
                return (topo_mod_seterrno(mod, EMOD_NOMEM));

        if (topo_node_flags(node) & TOPO_NODE_FACILITY)
                refnode = topo_node_parent(node);
        else
                refnode = node;

        for (int i = 0; i < nelems; i++) {
                switch (nparams) {
                case 1:
                        /* LINTED: E_SEC_PRINTF_VAR_FMT */
                        (void) snprintf(buf, BUFSZ, fmtarr[i],
                            (topo_node_instance(refnode) + offset));
                        break;
                case 2:
                        inst1 = topo_node_instance(topo_node_parent(refnode))
                            + offset;
                        inst2 = topo_node_instance(refnode) + offset;
                        /* LINTED: E_SEC_PRINTF_VAR_FMT */
                        (void) snprintf(buf, BUFSZ, fmtarr[i], inst1, inst2);
                        break;
                default:
                        topo_mod_dprintf(mod, "Invalid 'nparams' argval (%d)\n",
                            nparams);
                        topo_mod_strfreev(mod, entity_refs, nelems);
                        return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
                }
                entity_refs[i] = topo_mod_strdup(mod, buf);
        }
        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, "entity_ref") != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE,
            TOPO_TYPE_STRING_ARRAY) != 0 ||
            nvlist_add_string_array(nvl, TOPO_PROP_VAL_VAL, entity_refs,
            nelems) != 0) {

                topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
                topo_mod_strfreev(mod, entity_refs, nelems);
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }
        topo_mod_strfreev(mod, entity_refs, nelems);
        *out = nvl;

        return (0);
}

/* ARGSUSED */
static int
dimm_ipmi_entity(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        char **fmtarr, **entity_refs, buf[BUFSZ];
        tnode_t *chip, *dimm;
        int ret;
        uint_t nelems;
        uint32_t offset;
        nvlist_t *args, *nvl;

        if ((ret = nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args)) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup 'args' list (%s)\n",
                    strerror(ret));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }
        if ((ret = nvlist_lookup_uint32(args, "offset", &offset)) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup 'offset' arg (%s)\n",
                    strerror(ret));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }
        if (nvlist_lookup_string_array(args, "format", &fmtarr, &nelems) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup 'format' arg (%s)\n",
                    strerror(errno));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        if ((entity_refs = topo_mod_alloc(mod, (nelems * sizeof (char *))))
            == NULL)
                return (topo_mod_seterrno(mod, EMOD_NOMEM));

        if (topo_node_flags(node) & TOPO_NODE_FACILITY)
                dimm = topo_node_parent(node);
        else
                dimm = node;

        chip = topo_node_parent(topo_node_parent(dimm));

        for (int i = 0; i < nelems; i++) {
                /* LINTED: E_SEC_PRINTF_VAR_FMT */
                (void) snprintf(buf, BUFSZ, fmtarr[i], topo_node_instance(chip),
                    (topo_node_instance(dimm) + offset));
                entity_refs[i] = topo_mod_strdup(mod, buf);
        }

        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, "entity_ref") != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE,
            TOPO_TYPE_STRING_ARRAY) != 0 ||
            nvlist_add_string_array(nvl, TOPO_PROP_VAL_VAL, entity_refs, nelems)
            != 0) {
                topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
                topo_mod_strfreev(mod, entity_refs, nelems);
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }
        topo_mod_strfreev(mod, entity_refs, nelems);
        *out = nvl;

        return (0);
}

/* ARGSUSED */
static int
cs_ipmi_entity(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        char **fmtarr, **entity_refs, buf[BUFSZ];
        tnode_t *chip, *chan, *cs;
        int ret, dimm_num;
        uint_t nelems;
        uint32_t offset;
        nvlist_t *args, *nvl;

        if ((ret = nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args)) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup 'args' list (%s)\n",
                    strerror(ret));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }
        if ((ret = nvlist_lookup_uint32(args, "offset", &offset)) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup 'offset' arg (%s)\n",
                    strerror(ret));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }
        if (nvlist_lookup_string_array(args, "format", &fmtarr, &nelems) != 0) {
                topo_mod_dprintf(mod, "Failed to lookup 'format' arg (%s)\n",
                    strerror(errno));
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        if ((entity_refs = topo_mod_alloc(mod, (nelems * sizeof (char *))))
            == NULL)
                return (topo_mod_seterrno(mod, EMOD_NOMEM));

        if (topo_node_flags(node) & TOPO_NODE_FACILITY) {
                cs = topo_node_parent(node);
                chip = topo_node_parent(topo_node_parent(topo_node_parent(cs)));
                chan = topo_node_parent(cs);

                dimm_num = topo_node_instance(cs) - (topo_node_instance(cs) % 2)
                    + topo_node_instance(cs) + offset;
        } else {
                cs = node;
                chip = topo_node_parent(topo_node_parent(topo_node_parent(cs)));
                chan = topo_node_parent(cs);

                dimm_num = topo_node_instance(cs) - (topo_node_instance(cs) % 2)
                    + topo_node_instance(chan) + offset;
        }

        for (int i = 0; i < nelems; i++) {
                /* LINTED: E_SEC_PRINTF_VAR_FMT */
                (void) snprintf(buf, BUFSZ, fmtarr[i], topo_node_instance(chip),
                    dimm_num);
                entity_refs[i] = topo_mod_strdup(mod, buf);
        }

        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, "entity_ref") != 0 ||
            nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE,
            TOPO_TYPE_STRING_ARRAY) != 0 ||
            nvlist_add_string_array(nvl, TOPO_PROP_VAL_VAL, entity_refs, nelems)
            != 0) {
                topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
                topo_mod_strfreev(mod, entity_refs, nelems);
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }
        topo_mod_strfreev(mod, entity_refs, nelems);
        *out = nvl;

        return (0);
}

/*ARGSUSED*/
static int
fac_prov_ipmi_enum(topo_mod_t *mod, tnode_t *rnode, const char *name,
    topo_instance_t min, topo_instance_t max, void *arg, void *unused)
{
        topo_pgroup_info_t pgi;
        int err;

        if (topo_node_flags(rnode) == TOPO_NODE_DEFAULT) {
                pgi.tpi_name = TOPO_PGROUP_IPMI;
                pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
                pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
                pgi.tpi_version = 1;
                if (topo_pgroup_create(rnode, &pgi, &err) != 0) {
                        if (err != ETOPO_PROP_DEFD) {
                                topo_mod_dprintf(mod,
                                    "pgroups create failure: %s\n",
                                    topo_strerror(err));
                                return (-1);
                        }
                }
                if (topo_method_register(mod, rnode, ipmi_node_methods) != 0) {
                        topo_mod_dprintf(mod, "fac_prov_ipmi_enum: "
                            "topo_method_register() failed: %s",
                            topo_mod_errmsg(mod));
                        return (-1);
                }
        } else {
                if (topo_method_register(mod, rnode, ipmi_fac_methods) != 0) {
                        topo_mod_dprintf(mod, "fac_prov_ipmi_enum: "
                            "topo_method_register() failed: %s",
                            topo_mod_errmsg(mod));
                        return (-1);
                }
        }
        return (0);
}