root/usr/src/lib/libipmi/common/ipmi_entity.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * IPMI entities are a strange beast.  A reasonable assumption for those
 * unfamiliar with the spec would be that there was a command to iterate over
 * all entities, and a command to iterate over sensors associated with each
 * entity.  Instead, the entire IPMI world is derived from the SDR repository.
 * Entities only exist in the sense that they are referenced by a SDR record.
 *
 * In addition, entities can be associated into groups, and determining entity
 * presence is quite complicated.  The IPMI spec dedicates an entire chapter
 * (40) to the process of handling sensor associations.
 *
 * The above logic is implemented via the ipmi_entity_present() function.  We
 * make a first pass over the SDR repository to discover entities, creating
 * entity groups and associating SDR records with the each.
 *
 * We don't currently support device-relative entities.
 */

#include <libipmi.h>
#include <ipmi_impl.h>
#include <stddef.h>

typedef struct ipmi_entity_sdr {
        ipmi_list_t                     ies_list;
        const char                      *ies_name;
        ipmi_sdr_t                      *ies_sdr;
} ipmi_entity_sdr_t;

typedef struct ipmi_entity_impl {
        ipmi_list_t                     ie_list;
        ipmi_entity_t                   ie_entity;
        struct ipmi_entity_impl         *ie_parent;
        ipmi_hash_link_t                ie_link;
        ipmi_list_t                     ie_child_list;
        ipmi_list_t                     ie_sdr_list;
} ipmi_entity_impl_t;

#define ENTITY_TO_IMPL(ep)      \
        ((ipmi_entity_impl_t *)((char *)(ep) - \
        offsetof(ipmi_entity_impl_t, ie_entity)))

static int
ipmi_entity_add_assoc(ipmi_handle_t *ihp, ipmi_entity_impl_t *eip,
    uint8_t id, uint8_t instance)
{
        ipmi_entity_impl_t *cp;
        ipmi_entity_t search;

        search.ie_type = id;
        search.ie_instance = instance;

        if ((cp = ipmi_hash_lookup(ihp->ih_entities, &search)) == NULL) {
                if ((cp = ipmi_zalloc(ihp,
                    sizeof (ipmi_entity_impl_t))) == NULL)
                        return (-1);

                cp->ie_entity.ie_type = id;
                cp->ie_entity.ie_instance = instance;

                ipmi_hash_insert(ihp->ih_entities, cp);
        }

        if (cp->ie_parent != NULL) {
                /*
                 * This should never happen.  However, we want to be tolerant of
                 * pathologically broken IPMI implementations, so we ignore this
                 * error, and the first parent wins.
                 */
                return (0);
        }

        cp->ie_parent = eip;
        ipmi_list_append(&eip->ie_child_list, cp);
        eip->ie_entity.ie_children++;

        return (0);
}

