root/usr/src/lib/fm/topo/modules/common/ses/ses_facility.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 2015 Nexenta Systems, Inc.  All rights reserved.
 */

/*
 * Copyright (c) 2018, Joyent, Inc.
 */

/*
 * Facility node support for SES enclosures.  We support the following facility
 * nodes, based on the node type:
 *
 *      bay
 *              indicator=ident
 *              indicator=fail
 *              indicator=ok2rm
 *              sensor=fault
 *
 *      controller
 *              indicator=ident
 *              indicator=fail
 *
 *      fan
 *              indicator=ident
 *              indicator=fail
 *              sensor=speed
 *              sensor=fault
 *
 *      psu
 *              indicator=ident
 *              indicator=fail
 *              sensor=status
 *
 *      ses-enclosure
 *              indicator=ident
 *              indicator=fail
 *              sensor=fault
 *              sensor=<name>   (temperature)
 *              sensor=<name>   (voltage)
 *              sensor=<name>   (current)
 *
 * Most of these are handled by a single method that supports getting and
 * setting boolean properties on the node.  The fan speed sensor requires a
 * special handler, while the analog enclosure sensors all have similar
 * behavior and can be grouped together using a common method.
 */

#include "ses.h"
#include "disk.h"

#include <string.h>

static int ses_indicator_mode(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);
static int ses_sensor_reading(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);
static int ses_sensor_state(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);
static int ses_psu_state(topo_mod_t *, tnode_t *, topo_version_t,
    nvlist_t *, nvlist_t **);

#define SES_SUPP_WARN_UNDER     0x01
#define SES_SUPP_WARN_OVER      0x02
#define SES_SUPP_CRIT_UNDER     0x04
#define SES_SUPP_CRIT_OVER      0x08

typedef struct ses_sensor_desc {
        int             sd_type;
        int             sd_units;
        const char      *sd_propname;
        double          sd_multiplier;
} ses_sensor_desc_t;

#define TOPO_METH_SES_MODE_VERSION      0
#define TOPO_METH_SES_READING_VERSION   0
#define TOPO_METH_SES_STATE_VERSION     0
#define TOPO_METH_SES_PSU_VERSION       0

#define TOPO_METH_SES_READING_PROP      "propname"
#define TOPO_METH_SES_READING_MULT      "multiplier"

#define TOPO_METH_SES_STATE_PROP        "propname"

#define TOPO_METH_SES_MODE_PROP         "property-name"
#define TOPO_METH_SES_MODE_ALTPROP      "alternate-property"

static const topo_method_t ses_indicator_methods[] = {
        { "ses_indicator_mode", TOPO_PROP_METH_DESC,
            TOPO_METH_SES_MODE_VERSION, TOPO_STABILITY_INTERNAL,
            ses_indicator_mode },
        { NULL }
};

static const topo_method_t ses_sensor_methods[] = {
        { "ses_sensor_reading", TOPO_PROP_METH_DESC,
            TOPO_METH_SES_READING_VERSION, TOPO_STABILITY_INTERNAL,
            ses_sensor_reading },
        { "ses_sensor_state", TOPO_PROP_METH_DESC,
            TOPO_METH_SES_STATE_VERSION, TOPO_STABILITY_INTERNAL,
            ses_sensor_state },
        { "ses_psu_state", TOPO_PROP_METH_DESC,
            TOPO_METH_SES_PSU_VERSION, TOPO_STABILITY_INTERNAL,
            ses_psu_state },
        { NULL }
};

/*
 * Get or set an indicator.  This method is invoked with arguments indicating
 * the property to query to retrieve the value.  Some elements (enclosures and
 * devices) support a request property that is distinct from an array-detected
 * property.  Either of these conditions will result in the indicator being
 * lit, so we have to check both properties.
 */
