root/usr/src/lib/scsi/plugins/ses/SUN-Storage-J4500/common/loki.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.
 */

#include <string.h>
#include <strings.h>

#include <scsi/libses.h>
#include <scsi/libses_plugin.h>

#include <scsi/plugins/ses/framework/ses2_impl.h>

static int
sun_loki_fix_bay(ses_plugin_t *sp, ses_node_t *np)
{
        ses2_aes_descr_eip_impl_t *dep;
        ses2_aes_descr_sas0_eip_impl_t *s0ep;
        size_t len;
        int nverr;
        nvlist_t *props = ses_node_props(np);

        /*
         * The spec conveniently defines the bay number as part of the
         * additional element status descriptor.  However, the AES descriptor
         * is technically only valid if the device is inserted.  This is a
         * problem for loki because the bay numbers don't match the element
         * class index, so when a device is removed we have no way of knowing
         * *which* bay is empty.  Thankfully, loki defines this value even if
         * the invalid bit is set, so we override this value, even for empty
         * bays.
         */
        if ((dep = ses_plugin_page_lookup(sp, ses_node_snapshot(np),
            SES2_DIAGPAGE_ADDL_ELEM_STATUS, np, &len)) == NULL)
                return (0);

        if (dep->sadei_protocol_identifier != SPC4_PROTO_SAS ||
            !dep->sadei_eip || !dep->sadei_invalid)
                return (0);

        s0ep = (ses2_aes_descr_sas0_eip_impl_t *)dep->sadei_protocol_specific;

        SES_NV_ADD(uint64, nverr, props, SES_PROP_BAY_NUMBER,
            s0ep->sadsi_bay_number);

        return (0);
}

static int
sun_loki_parse_node(ses_plugin_t *sp, ses_node_t *np)
{
        ses_node_t *encp;
        nvlist_t *props = ses_node_props(np);
        nvlist_t *encprops;
        uint8_t *stringin;
        uint_t len;
        nvlist_t *lid;
        int nverr;
        char serial[17];
        uint8_t fieldlen;
        char *field;
        uint64_t wwn;
        uint64_t type, index;
        int i;

        if (ses_node_type(np) != SES_NODE_ENCLOSURE &&
            ses_node_type(np) != SES_NODE_ELEMENT)
                return (0);

        if (ses_node_type(np) == SES_NODE_ELEMENT) {
                VERIFY(nvlist_lookup_uint64(props, SES_PROP_ELEMENT_TYPE,
                    &type) == 0);

                if (type == SES_ET_ARRAY_DEVICE)
                        return (sun_loki_fix_bay(sp, np));

                if (type != SES_ET_COOLING &&
                    type != SES_ET_POWER_SUPPLY)
                        return (0);

                VERIFY(nvlist_lookup_uint64(props, SES_PROP_ELEMENT_CLASS_INDEX,
                    &index) == 0);
        }

        /*
         * Find the containing enclosure node and extract the STRING IN
         * information.
         */
        for (encp = np; ses_node_type(encp) != SES_NODE_ENCLOSURE;
            encp = ses_node_parent(encp))
                ;

        encprops = ses_node_props(encp);
        if (nvlist_lookup_byte_array(encprops, SES_EN_PROP_STRING,
            &stringin, &len) != 0 || len < 4)
                return (0);

        /*
         * If this is an enclosure, then calculate the chassis WWN by masking
         * off the bottom 8 bits of the WWN.
         */
        if (ses_node_type(np) == SES_NODE_ENCLOSURE) {
                VERIFY(nvlist_lookup_nvlist(props, SES_EN_PROP_LID, &lid) == 0);
                VERIFY(nvlist_lookup_uint64(lid, SPC3_NAA_INT, &wwn) == 0);
                (void) snprintf(serial, sizeof (serial), "%llx",
                    wwn & ~0xFFULL);
                SES_NV_ADD(string, nverr, props, LIBSES_EN_PROP_CSN, serial);
        }

        /*
         * The STRING IN data is organized into a series of variable-length
         * fields, where each field can be either a key ("Fan PartNUM") or a
         * value.  If the field length is less than our shortest expected
         * identifier, then something has gone awry and we assume that the data
         * is corrupt.
         */
        fieldlen = stringin[3];
        if (fieldlen < 11)
                return (0);

        for (field = (char *)stringin + 4;
            field + fieldlen <= (char *)stringin + len; field += fieldlen) {
                if (strncmp(field, "Storage J4500", 13) == 0) {
                        /*
                         * This is the part number for the enclosure itself.
                         */
                        if (ses_node_type(np) != SES_NODE_ENCLOSURE)
                                continue;

                        field += fieldlen;
                        if (field + fieldlen > (char *)stringin + len)
                                break;

                        if (ses_node_type(np) == SES_NODE_ENCLOSURE) {
                                SES_NV_ADD(fixed_string_trunc, nverr, props,
                                    LIBSES_PROP_PART, field, fieldlen);
                                return (0);
                        }

                } else if (strncmp(field, "Fan PartNUM", 11) == 0) {
                        /*
                         * Part numbers for the fans, of which there are 5.
                         */
                        if (ses_node_type(np) != SES_NODE_ELEMENT ||
                            type != SES_ET_COOLING) {
                                field += fieldlen * 5;
                                continue;
                        }

                        field += fieldlen;

                        for (i = 0; i < 5 &&
                            field + fieldlen <= (char *)stringin + len;
                            i++, field += fieldlen) {
                                if (index == i &&
                                    strncmp(field, "Unknown", 7) != 0 &&
                                    strncmp(field, "Not Installed", 13) != 0) {
                                        SES_NV_ADD(fixed_string_trunc, nverr,
                                            props, LIBSES_PROP_PART,
                                            field, fieldlen);
                                        return (0);
                                }
                        }

                } else if (strncmp(field, "PS PartNUM", 10) == 0) {
                        /*
                         * Part numbers for the power supplies, of which there
                         * are 2.
                         */
                        if (ses_node_type(np) != SES_NODE_ELEMENT ||
                            type != SES_ET_POWER_SUPPLY) {
                                field += fieldlen * 2;
                                continue;
                        }

                        field += fieldlen;

                        for (i = 0; i < 2 &&
                            field + fieldlen <= (char *)stringin + len;
                            i++, field += fieldlen) {
                                if (index == i &&
                                    strncmp(field, "Unknown", 7) != 0 &&
                                    strncmp(field, "Not Installed", 13) != 0) {
                                        SES_NV_ADD(fixed_string_trunc, nverr,
                                            props, LIBSES_PROP_PART,
                                            field, fieldlen);
                                        return (0);
                                }
                        }
                }
        }

        return (0);
}

int
_ses_init(ses_plugin_t *sp)
{
        ses_plugin_config_t config = {
                .spc_node_parse = sun_loki_parse_node
        };

        return (ses_plugin_register(sp, LIBSES_PLUGIN_VERSION,
            &config) != 0);
}