static int
ipmi_entity_sdr_parse(ipmi_sdr_t *sdrp, uint8_t *id, uint8_t *instance,
    boolean_t *logical)
{
        switch (sdrp->is_type) {
        case IPMI_SDR_TYPE_FULL_SENSOR:
                {
                        ipmi_sdr_full_sensor_t *fsp =
                            (ipmi_sdr_full_sensor_t *)sdrp->is_record;
                        *id = fsp->is_fs_entity_id;
                        *instance = fsp->is_fs_entity_instance;
                        *logical = fsp->is_fs_entity_logical;
                        break;
                }

        case IPMI_SDR_TYPE_COMPACT_SENSOR:
                {
                        ipmi_sdr_compact_sensor_t *csp =
                            (ipmi_sdr_compact_sensor_t *)sdrp->is_record;
                        *id = csp->is_cs_entity_id;
                        *instance = csp->is_cs_entity_instance;
                        *logical = csp->is_cs_entity_logical;
                        break;
                }

        case IPMI_SDR_TYPE_EVENT_ONLY:
                {
                        ipmi_sdr_event_only_t *eop =
                            (ipmi_sdr_event_only_t *)sdrp->is_record;
                        *id = eop->is_eo_entity_id;
                        *instance = eop->is_eo_entity_instance;
                        *logical = eop->is_eo_entity_logical;
                        break;
                }

        case IPMI_SDR_TYPE_ENTITY_ASSOCIATION:
                {
                        ipmi_sdr_entity_association_t *eap =
                            (ipmi_sdr_entity_association_t *)sdrp->is_record;
                        *id = eap->is_ea_entity_id;
                        *instance = eap->is_ea_entity_instance;
                        *logical = B_TRUE;
                        break;
                }

        case IPMI_SDR_TYPE_GENERIC_LOCATOR:
                {
                        ipmi_sdr_generic_locator_t *glp =
                            (ipmi_sdr_generic_locator_t *)sdrp->is_record;
                        *id = glp->is_gl_entity;
                        *instance = glp->is_gl_instance;
                        *logical = B_FALSE;
                        break;
                }

        case IPMI_SDR_TYPE_FRU_LOCATOR:
                {
                        ipmi_sdr_fru_locator_t *flp =
                            (ipmi_sdr_fru_locator_t *)sdrp->is_record;
                        *id = flp->is_fl_entity;
                        *instance = flp->is_fl_instance;
                        *logical = B_FALSE;
                        break;
                }

        case IPMI_SDR_TYPE_MANAGEMENT_LOCATOR:
                {
                        ipmi_sdr_management_locator_t *mlp =
                            (ipmi_sdr_management_locator_t *)sdrp->is_record;
                        *id = mlp->is_ml_entity_id;
                        *instance = mlp->is_ml_entity_instance;
                        *logical = B_FALSE;
                        break;
                }

        default:
                return (-1);
        }

        return (0);
}

/*
 * This function is responsible for gathering all entities, inserting them into
 * the global hash, and establishing any associations.
 */
/*ARGSUSED*/
static int
ipmi_entity_visit(ipmi_handle_t *ihp, const char *name, ipmi_sdr_t *sdrp,
    void *unused)
{
        uint8_t id, instance;
        boolean_t logical;
        ipmi_entity_t search;
        ipmi_entity_impl_t *eip;
        ipmi_entity_sdr_t *esp;

        if (ipmi_entity_sdr_parse(sdrp, &id, &instance, &logical) != 0)
                return (0);

        search.ie_type = id;
        search.ie_instance = instance;

        if ((eip = ipmi_hash_lookup(ihp->ih_entities, &search)) == NULL) {
                if ((eip = ipmi_zalloc(ihp,
                    sizeof (ipmi_entity_impl_t))) == NULL)
                        return (-1);

                eip->ie_entity.ie_type = id;
                eip->ie_entity.ie_instance = instance;

                ipmi_hash_insert(ihp->ih_entities, eip);
        }

        eip->ie_entity.ie_logical |= logical;

        if (sdrp->is_type == IPMI_SDR_TYPE_ENTITY_ASSOCIATION) {
                uint8_t start, end;
                uint8_t i, type;

                ipmi_sdr_entity_association_t *eap =
                    (ipmi_sdr_entity_association_t *)sdrp->is_record;

                if (eap->is_ea_range) {

                        type = eap->is_ea_sub[0].is_ea_sub_id;
                        start = eap->is_ea_sub[0].is_ea_sub_instance;
                        end = eap->is_ea_sub[1].is_ea_sub_instance;

                        if (type != 0) {
                                for (i = start; i <= end; i++) {
                                        if (ipmi_entity_add_assoc(ihp, eip,
                                            type, i) != 0)
                                                return (-1);
                                }
                        }

                        type = eap->is_ea_sub[2].is_ea_sub_id;
                        start = eap->is_ea_sub[2].is_ea_sub_instance;
                        end = eap->is_ea_sub[3].is_ea_sub_instance;

                        if (type != 0) {
                                for (i = start; i <= end; i++) {
                                        if (ipmi_entity_add_assoc(ihp, eip,
                                            type, i) != 0)
                                                return (-1);
                                }
                        }
                } else {
                        for (i = 0; i < 4; i++) {
                                type = eap->is_ea_sub[i].is_ea_sub_id;
                                instance = eap->is_ea_sub[i].is_ea_sub_instance;

                                if (type == 0)
                                        continue;

                                if (ipmi_entity_add_assoc(ihp, eip, type,
                                    instance) != 0)
                                        return (-1);
                        }
                }
        } else {
                if ((esp = ipmi_zalloc(ihp,
                    sizeof (ipmi_entity_sdr_t))) == NULL)
                        return (-1);

                esp->ies_sdr = sdrp;
                esp->ies_name = name;
                ipmi_list_append(&eip->ie_sdr_list, esp);
        }

        return (0);
}