static int
ses_indicator_mode(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        ses_node_t *np;
        nvlist_t *args, *pargs, *props;
        char *propname, *altprop;
        uint32_t mode;
        boolean_t current, altcurrent;
        nvlist_t *nvl;
        ses_enum_target_t *tp = topo_node_getspecific(tn);

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

        if (nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args) != 0 ||
            nvlist_lookup_string(args, TOPO_METH_SES_MODE_PROP,
            &propname) != 0) {
                topo_mod_dprintf(mod, "invalid arguments to 'mode' method\n");
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        if (nvlist_lookup_string(args, TOPO_METH_SES_MODE_ALTPROP,
            &altprop) != 0)
                altprop = NULL;

        if ((np = ses_node_lock(mod, tn)) == NULL) {
                topo_mod_dprintf(mod, "failed to lookup ses node in 'mode' "
                    "method\n");
                return (-1);
        }
        verify((props = ses_node_props(np)) != NULL);

        if (nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs) == 0 &&
            nvlist_exists(pargs, TOPO_PROP_VAL_VAL)) {
                /* set operation */
                if (nvlist_lookup_uint32(pargs, TOPO_PROP_VAL_VAL,
                    &mode) != 0) {
                        topo_mod_dprintf(mod, "invalid type for indicator "
                            "mode property");
                        (void) topo_mod_seterrno(mod, EMOD_NVL_INVAL);
                        goto error;
                }

                if (mode != TOPO_LED_STATE_OFF && mode != TOPO_LED_STATE_ON) {
                        topo_mod_dprintf(mod, "invalid indicator mode %d\n",
                            mode);
                        (void) topo_mod_seterrno(mod, EMOD_NVL_INVAL);
                        goto error;
                }

                nvl = NULL;
                if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
                    nvlist_add_boolean_value(nvl, propname,
                    mode == TOPO_LED_STATE_ON ? B_TRUE : B_FALSE) != 0) {
                        nvlist_free(nvl);
                        (void) topo_mod_seterrno(mod, EMOD_NOMEM);
                        goto error;
                }

                if (ses_node_ctl(np, SES_CTL_OP_SETPROP, nvl) != 0) {
                        topo_mod_dprintf(mod, "failed to set indicator: %s\n",
                            ses_errmsg());
                        nvlist_free(nvl);
                        goto error;
                }

                tp->set_snaptime = 0;
                nvlist_free(nvl);
        } else {
                /* get operation */
                if (nvlist_lookup_boolean_value(props,
                    propname, &current) != 0) {
                        topo_mod_dprintf(mod, "failed to lookup %s in node "
                            "properties\n", propname);
                        (void) topo_mod_seterrno(mod, EMOD_METHOD_NOTSUP);
                        goto error;
                }

                if (altprop != NULL && nvlist_lookup_boolean_value(props,
                    altprop, &altcurrent) == 0)
                        current |= altcurrent;

                mode = current ? TOPO_LED_STATE_ON : TOPO_LED_STATE_OFF;
        }

        nvl = NULL;
        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) {
                nvlist_free(nvl);
                (void) topo_mod_seterrno(mod, EMOD_NOMEM);
                goto error;
        }

        ses_node_unlock(mod, tn);
        *out = nvl;
        return (0);

error:
        ses_node_unlock(mod, tn);
        return (-1);
}

/*
 * Read the given sensor value.  This just looks up the value in the node
 * properties, and multiplies by a fixed value (determined when the method is
 * instantiated).
 */
static int
ses_sensor_reading(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        ses_node_t *np;
        nvlist_t *args, *props;
        char *prop;
        double raw, multiplier;
        uint64_t current;
        int64_t scurrent;
        nvlist_t *nvl;

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

        if (nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args) != 0 ||
            nvlist_lookup_string(args, TOPO_METH_SES_READING_PROP,
            &prop) != 0) {
                topo_mod_dprintf(mod,
                    "invalid arguments to 'reading' method\n");
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        if (nvlist_lookup_double(args, TOPO_METH_SES_READING_MULT,
            &multiplier) != 0)
                multiplier = 1;

        if ((np = ses_node_lock(mod, tn)) == NULL) {
                topo_mod_dprintf(mod, "failed to lookup ses node in 'mode' "
                    "method\n");
                return (-1);
        }
        verify((props = ses_node_props(np)) != NULL);

        if (nvlist_lookup_uint64(props, prop, &current) == 0) {
                raw = (double)current;
        } else if (nvlist_lookup_int64(props, prop, &scurrent) == 0) {
                raw = (double)scurrent;
        } else {
                topo_mod_dprintf(mod, "failed to lookup %s in node "
                    "properties\n", prop);
                ses_node_unlock(mod, tn);
                return (topo_mod_seterrno(mod, EMOD_METHOD_NOTSUP));
        }

        ses_node_unlock(mod, tn);

        nvl = NULL;
        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, raw * multiplier) != 0) {
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }

        *out = nvl;
        return (0);
}