/*
 * Given a SDR record, return boolean values indicating whether the sensor
 * indicates explicit presence.
 *
 * XXX this should really share code with entity_present()
 */
int
ipmi_entity_present_sdr(ipmi_handle_t *ihp, ipmi_sdr_t *sdrp,
    boolean_t *valp)
{
        uint16_t mask;
        uint8_t number, sensor_type, reading_type;
        ipmi_sdr_compact_sensor_t *csp;
        ipmi_sdr_full_sensor_t *fsp;
        ipmi_sensor_reading_t *srp;

        switch (sdrp->is_type) {
        case IPMI_SDR_TYPE_COMPACT_SENSOR:
                csp = (ipmi_sdr_compact_sensor_t *)sdrp->is_record;
                number = csp->is_cs_number;
                sensor_type = csp->is_cs_type;
                reading_type = csp->is_cs_reading_type;
                break;

        case IPMI_SDR_TYPE_FULL_SENSOR:
                fsp = (ipmi_sdr_full_sensor_t *)sdrp->is_record;
                number = fsp->is_fs_number;
                sensor_type = fsp->is_fs_type;
                reading_type = fsp->is_fs_reading_type;
                break;

        default:
                *valp = B_FALSE;
                return (0);
        }

        switch (reading_type) {
        case IPMI_RT_PRESENT:
                mask = IPMI_SR_PRESENT_ASSERT;
                break;

        case IPMI_RT_SPECIFIC:
                switch (sensor_type) {
                case IPMI_ST_PROCESSOR:
                        mask = IPMI_EV_PROCESSOR_PRESENT;
                        break;

                case IPMI_ST_POWER_SUPPLY:
                        mask = IPMI_EV_POWER_SUPPLY_PRESENT;
                        break;

                case IPMI_ST_MEMORY:
                        mask = IPMI_EV_MEMORY_PRESENT;
                        break;

                case IPMI_ST_BAY:
                        mask = IPMI_EV_BAY_PRESENT;
                        break;

                default:
                        *valp = B_FALSE;
                        return (0);
                }
                break;

        default:
                *valp = B_FALSE;
                return (0);
        }

        /*
         * If we've reached here, then we have a dedicated sensor that
         * indicates presence.
         */
        if ((srp = ipmi_get_sensor_reading(ihp, number)) == NULL) {
                if (ipmi_errno(ihp) == EIPMI_NOT_PRESENT) {
                        *valp = B_FALSE;
                        return (0);
                }

                return (-1);
        }

        *valp = (srp->isr_state & mask) != 0;
        return (0);
}