/*
 * Returns the current sensor state.  This can be invoked for one of two
 * different types of sensors: threshold or discrete sensors.  For discrete
 * sensors, we expect a name of a boolean property and indicate
 * asserted/deasserted based on that.  For threshold sensors, we check for the
 * standard warning/critical properties and translate that into the appropriate
 * topo state.
 */
/*ARGSUSED*/
static int
ses_sensor_state(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        nvlist_t *nvl, *args, *props;
        boolean_t value;
        uint64_t status;
        uint32_t state;
        ses_node_t *np;
        char *prop;

        if (nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args) != 0) {
                topo_mod_dprintf(mod,
                    "invalid arguments to 'state' method\n");
                return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
        }

        if ((np = ses_node_lock(mod, tn)) == NULL) {
                topo_mod_dprintf(mod, "failed to lookup ses node in 'mode' "
                    "method\n");
                return (-1);
        }
        verify((props = ses_node_props(np)) != NULL);

        if (nvlist_lookup_uint64(props, SES_PROP_STATUS_CODE, &status) != 0)
                status = SES_ESC_UNSUPPORTED;

        state = 0;
        if (nvlist_lookup_string(args, TOPO_METH_SES_STATE_PROP,
            &prop) == 0) {
                /* discrete (fault) sensor */

                if (status == SES_ESC_UNRECOVERABLE)
                        state |= TOPO_SENSOR_STATE_GENERIC_FAIL_NONRECOV;
                else if (status == SES_ESC_CRITICAL)
                        state |= TOPO_SENSOR_STATE_GENERIC_FAIL_CRITICAL;
                else if (nvlist_lookup_boolean_value(props, prop,
                    &value) == 0 && value)
                        state |= TOPO_SENSOR_STATE_GENERIC_FAIL_NONRECOV;
                else
                        state |= TOPO_SENSOR_STATE_GENERIC_FAIL_DEASSERTED;
        } else {
                /* threshold sensor */
                if (nvlist_lookup_boolean_value(props,
                    SES_PROP_WARN_UNDER, &value) == 0 && value)
                        state |= TOPO_SENSOR_STATE_THRESH_LOWER_NONCRIT;
                if (nvlist_lookup_boolean_value(props,
                    SES_PROP_WARN_OVER, &value) == 0 && value)
                        state |= TOPO_SENSOR_STATE_THRESH_UPPER_NONCRIT;
                if (nvlist_lookup_boolean_value(props,
                    SES_PROP_CRIT_UNDER, &value) == 0 && value)
                        state |= TOPO_SENSOR_STATE_THRESH_LOWER_CRIT;
                if (nvlist_lookup_boolean_value(props,
                    SES_PROP_CRIT_OVER, &value) == 0 && value)
                        state |= TOPO_SENSOR_STATE_THRESH_UPPER_CRIT;
        }

        ses_node_unlock(mod, tn);

        nvl = NULL;
        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) {
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }

        *out = nvl;
        return (0);
}

/*
 * Read the status of a PSU.  This is such a specialized operation that it has
 * its own method instead of trying to piggyback on ses_sensor_state().  We
 * use the following mapping to get to the standard topo power supply states:
 *
 *      acfail          -> INPUT_LOST
 *      dcfail          -> INPUT_LOST
 *      undervoltage    -> INPUT_RANGE
 *      overvoltage     -> INPUT_RANGE_PRES
 *      overcurrent     -> INPUT_RANGE_PRES
 *      overtemp        -> (none)
 *
 * If we ever have a need for reading overtemp, we can expand the topo
 * representation for power supplies, but at the moment this seems unnecessary.
 */
/*ARGSUSED*/
static int
ses_psu_state(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        nvlist_t *nvl, *props;
        boolean_t value;
        uint32_t state;
        ses_node_t *np;

        if ((np = ses_node_lock(mod, tn)) == NULL) {
                topo_mod_dprintf(mod, "failed to lookup ses node in 'mode' "
                    "method\n");
                return (-1);
        }
        verify((props = ses_node_props(np)) != NULL);

        state = 0;
        if ((nvlist_lookup_boolean_value(props, SES_PSU_PROP_DC_FAIL,
            &value) == 0 && value) ||
            (nvlist_lookup_boolean_value(props, SES_PSU_PROP_AC_FAIL,
            &value) == 0 && value))
                state |= TOPO_SENSOR_STATE_POWER_SUPPLY_INPUT_LOST;

        if (nvlist_lookup_boolean_value(props, SES_PSU_PROP_DC_UNDER_VOLTAGE,
            &value) == 0 && value)
                state |= TOPO_SENSOR_STATE_POWER_SUPPLY_INPUT_RANGE;

        if ((nvlist_lookup_boolean_value(props, SES_PSU_PROP_DC_OVER_VOLTAGE,
            &value) == 0 && value) ||
            (nvlist_lookup_boolean_value(props, SES_PSU_PROP_DC_OVER_CURRENT,
            &value) == 0 && value))
                state |= TOPO_SENSOR_STATE_POWER_SUPPLY_INPUT_RANGE_PRES;

        ses_node_unlock(mod, tn);

        nvl = NULL;
        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) {
                nvlist_free(nvl);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }

        *out = nvl;
        return (0);
}

/*
 * Create a facility node, either a sensor or an indicator.
 */
static tnode_t *
ses_add_fac_common(topo_mod_t *mod, tnode_t *pnode, const char *name,
    const char *type, uint64_t nodeid)
{
        tnode_t *tn;
        topo_pgroup_info_t pgi;
        int err;
        ses_enum_target_t *stp = topo_node_getspecific(pnode);

        if ((tn = topo_node_facbind(mod, pnode, name, type)) == NULL) {
                topo_mod_dprintf(mod, "failed to bind facility node %s\n",
                    name);
                return (NULL);
        }

        stp->set_refcount++;
        topo_node_setspecific(tn, stp);

        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(tn, &pgi, &err) != 0) {
                topo_mod_dprintf(mod, "failed to create facility property "
                    "group: %s\n", topo_strerror(err));
                topo_node_unbind(tn);
                return (NULL);
        }

        /*
         * We need the node-id property for each facility node.
         */
        pgi.tpi_name = TOPO_PGROUP_SES;
        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) {
                topo_mod_dprintf(mod, "failed to create ses property "
                    "group: %s\n", topo_strerror(err));
                topo_node_unbind(tn);
                return (NULL);
        }

        if (topo_prop_set_uint64(tn, TOPO_PGROUP_SES,
            TOPO_PROP_NODE_ID, TOPO_PROP_IMMUTABLE,
            nodeid, &err) != 0) {
                topo_mod_dprintf(mod,
                    "failed to create property %s: %s\n",
                    TOPO_PROP_NODE_ID, topo_strerror(err));
                topo_node_unbind(tn);
                return (NULL);
        }

        return (tn);
}

/*
 * Add an indicator.  This can be represented by a single property, or by the
 * union of two elements when SES is capable of distinguishing between
 * requested failure and detected failure.
 */