/*
 * This function follows the procedure documented in section 40 of the spec.
 * To quote the conclusion from section 40.2:
 *
 *      Thus, the steps to detecting an Entity are:
 *
 *      a) Scan the SDRs for sensors associated with the entity.
 *
 *      b) If there is an active sensor that includes a presence bit, or the
 *         entity has an active Entity Presence sensor, use the sensor to
 *         determine the presence of the entity.
 *
 *      c) Otherwise, check to see that there is at least one active sensor
 *         associated with the entity.  Do this by doing 'Get Sensor Readings'
 *         to the sensors associated with the entity until a scanning sensor is
 *         found.
 *
 *      d) If there are no active sensors directly associated with the entity,
 *         check the SDRs to see if the entity is a container entity in an
 *         entity-association.  If so, check to see if any of the contained
 *         entities are present, if so, assume the container entity exists.
 *         Note that this may need to be iterative, since it's possible to have
 *         multi-level entity associations.
 *
 *      e) If there are no active sensors for the entity, and the entity is not
 *         the container entity in an active entity-assocation, then the entity
 *         is present if (sic) there there is a FRU device for the entity, and
 *         the FRU device is present.
 *
 *      It should not be considered an error if a FRU device locator record is
 *      present for a FRU device, but the FRU device is not there.
 *
 */
int
ipmi_entity_present(ipmi_handle_t *ihp, ipmi_entity_t *ep, boolean_t *valp)
{
        /* LINTED - alignment */
        ipmi_entity_impl_t *eip = ENTITY_TO_IMPL(ep);
        ipmi_entity_impl_t *cp;
        ipmi_entity_sdr_t *esp;
        ipmi_sdr_t *sdrp;
        uint16_t mask;
        uint8_t number, sensor_type, reading_type;
        ipmi_sensor_reading_t *srp;
        ipmi_sdr_compact_sensor_t *csp;
        ipmi_sdr_full_sensor_t *fsp;
        ipmi_sdr_fru_locator_t *frup;
        char *frudata;

        /*
         * Search the sensors for a present sensor or a discrete sensor that
         * indicates presence.
         */
        for (esp = ipmi_list_next(&eip->ie_sdr_list); esp != NULL;
            esp = ipmi_list_next(esp)) {
                sdrp = esp->ies_sdr;
                switch (sdrp->is_type) {
                case IPMI_SDR_TYPE_COMPACT_SENSOR:
                        csp = (ipmi_sdr_compact_sensor_t *)sdrp->is_record;
                        number = csp->is_cs_number;
                        sensor_type = csp->is_cs_type;
                        reading_type = csp->is_cs_reading_type;
                        break;

                case IPMI_SDR_TYPE_FULL_SENSOR:
                        fsp = (ipmi_sdr_full_sensor_t *)sdrp->is_record;
                        number = fsp->is_fs_number;
                        sensor_type = fsp->is_fs_type;
                        reading_type = fsp->is_fs_reading_type;
                        break;

                default:
                        continue;
                }

                switch (reading_type) {
                case IPMI_RT_PRESENT:
                        mask = IPMI_SR_PRESENT_ASSERT;
                        break;

                case IPMI_RT_SPECIFIC:
                        switch (sensor_type) {
                        case IPMI_ST_PROCESSOR:
                                mask = IPMI_EV_PROCESSOR_PRESENT;
                                break;

                        case IPMI_ST_POWER_SUPPLY:
                                mask = IPMI_EV_POWER_SUPPLY_PRESENT;
                                break;

                        case IPMI_ST_MEMORY:
                                mask = IPMI_EV_MEMORY_PRESENT;
                                break;

                        case IPMI_ST_BAY:
                                mask = IPMI_EV_BAY_PRESENT;
                                break;

                        default:
                                continue;
                        }
                        break;

                default:
                        continue;
                }

                /*
                 * If we've reached here, then we have a dedicated sensor that
                 * indicates presence.
                 */
                if ((srp = ipmi_get_sensor_reading(ihp, number)) == NULL) {
                        if (ipmi_errno(ihp) == EIPMI_NOT_PRESENT) {
                                *valp = B_FALSE;
                                return (0);
                        }

                        return (-1);
                }

                *valp = (srp->isr_state & mask) != 0;
                return (0);
        }

        /*
         * No explicit presence sensor was found.  See if there is at least one
         * active sensor associated with the entity.
         */
        for (esp = ipmi_list_next(&eip->ie_sdr_list); esp != NULL;
            esp = ipmi_list_next(esp)) {
                sdrp = esp->ies_sdr;
                switch (sdrp->is_type) {
                case IPMI_SDR_TYPE_COMPACT_SENSOR:
                        csp = (ipmi_sdr_compact_sensor_t *)sdrp->is_record;
                        number = csp->is_cs_number;
                        break;

                case IPMI_SDR_TYPE_FULL_SENSOR:
                        fsp = (ipmi_sdr_full_sensor_t *)sdrp->is_record;
                        number = fsp->is_fs_number;
                        break;

                default:
                        continue;
                }

                if ((srp = ipmi_get_sensor_reading(ihp, number)) == NULL) {
                        if (ipmi_errno(ihp) == EIPMI_NOT_PRESENT)
                                continue;

                        return (-1);
                }

                if (srp->isr_scanning_enabled) {
                        *valp = B_TRUE;
                        return (0);
                }
        }

        /*
         * If this entity has children, then it is present if any of its
         * children are present.
         */
        for (cp = ipmi_list_next(&eip->ie_child_list); cp != NULL;
            cp = ipmi_list_next(cp)) {
                if (ipmi_entity_present(ihp, &cp->ie_entity, valp) != 0)
                        return (-1);

                if (*valp)
                        return (0);
        }

        /*
         * If the FRU device is present, then the entity is present.
         */
        for (esp = ipmi_list_next(&eip->ie_sdr_list); esp != NULL;
            esp = ipmi_list_next(esp)) {
                sdrp = esp->ies_sdr;
                if (sdrp->is_type != IPMI_SDR_TYPE_FRU_LOCATOR)
                        continue;

                frup = (ipmi_sdr_fru_locator_t *)sdrp->is_record;
                if (ipmi_fru_read(ihp, frup, &frudata) >= 0) {
                        ipmi_free(ihp, frudata);
                        *valp = B_TRUE;
                        return (0);
                }

                if (ipmi_errno(ihp) != EIPMI_NOT_PRESENT)
                        return (-1);
        }

        *valp = B_FALSE;
        return (0);
}