static int
ses_add_indicator(topo_mod_t *mod, tnode_t *pnode, uint64_t nodeid,
    int type, const char *name, const char *propname, const char *altprop)
{
        tnode_t *tn;
        int err;
        nvlist_t *nvl;

        /* create facility node and add methods */
        if ((tn = ses_add_fac_common(mod, pnode, name,
            TOPO_FAC_TYPE_INDICATOR, nodeid)) == NULL)
                return (-1);

        if (topo_method_register(mod, tn, ses_indicator_methods) < 0) {
                topo_mod_dprintf(mod, "failed to register facility methods\n");
                topo_node_unbind(tn);
                return (-1);
        }

        /* set standard properties */
        if (topo_prop_set_uint32(tn, TOPO_PGROUP_FACILITY,
            TOPO_FACILITY_TYPE, TOPO_PROP_IMMUTABLE, type, &err) != 0) {
                topo_mod_dprintf(mod,
                    "failed to set facility node properties: %s\n",
                    topo_strerror(err));
                topo_node_unbind(tn);
                return (-1);
        }

        /* 'mode' property */
        nvl = NULL;
        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_METH_SES_MODE_PROP,
            propname) != 0 ||
            (altprop != NULL && nvlist_add_string(nvl,
            TOPO_METH_SES_MODE_ALTPROP, altprop) != 0)) {
                nvlist_free(nvl);
                topo_mod_dprintf(mod, "failed to setup method arguments\n");
                topo_node_unbind(tn);
                return (topo_mod_seterrno(mod, EMOD_NOMEM));
        }

        if (topo_prop_method_register(tn, TOPO_PGROUP_FACILITY,
            TOPO_LED_MODE, TOPO_TYPE_UINT32, "ses_indicator_mode",
            nvl, &err) != 0) {
                nvlist_free(nvl);
                topo_mod_dprintf(mod, "failed to register reading method: %s\n",
                    topo_strerror(err));
                return (-1);
        }

        if (topo_prop_setmutable(tn, TOPO_PGROUP_FACILITY,
            TOPO_LED_MODE, &err) != 0) {
                nvlist_free(nvl);
                topo_mod_dprintf(mod, "failed to set property as mutable: %s\n",
                    topo_strerror(err));
                return (-1);
        }

        nvlist_free(nvl);
        return (0);
}

static tnode_t *
ses_add_sensor_common(topo_mod_t *mod, tnode_t *pnode, uint64_t nodeid,
    const char *name, const char *class, int type)
{
        tnode_t *tn;
        int err;

        /* create facility node and add methods */
        if ((tn = ses_add_fac_common(mod, pnode, name,
            TOPO_FAC_TYPE_SENSOR, nodeid)) == NULL)
                return (NULL);

        if (topo_method_register(mod, tn, ses_sensor_methods) < 0) {
                topo_mod_dprintf(mod, "failed to register facility methods\n");
                topo_node_unbind(tn);
                return (NULL);
        }

        /* set standard properties */
        if (topo_prop_set_string(tn, TOPO_PGROUP_FACILITY,
            TOPO_SENSOR_CLASS, TOPO_PROP_IMMUTABLE,
            class, &err) != 0 ||
            topo_prop_set_uint32(tn, TOPO_PGROUP_FACILITY,
            TOPO_FACILITY_TYPE, TOPO_PROP_IMMUTABLE,
            type, &err) != 0) {
                topo_mod_dprintf(mod,
                    "failed to set facility node properties: %s\n",
                    topo_strerror(err));
                topo_node_unbind(tn);
                return (NULL);
        }

        return (tn);
}

/*
 * Add an analog (threshold) sensor to the enclosure.  This is used for fan
 * speed, voltage, current, and temperature sensors.
 */
static int
ses_add_sensor(topo_mod_t *mod, tnode_t *pnode, uint64_t nodeid,
    const char *name, const ses_sensor_desc_t *sdp)
{
        tnode_t *tn;
        int err;
        nvlist_t *nvl;

        if ((tn = ses_add_sensor_common(mod, pnode, nodeid, name,
            TOPO_SENSOR_CLASS_THRESHOLD, sdp->sd_type)) == NULL)
                return (-1);

        if (topo_prop_set_uint32(tn, TOPO_PGROUP_FACILITY,
            TOPO_SENSOR_UNITS, TOPO_PROP_IMMUTABLE, sdp->sd_units, &err) != 0) {
                topo_mod_dprintf(mod,
                    "failed to set facility node properties: %s\n",
                    topo_strerror(err));
                topo_node_unbind(tn);
                return (-1);
        }

        /* 'reading' property */
        nvl = NULL;
        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_METH_SES_READING_PROP,
            sdp->sd_propname) != 0 ||
            (sdp->sd_multiplier != 0 &&
            nvlist_add_double(nvl, TOPO_METH_SES_READING_MULT,
            sdp->sd_multiplier) != 0)) {
                nvlist_free(nvl);
                topo_mod_dprintf(mod, "failed to setup method arguments\n");
                topo_node_unbind(tn);
                return (-1);
        }

        if (topo_prop_method_register(tn, TOPO_PGROUP_FACILITY,
            TOPO_SENSOR_READING, TOPO_TYPE_DOUBLE, "ses_sensor_reading",
            nvl, &err) != 0) {
                nvlist_free(nvl);
                topo_mod_dprintf(mod, "failed to register reading method: %s\n",
                    topo_strerror(err));
                return (-1);
        }

        nvlist_free(nvl);
        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0) {
                topo_mod_dprintf(mod, "failed to setup method arguments\n");
                topo_node_unbind(tn);
                return (-1);
        }

        /* 'state' property */
        if (topo_prop_method_register(tn, TOPO_PGROUP_FACILITY,
            TOPO_SENSOR_STATE, TOPO_TYPE_UINT32, "ses_sensor_state",
            nvl, &err) != 0) {
                nvlist_free(nvl);
                topo_mod_dprintf(mod, "failed to register state method: %s\n",
                    topo_strerror(err));
                return (-1);
        }

        nvlist_free(nvl);
        return (0);
}

/*
 * Add a discrete sensor for simple boolean values.  This is used to indicate
 * externally-detected failures for fans, bays, and enclosures.
 */
static int
ses_add_discrete(topo_mod_t *mod, tnode_t *pnode, uint64_t nodeid,
    const char *name, const char *prop)
{
        tnode_t *tn;
        int err;
        nvlist_t *nvl;

        if ((tn = ses_add_sensor_common(mod, pnode, nodeid, name,
            TOPO_SENSOR_CLASS_DISCRETE,
            TOPO_SENSOR_TYPE_GENERIC_FAILURE)) == NULL)
                return (-1);

        nvl = NULL;
        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
            nvlist_add_string(nvl, TOPO_METH_SES_STATE_PROP, prop) != 0) {
                nvlist_free(nvl);
                topo_mod_dprintf(mod, "failed to setup method arguments\n");
                topo_node_unbind(tn);
                return (-1);
        }

        /* 'state' property */
        if (topo_prop_method_register(tn, TOPO_PGROUP_FACILITY,
            TOPO_SENSOR_STATE, TOPO_TYPE_UINT32, "ses_sensor_state",
            nvl, &err) != 0) {
                nvlist_free(nvl);
                topo_mod_dprintf(mod, "failed to register state method: %s\n",
                    topo_strerror(err));
                return (-1);
        }

        nvlist_free(nvl);
        return (0);
}

/*ARGSUSED*/
static int
ses_add_psu_status(topo_mod_t *mod, tnode_t *pnode, uint64_t nodeid)
{
        tnode_t *tn;
        int err;
        nvlist_t *nvl;

        /* create facility node and add methods */
        if ((tn = ses_add_sensor_common(mod, pnode, nodeid, "status",
            TOPO_SENSOR_CLASS_DISCRETE,
            TOPO_SENSOR_TYPE_POWER_SUPPLY)) == NULL)
                return (-1);

        if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0) {
                nvlist_free(nvl);
                topo_mod_dprintf(mod, "failed to setup method arguments\n");
                topo_node_unbind(tn);
                return (-1);
        }

        /* 'state' property */
        if (topo_prop_method_register(tn, TOPO_PGROUP_FACILITY,
            TOPO_SENSOR_STATE, TOPO_TYPE_UINT32, "ses_psu_state",
            nvl, &err) != 0) {
                nvlist_free(nvl);
                topo_mod_dprintf(mod, "failed to register state method: %s\n",
                    topo_strerror(err));
                return (-1);
        }

        nvlist_free(nvl);
        return (0);
}