static int
ipmi_entity_refresh(ipmi_handle_t *ihp)
{
        if (ipmi_hash_first(ihp->ih_entities) != NULL &&
            !ipmi_sdr_changed(ihp))
                return (0);

        if (ipmi_sdr_iter(ihp, ipmi_entity_visit, NULL) != 0)
                return (-1);

        return (0);
}

int
ipmi_entity_iter(ipmi_handle_t *ihp, int (*func)(ipmi_handle_t *,
    ipmi_entity_t *, void *), void *data)
{
        ipmi_entity_impl_t *eip;
        int ret;

        if (ipmi_entity_refresh(ihp) != 0)
                return (-1);

        for (eip = ipmi_hash_first(ihp->ih_entities); eip != NULL;
            eip = ipmi_hash_next(ihp->ih_entities, eip)) {
                if (eip->ie_parent != NULL)
                        continue;

                if ((ret = func(ihp, &eip->ie_entity, data)) != 0)
                        return (ret);
        }

        return (0);
}

int
ipmi_entity_iter_sdr(ipmi_handle_t *ihp, ipmi_entity_t *ep,
    int (*func)(ipmi_handle_t *, ipmi_entity_t *, const char *, ipmi_sdr_t *,
    void *), void *data)
{
        /* LINTED - alignment */
        ipmi_entity_impl_t *eip = ENTITY_TO_IMPL(ep);
        ipmi_entity_sdr_t *isp;
        int ret;

        for (isp = ipmi_list_next(&eip->ie_sdr_list); isp != NULL;
            isp = ipmi_list_next(isp)) {
                if ((ret = func(ihp, ep, isp->ies_name,
                    isp->ies_sdr, data)) != 0)
                        return (ret);
        }

        return (0);
}