/*ARGSUSED*/
int
ses_node_enum_facility(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        ses_node_t *np;
        nvlist_t *props;
        uint64_t type, nodeid;
        ses_sensor_desc_t sd = { 0 };

        if ((np = ses_node_lock(mod, tn)) == NULL)
                return (-1);

        assert(ses_node_type(np) == SES_NODE_ELEMENT);
        nodeid = ses_node_id(np);
        verify((props = ses_node_props(np)) != NULL);
        verify(nvlist_lookup_uint64(props, SES_PROP_ELEMENT_TYPE, &type) == 0);

        if (type != SES_ET_DEVICE && type != SES_ET_ARRAY_DEVICE &&
            type != SES_ET_COOLING && type != SES_ET_POWER_SUPPLY) {
                ses_node_unlock(mod, tn);
                return (0);
        }

        /*
         * Every element supports an 'ident' indicator.  All elements also
         * support a 'fail' indicator, but the properties used to represent
         * this condition differs between elements.
         */
        if (ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_LOCATE, "ident",
            SES_PROP_IDENT, NULL) != 0)
                goto error;

        switch (type) {
        case SES_ET_DEVICE:
        case SES_ET_ARRAY_DEVICE:
                /*
                 * Disks support an additional 'ok2rm' indicator, as well as
                 * externally detected 'fail' sensor.
                 */
                if (ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_SERVICE,
                    "fail", SES_DEV_PROP_FAULT_RQSTD,
                    SES_DEV_PROP_FAULT_SENSED) != 0 ||
                    ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_OK2RM,
                    "ok2rm", SES_PROP_RMV, SES_PROP_RMV) != 0 ||
                    ses_add_discrete(mod, tn, nodeid, "fault",
                    SES_DEV_PROP_FAULT_SENSED) != 0)
                        goto error;
                break;

        case SES_ET_COOLING:
                /*
                 * Add the fan speed sensor, and a discrete sensor for
                 * detecting failure.
                 */
                sd.sd_type = TOPO_SENSOR_TYPE_THRESHOLD_STATE;
                sd.sd_units = TOPO_SENSOR_UNITS_RPM;
                sd.sd_propname = SES_COOLING_PROP_FAN_SPEED;
                if (ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_SERVICE,
                    "fail", SES_PROP_FAIL, NULL) != 0 ||
                    ses_add_sensor(mod, tn, nodeid, "speed", &sd) != 0 ||
                    ses_add_discrete(mod, tn, nodeid, "fault",
                    SES_PROP_FAIL) != 0)
                        goto error;
                break;

        case SES_ET_POWER_SUPPLY:
                /*
                 * For power supplies, we have a number of different sensors:
                 * acfail, dcfail, overtemp, undervoltate, overvoltage,
                 * and overcurrent.  Rather than expose these all as individual
                 * sensors, we lump them together into a 'status' sensor of
                 * type TOPO_SENSOR_TYPE_POWER_SUPPLY and export the
                 * appropriate status flags as defined by the libtopo standard.
                 */
                if (ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_SERVICE,
                    "fail", SES_PROP_FAIL, NULL) != 0)
                        goto error;

                if (ses_add_psu_status(mod, tn, nodeid) != 0)
                        goto error;
                break;

        default:
                return (0);
        }

        ses_node_unlock(mod, tn);
        return (0);

error:
        ses_node_unlock(mod, tn);
        return (-1);
}

/*
 * Add enclosure-wide sensors (temperature, voltage, and current) beneath the
 * given aggregate.
 */
static int
ses_add_enclosure_sensors(topo_mod_t *mod, tnode_t *tn, ses_node_t *agg,
    uint64_t type)
{
        ses_node_t *child;
        const char *defaultname;
        char *desc, *name;
        char rawname[64];
        nvlist_t *props, *aprops;
        uint64_t index, nodeid;
        ses_sensor_desc_t sd = { 0 };
        size_t len;

        switch (type) {
        case SES_ET_TEMPERATURE_SENSOR:
                sd.sd_type = TOPO_SENSOR_TYPE_TEMP;
                sd.sd_units = TOPO_SENSOR_UNITS_DEGREES_C;
                sd.sd_propname = SES_TEMP_PROP_TEMP;
                defaultname = "temperature";
                break;

        case SES_ET_VOLTAGE_SENSOR:
                sd.sd_type = TOPO_SENSOR_TYPE_VOLTAGE;
                sd.sd_units = TOPO_SENSOR_UNITS_VOLTS;
                sd.sd_propname = SES_VS_PROP_VOLTAGE_MV;
                sd.sd_multiplier = 0.001;
                defaultname = "voltage";
                break;

        case SES_ET_CURRENT_SENSOR:
                sd.sd_type = TOPO_SENSOR_TYPE_CURRENT;
                sd.sd_units = TOPO_SENSOR_UNITS_AMPS;
                sd.sd_propname = SES_CS_PROP_CURRENT_MA;
                sd.sd_multiplier = 0.001;
                defaultname = "current";
                break;

        default:
                return (0);
        }

        aprops = ses_node_props(agg);

        for (child = ses_node_child(agg); child != NULL;
            child = ses_node_sibling(child)) {
                /*
                 * The only tricky part here is getting the name for the
                 * sensor, where we follow the algorithm of the standard
                 * elements.
                 */
                props = ses_node_props(child);
                nodeid = ses_node_id(child);
                if (nvlist_lookup_uint64(props, SES_PROP_ELEMENT_CLASS_INDEX,
                    &index) != 0)
                        continue;

                if (nvlist_lookup_string(props, SES_PROP_DESCRIPTION,
                    &desc) == 0 && desc[0] != '\0') {
                        (void) strlcpy(rawname, desc, sizeof (rawname));
                } else {
                        if (nvlist_lookup_string(aprops,
                            SES_PROP_CLASS_DESCRIPTION, &desc) != 0 ||
                            desc[0] == '\0')
                                desc = (char *)defaultname;

                        len = strlen(desc);
                        while (len > 0 && desc[len - 1] == ' ')
                                len--;

                        (void) snprintf(rawname, sizeof (rawname),
                            "%.*s %llu", len, desc, index);
                }

                if ((name = topo_mod_clean_str(mod, rawname)) == NULL)
                        return (-1);

                if (ses_add_sensor(mod, tn, nodeid, name, &sd) != 0) {
                        topo_mod_strfree(mod, name);
                        return (-1);
                }

                topo_mod_strfree(mod, name);
        }

        return (0);
}

/*ARGSUSED*/
int
ses_enc_enum_facility(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
    nvlist_t *in, nvlist_t **out)
{
        ses_node_t *np, *agg;
        nvlist_t *aprops;
        uint64_t type, nodeid;

        if ((np = ses_node_lock(mod, tn)) == NULL)
                return (-1);

        assert(ses_node_type(np) == SES_NODE_ENCLOSURE);
        nodeid = ses_node_id(np);

        /*
         * 'ident' and 'fail' LEDs, and 'fault' sensor.
         */
        if (ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_LOCATE, "ident",
            SES_PROP_IDENT, NULL) != 0 ||
            ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_SERVICE, "fail",
            SES_PROP_FAIL_REQ, SES_PROP_FAIL) != 0 ||
            ses_add_discrete(mod, tn, nodeid, "fault", SES_PROP_FAIL) != 0)
                goto error;

        /*
         * Environmental sensors (temperature, voltage, current).  We have no
         * way of knowing if any of these sensors correspond to a particular
         * element, so we just attach them to the enclosure as a whole.  In the
         * future, some vendor-specific libses plugin knowledge could let us
         * make this correlation clearer.
         */
        for (agg = ses_node_child(np); agg != NULL;
            agg = ses_node_sibling(agg)) {
                if (ses_node_type(agg) != SES_NODE_AGGREGATE)
                        continue;

                verify((aprops = ses_node_props(agg)) != NULL);
                if (nvlist_lookup_uint64(aprops, SES_PROP_ELEMENT_TYPE,
                    &type) != 0)
                        continue;

                if (ses_add_enclosure_sensors(mod, tn, agg, type) != 0)
                        goto error;
        }

        ses_node_unlock(mod, tn);
        return (0);

error:
        ses_node_unlock(mod, tn);
        return (-1);
}