int
ipmi_entity_iter_children(ipmi_handle_t *ihp, ipmi_entity_t *ep,
    int (*func)(ipmi_handle_t *, ipmi_entity_t *, void *), void *data)
{
        /* LINTED - alignment */
        ipmi_entity_impl_t *eip = ENTITY_TO_IMPL(ep);
        ipmi_entity_impl_t *cp;
        int ret;

        for (cp = ipmi_list_next(&eip->ie_child_list); cp != NULL;
            cp = ipmi_list_next(cp)) {
                if ((ret = func(ihp, &cp->ie_entity, data)) != 0)
                        return (ret);
        }

        return (0);
}

ipmi_entity_t *
ipmi_entity_parent(ipmi_handle_t *ihp, ipmi_entity_t *ep)
{
        /* LINTED - alignment */
        ipmi_entity_impl_t *eip = ENTITY_TO_IMPL(ep);

        if (eip->ie_parent == NULL) {
                (void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT, NULL);
                return (NULL);
        }

        return (&eip->ie_parent->ie_entity);
}

ipmi_entity_t *
ipmi_entity_lookup(ipmi_handle_t *ihp, uint8_t type, uint8_t instance)
{
        ipmi_entity_t search;
        ipmi_entity_impl_t *eip;

        if (ipmi_entity_refresh(ihp) != 0)
                return (NULL);

        search.ie_type = type;
        search.ie_instance = instance;

        if ((eip = ipmi_hash_lookup(ihp->ih_entities, &search)) == NULL) {
                (void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT, NULL);
                return (NULL);
        }

        return (&eip->ie_entity);
}

ipmi_entity_t *
ipmi_entity_lookup_sdr(ipmi_handle_t *ihp, const char *name)
{
        ipmi_sdr_t *sdrp;
        uint8_t id, instance;
        boolean_t logical;

        if ((sdrp = ipmi_sdr_lookup(ihp, name)) == NULL)
                return (NULL);

        if (ipmi_entity_sdr_parse(sdrp, &id, &instance, &logical) != 0) {
                (void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT,
                    "SDR record %s has no associated entity", name);
                return (NULL);
        }

        return (ipmi_entity_lookup(ihp, id, instance));
}

static const void *
ipmi_entity_hash_convert(const void *p)
{
        const ipmi_entity_impl_t *eip = p;

        return (&eip->ie_entity);
}

static ulong_t
ipmi_entity_hash_compute(const void *p)
{
        const ipmi_entity_t *ep = p;

        return ((ep->ie_type << 8) | ep->ie_instance);
}

static int
ipmi_entity_hash_compare(const void *a, const void *b)
{
        const ipmi_entity_t *ea = a;
        const ipmi_entity_t *eb = b;

        if (ea->ie_type == eb->ie_type &&
            ea->ie_instance == eb->ie_instance)
                return (0);
        else
                return (-1);
}

int
ipmi_entity_init(ipmi_handle_t *ihp)
{
        if ((ihp->ih_entities = ipmi_hash_create(ihp,
            offsetof(ipmi_entity_impl_t, ie_link),
            ipmi_entity_hash_convert,
            ipmi_entity_hash_compute,
            ipmi_entity_hash_compare)) == NULL)
                return (-1);

        return (0);
}

void
ipmi_entity_clear(ipmi_handle_t *ihp)
{
        ipmi_entity_impl_t *eip;
        ipmi_entity_sdr_t *esp;

        while ((eip = ipmi_hash_first(ihp->ih_entities)) != NULL) {
                while ((esp = ipmi_list_next(&eip->ie_sdr_list)) != NULL) {
                        ipmi_list_delete(&eip->ie_sdr_list, esp);
                        ipmi_free(ihp, esp);
                }
                ipmi_hash_remove(ihp->ih_entities, eip);
                ipmi_free(ihp, eip);
        }
}

void
ipmi_entity_fini(ipmi_handle_t *ihp)
{
        if (ihp->ih_entities != NULL) {
                ipmi_entity_clear(ihp);
                ipmi_hash_destroy(ihp->ih_entities);
        }
}