root/usr/src/uts/common/io/scsi/targets/ses_safte.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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
 */
/*
 * Enclosure Services Devices, SAF-TE Enclosure Routines
 *
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/modctl.h>
#include <sys/file.h>
#include <sys/scsi/scsi.h>
#include <sys/stat.h>
#include <sys/scsi/targets/sesio.h>
#include <sys/scsi/targets/ses.h>


static int set_objstat_sel(ses_softc_t *, ses_objarg *, int);
static int wrbuf16(ses_softc_t *, uchar_t, uchar_t, uchar_t, uchar_t, int);
static void wrslot_stat(ses_softc_t *, int);
static int perf_slotop(ses_softc_t *, uchar_t, uchar_t, int);

#define ALL_ENC_STAT \
        (ENCSTAT_CRITICAL|ENCSTAT_UNRECOV|ENCSTAT_NONCRITICAL|ENCSTAT_INFO)

#define SCRATCH 64
#define NPSEUDO_THERM   1
#define NPSEUDO_ALARM   1
struct scfg {
        /*
         * Cached Configuration
         */
        uchar_t Nfans;          /* Number of Fans */
        uchar_t Npwr;           /* Number of Power Supplies */
        uchar_t Nslots;         /* Number of Device Slots */
        uchar_t DoorLock;       /* Door Lock Installed */
        uchar_t Ntherm;         /* Number of Temperature Sensors */
        uchar_t Nspkrs;         /* Number of Speakers */
        uchar_t  Nalarm;                /* Number of Alarms (at least one) */
        /*
         * Cached Flag Bytes for Global Status
         */
        uchar_t flag1;
        uchar_t flag2;
        /*
         * What object index ID is where various slots start.
         */
        uchar_t pwroff;
        uchar_t slotoff;
#define ALARM_OFFSET(cc)        (cc)->slotoff - 1
};
#define FLG1_ALARM      0x1
#define FLG1_GLOBFAIL   0x2
#define FLG1_GLOBWARN   0x4
#define FLG1_ENCPWROFF  0x8
#define FLG1_ENCFANFAIL 0x10
#define FLG1_ENCPWRFAIL 0x20
#define FLG1_ENCDRVFAIL 0x40
#define FLG1_ENCDRVWARN 0x80

#define FLG2_LOCKDOOR   0x4
#define SAFTE_PRIVATE   sizeof (struct scfg)

#if     !defined(lint)
_NOTE(MUTEX_PROTECTS_DATA(scsi_device::sd_mutex, scfg))
_NOTE(DATA_READABLE_WITHOUT_LOCK(scfg::Nfans))
_NOTE(DATA_READABLE_WITHOUT_LOCK(scfg::Npwr))
_NOTE(DATA_READABLE_WITHOUT_LOCK(scfg::Nslots))
_NOTE(DATA_READABLE_WITHOUT_LOCK(scfg::DoorLock))
_NOTE(DATA_READABLE_WITHOUT_LOCK(scfg::Ntherm))
_NOTE(DATA_READABLE_WITHOUT_LOCK(scfg::Nspkrs))
_NOTE(DATA_READABLE_WITHOUT_LOCK(scfg::Nalarm))
_NOTE(DATA_READABLE_WITHOUT_LOCK(scfg::flag1))
_NOTE(DATA_READABLE_WITHOUT_LOCK(scfg::flag2))
_NOTE(DATA_READABLE_WITHOUT_LOCK(scfg::pwroff))
_NOTE(DATA_READABLE_WITHOUT_LOCK(scfg::slotoff))
#endif

static int
safte_getconfig(ses_softc_t *ssc)
{
        struct scfg *cfg;
        int err;
        Uscmd local, *lp = &local;
        char rqbuf[SENSE_LENGTH], *sdata;
        static char cdb[CDB_GROUP1] =
            { SCMD_READ_BUFFER, 1, SAFTE_RD_RDCFG, 0, 0, 0, 0, 0, SCRATCH, 0 };

        cfg = ssc->ses_private;
        if (cfg == NULL)
                return (ENXIO);

        sdata = kmem_alloc(SCRATCH, KM_SLEEP);
        if (sdata == NULL)
                return (ENOMEM);

        lp->uscsi_flags = USCSI_READ|USCSI_RQENABLE;
        lp->uscsi_timeout = ses_io_time;
        lp->uscsi_cdb = cdb;
        lp->uscsi_bufaddr = sdata;
        lp->uscsi_buflen = SCRATCH;
        lp->uscsi_cdblen = sizeof (cdb);
        lp->uscsi_rqbuf = rqbuf;
        lp->uscsi_rqlen = sizeof (rqbuf);

        err = ses_runcmd(ssc, lp);
        if (err) {
                kmem_free(sdata, SCRATCH);
                return (err);
        }

        if ((lp->uscsi_buflen - lp->uscsi_resid) < 6) {
                SES_LOG(ssc, CE_NOTE, "Too little data (%ld) for configuration",
                    lp->uscsi_buflen - lp->uscsi_resid);
                kmem_free(sdata, SCRATCH);
                return (EIO);
        }
        SES_LOG(ssc, SES_CE_DEBUG1, "Nfans %d Npwr %d Nslots %d Lck %d Ntherm "
            "%d Nspkrs %d", sdata[0], sdata[1], sdata[2], sdata[3], sdata[4],
            sdata[5]);

        mutex_enter(&ssc->ses_devp->sd_mutex);
        cfg->Nfans = sdata[0];
        cfg->Npwr = sdata[1];
        cfg->Nslots = sdata[2];
        cfg->DoorLock = sdata[3];
        cfg->Ntherm = sdata[4];
        cfg->Nspkrs = sdata[5];
        cfg->Nalarm = NPSEUDO_ALARM;
        mutex_exit(&ssc->ses_devp->sd_mutex);
        kmem_free(sdata, SCRATCH);
        return (0);
}

int
safte_softc_init(ses_softc_t *ssc, int doinit)
{
        int r, i;
        struct scfg *cc;

        if (doinit == 0) {
                mutex_enter(&ssc->ses_devp->sd_mutex);
                if (ssc->ses_nobjects) {
                        if (ssc->ses_objmap) {
                                kmem_free(ssc->ses_objmap,
                                    ssc->ses_nobjects * sizeof (encobj));
                                ssc->ses_objmap = NULL;
                        }
                        ssc->ses_nobjects = 0;
                }
                if (ssc->ses_private) {
                        kmem_free(ssc->ses_private, SAFTE_PRIVATE);
                        ssc->ses_private = NULL;
                }
                mutex_exit(&ssc->ses_devp->sd_mutex);
                return (0);
        }

        mutex_enter(&ssc->ses_devp->sd_mutex);
        if (ssc->ses_private == NULL) {
                ssc->ses_private = kmem_zalloc(SAFTE_PRIVATE, KM_SLEEP);
                if (ssc->ses_private == NULL) {
                        mutex_exit(&ssc->ses_devp->sd_mutex);
                        return (ENOMEM);
                }
        }

        ssc->ses_nobjects = 0;
        ssc->ses_encstat = 0;
        mutex_exit(&ssc->ses_devp->sd_mutex);

        if ((r = safte_getconfig(ssc)) != 0) {
                return (r);
        }

        /*
         * The number of objects here, as well as that reported by the
         * READ_BUFFER/GET_CONFIG call, are the over-temperature flags (15)
         * that get reported during READ_BUFFER/READ_ENC_STATUS.
         */
        mutex_enter(&ssc->ses_devp->sd_mutex);
        cc = ssc->ses_private;
        ssc->ses_nobjects = cc->Nfans + cc->Npwr + cc->Nslots + cc->DoorLock +
            cc->Ntherm + cc->Nspkrs + NPSEUDO_THERM + NPSEUDO_ALARM;
        ssc->ses_objmap = (encobj *)
            kmem_zalloc(ssc->ses_nobjects * sizeof (encobj), KM_SLEEP);
        mutex_exit(&ssc->ses_devp->sd_mutex);
        if (ssc->ses_objmap == NULL)
                return (ENOMEM);
        r = 0;
        /*
         * Note that this is all arranged for the convenience
         * in later fetches of status.
         */
        mutex_enter(&ssc->ses_devp->sd_mutex);
        for (i = 0; i < cc->Nfans; i++)
                ssc->ses_objmap[r++].enctype = SESTYP_FAN;
        cc->pwroff = (uchar_t)r;
        for (i = 0; i < cc->Npwr; i++)
                ssc->ses_objmap[r++].enctype = SESTYP_POWER;
        for (i = 0; i < cc->DoorLock; i++)
                ssc->ses_objmap[r++].enctype = SESTYP_DOORLOCK;
        for (i = 0; i < cc->Nspkrs; i++)
                ssc->ses_objmap[r++].enctype = SESTYP_ALARM;
        for (i = 0; i < cc->Ntherm; i++)
                ssc->ses_objmap[r++].enctype = SESTYP_THERM;
        for (i = 0; i < NPSEUDO_THERM; i++)
                ssc->ses_objmap[r++].enctype = SESTYP_THERM;
        ssc->ses_objmap[r++].enctype = SESTYP_ALARM;
        cc->slotoff = (uchar_t)r;
        for (i = 0; i < cc->Nslots; i++)
                ssc->ses_objmap[r++].enctype = SESTYP_DEVICE;
        mutex_exit(&ssc->ses_devp->sd_mutex);
        return (0);
}

int
safte_init_enc(ses_softc_t *ssc)
{
        int err;
        Uscmd local, *lp = &local;
        char rqbuf[SENSE_LENGTH], *sdata;
        static char cdb0[CDB_GROUP1] = { SCMD_SDIAG };
        static char cdb[CDB_GROUP1] =
            { SCMD_WRITE_BUFFER, 1, 0, 0, 0, 0, 0, 0, SCRATCH, 0 };

        sdata = kmem_alloc(SCRATCH, KM_SLEEP);
        lp->uscsi_flags = USCSI_RQENABLE;
        lp->uscsi_timeout = ses_io_time;
        lp->uscsi_cdb = cdb0;
        lp->uscsi_bufaddr = NULL;
        lp->uscsi_buflen = 0;
        lp->uscsi_cdblen = sizeof (cdb0);
        lp->uscsi_rqbuf = rqbuf;
        lp->uscsi_rqlen = sizeof (rqbuf);
        err = ses_runcmd(ssc, lp);
        if (err) {
                kmem_free(sdata, SCRATCH);
                return (err);
        }

        lp->uscsi_flags = USCSI_WRITE|USCSI_RQENABLE;
        lp->uscsi_timeout = ses_io_time;
        lp->uscsi_cdb = cdb;
        lp->uscsi_bufaddr = sdata;
        lp->uscsi_buflen = SCRATCH;
        lp->uscsi_cdblen = sizeof (cdb);
        lp->uscsi_rqbuf = rqbuf;
        lp->uscsi_rqlen = sizeof (rqbuf);
        bzero(&sdata[1], 15);
        sdata[0] = SAFTE_WT_GLOBAL;
        err = ses_runcmd(ssc, lp);
        kmem_free(sdata, SCRATCH);
        return (err);
}


static char *toolittle = "Too Little Data Returned (%d) at line %d";
#define BAIL(r, x, k, l, m, n) \
        if (r >= x) { \
                SES_LOG(ssc, CE_NOTE, toolittle, x, __LINE__); \
                kmem_free(k, l); \
                kmem_free(m, n); \
                return (EIO); \
        }

static int
safte_rdstat(ses_softc_t *ssc, int slpflg)
{
        int err, oid, r, i, hiwater, nitems;
        ushort_t tempflags;
        size_t buflen;
        uchar_t status, oencstat;
        Uscmd local, *lp = &local;
        struct scfg *cc = ssc->ses_private;
        char rqbuf[SENSE_LENGTH], *sdata;
        char cdb[CDB_GROUP1];
        int *driveids, id_size = cc->Nslots * sizeof (int);

        driveids = kmem_alloc(id_size, slpflg);
        if (driveids == NULL) {
                return (ENOMEM);
        }

        /*
         * The number of bytes of data we need to get is
         * Nfans + Npwr + Nslots + Nspkrs + Ntherm + nochoice
         * (nochoice = 1 doorlock + 1 spkr + 2 pseudo therms + 10 extra)
         * the extra are simply for good luck.
         */
        buflen = cc->Nfans + cc->Npwr + cc->Nslots + cc->Nspkrs;
        buflen += cc->Ntherm + 14;

        /*
         * Towards the end of this function this buffer is reused.
         * Thus we need to make sure that we have allocated enough
         * memory retrieving buffer 1 & 4.
         * buffer 1 -> element status & drive id
         * buffer 4 -> drive status & drive command history.
         * buffer 4 uses 4 bytes per drive bay.
         */

        if (buflen < cc->Nslots * 4) {
                buflen = cc->Nslots * 4;
        }

        if (ssc->ses_nobjects > buflen)
                buflen = ssc->ses_nobjects;

        if (buflen > 0xffff) {
                cmn_err(CE_WARN, "Illogical SCSI data");
                kmem_free(driveids, id_size);
                return (EIO);
        }

        sdata = kmem_alloc(buflen, slpflg);
        if (sdata == NULL) {
                kmem_free(driveids, id_size);
                return (ENOMEM);
        }

        cdb[0] = SCMD_READ_BUFFER;
        cdb[1] = 1;
        cdb[2] = SAFTE_RD_RDESTS;
        cdb[3] = 0;
        cdb[4] = 0;
        cdb[5] = 0;
        cdb[6] = 0;
        cdb[7] = (buflen >> 8) & 0xff;
        cdb[8] = buflen & 0xff;
        cdb[9] = 0;
        lp->uscsi_flags = USCSI_READ|USCSI_RQENABLE;
        lp->uscsi_timeout = ses_io_time;
        lp->uscsi_cdb = cdb;
        lp->uscsi_bufaddr = sdata;
        lp->uscsi_buflen = buflen;
        lp->uscsi_cdblen = sizeof (cdb);
        lp->uscsi_rqbuf = rqbuf;
        lp->uscsi_rqlen = sizeof (rqbuf);

        err = ses_runcmd(ssc, lp);
        if (err) {
                kmem_free(sdata, buflen);
                kmem_free(driveids, id_size);
                return (err);
        }

        hiwater = lp->uscsi_buflen - lp->uscsi_resid;

        /*
         * invalidate all status bits.
         */
        mutex_enter(&ssc->ses_devp->sd_mutex);
        for (i = 0; i < ssc->ses_nobjects; i++)
                ssc->ses_objmap[i].svalid = 0;
        oencstat = ssc->ses_encstat & ALL_ENC_STAT;
        ssc->ses_encstat = 0;
        mutex_exit(&ssc->ses_devp->sd_mutex);

        /*
         * Now parse returned buffer.
         * If we didn't get enough data back,
         * that's considered a fatal error.
         */
        oid = r = 0;

        for (nitems = i = 0; i < cc->Nfans; i++) {
                BAIL(r, hiwater, sdata, buflen, driveids, id_size);
                /*
                 * 0 = Fan Operational
                 * 1 = Fan is malfunctioning
                 * 2 = Fan is not present
                 * 0x80 = Unknown or Not Reportable Status
                 */
                mutex_enter(&ssc->ses_devp->sd_mutex);
                ssc->ses_objmap[oid].encstat[1] = 0;    /* resvd */
                ssc->ses_objmap[oid].encstat[2] = 0;    /* resvd */
                switch ((uchar_t)sdata[r]) {
                case 0:
                        nitems++;
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                        /*
                         * We could get fancier and cache
                         * fan speeds that we have set, but
                         * that isn't done now.
                         */
                        ssc->ses_objmap[oid].encstat[3] = 7;
                        break;

                case 1:
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_CRIT;
                        /*
                         * FAIL and FAN STOPPED synthesized
                         */
                        ssc->ses_objmap[oid].encstat[3] = 0x40;
                        /*
                         * Enclosure marked with CRITICAL error
                         * if only one fan or no thermometers,
                         * else NONCRIT error set.
                         */
                        if (cc->Nfans == 1 || cc->Ntherm == 0)
                                ssc->ses_encstat |= ENCSTAT_CRITICAL;
                        else
                                ssc->ses_encstat |= ENCSTAT_NONCRITICAL;
                        break;
                case 2:
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_NOTINSTALLED;
                        ssc->ses_objmap[oid].encstat[3] = 0;
                        if (cc->Nfans == 1)
                                ssc->ses_encstat |= ENCSTAT_CRITICAL;
                        else
                                ssc->ses_encstat |= ENCSTAT_NONCRITICAL;
                        break;
                case 0x80:
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_UNKNOWN;
                        ssc->ses_objmap[oid].encstat[3] = 0;
                        ssc->ses_encstat |= ENCSTAT_INFO;
                        break;
                default:
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_UNSUPPORTED;
                        SES_LOG(ssc, CE_NOTE, "unknown fan%d status 0x%x",
                            i, sdata[r] & 0xff);
                        break;
                }
                ssc->ses_objmap[oid++].svalid = 1;
                mutex_exit(&ssc->ses_devp->sd_mutex);
                r++;
        }
        mutex_enter(&ssc->ses_devp->sd_mutex);
        /*
         * No matter how you cut it, no cooling elements when there
         * should be some there is critical.
         */
        if (cc->Nfans && nitems == 0) {
                ssc->ses_encstat |= ENCSTAT_CRITICAL;
        }
        mutex_exit(&ssc->ses_devp->sd_mutex);


        for (i = 0; i < cc->Npwr; i++) {
                BAIL(r, hiwater, sdata, buflen, driveids, id_size);
                mutex_enter(&ssc->ses_devp->sd_mutex);
                ssc->ses_objmap[oid].encstat[0] = SESSTAT_UNSUPPORTED;
                ssc->ses_objmap[oid].encstat[1] = 0;    /* resvd */
                ssc->ses_objmap[oid].encstat[2] = 0;    /* resvd */
                ssc->ses_objmap[oid].encstat[3] = 0x20; /* requested on */
                switch ((uchar_t)sdata[r]) {
                case 0x00:      /* pws operational and on */
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                        break;
                case 0x01:      /* pws operational and off */
                        ssc->ses_objmap[oid].encstat[3] = 0x10;
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_NOTAVAIL;
                        ssc->ses_encstat |= ENCSTAT_INFO;
                        break;
                case 0x10:      /* pws is malfunctioning and commanded on */
                        ssc->ses_objmap[oid].encstat[3] = 0x61;
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_CRIT;
                        if (cc->Npwr < 2)
                                ssc->ses_encstat |= ENCSTAT_CRITICAL;
                        else
                                ssc->ses_encstat |= ENCSTAT_NONCRITICAL;
                        break;

                case 0x11:      /* pws is malfunctioning and commanded off */
                        ssc->ses_objmap[oid].encstat[3] = 0x51;
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_CRIT;
                        if (cc->Npwr < 2)
                                ssc->ses_encstat |= ENCSTAT_CRITICAL;
                        else
                                ssc->ses_encstat |= ENCSTAT_NONCRITICAL;
                        break;
                case 0x20:      /* pws is not present */
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_NOTINSTALLED;
                        ssc->ses_objmap[oid].encstat[3] = 0;
                        if (cc->Npwr < 2)
                                ssc->ses_encstat |= ENCSTAT_CRITICAL;
                        else
                                ssc->ses_encstat |= ENCSTAT_INFO;
                        break;
                case 0x21:      /* pws is present */
                        break;
                case 0x80:      /* Unknown or Not Reportable Status */
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_UNKNOWN;
                        ssc->ses_objmap[oid].encstat[3] = 0;
                        ssc->ses_encstat |= ENCSTAT_INFO;
                        break;
                default:
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_UNSUPPORTED;
                        SES_LOG(ssc, CE_NOTE, "unknown pwr%d status 0x%x",
                            i, sdata[r] & 0xff);
                        break;
                }
                ssc->ses_objmap[oid++].svalid = 1;
                mutex_exit(&ssc->ses_devp->sd_mutex);
                r++;
        }

        /*
         * Now I am going to save the target id's for the end of
         * the function.  (when I build the drive objects)
         * that is when I will be getting the drive status from buffer 4
         */

        for (i = 0; i < cc->Nslots; i++) {
                driveids[i] = sdata[r++];
        }



        /*
         * We always have doorlock status, no matter what,
         * but we only save the status if we have one.
         */
        BAIL(r, hiwater, sdata, buflen, driveids, id_size);
        if (cc->DoorLock) {
                /*
                 * 0 = Door Locked
                 * 1 = Door Unlocked, or no Lock Installed
                 * 0x80 = Unknown or Not Reportable Status
                 */
                mutex_enter(&ssc->ses_devp->sd_mutex);
                ssc->ses_objmap[oid].encstat[1] = 0;
                ssc->ses_objmap[oid].encstat[2] = 0;
                switch ((uchar_t)sdata[r]) {
                case 0:
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                        ssc->ses_objmap[oid].encstat[3] = 0;
                        break;
                case 1:
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                        ssc->ses_objmap[oid].encstat[3] = 1;
                        break;
                case 0x80:
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_UNKNOWN;
                        ssc->ses_objmap[oid].encstat[3] = 0;
                        ssc->ses_encstat |= ENCSTAT_INFO;
                        break;
                default:
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_UNSUPPORTED;
                        SES_LOG(ssc, CE_NOTE, "unknown lock status 0x%x",
                            sdata[r] & 0xff);
                        break;
                }
                ssc->ses_objmap[oid++].svalid = 1;
                mutex_exit(&ssc->ses_devp->sd_mutex);
        }
        r++;

        /*
         * We always have speaker status, no matter what,
         * but we only save the status if we have one.
         */
        BAIL(r, hiwater, sdata, buflen, driveids, id_size);
        if (cc->Nspkrs) {
                mutex_enter(&ssc->ses_devp->sd_mutex);
                ssc->ses_objmap[oid].encstat[1] = 0;
                ssc->ses_objmap[oid].encstat[2] = 0;
                if (sdata[r] == 1) {
                        /*
                         * We need to cache tone urgency indicators.
                         * Someday.
                         */
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_NONCRIT;
                        ssc->ses_objmap[oid].encstat[3] = 0x8;
                        ssc->ses_encstat |= ENCSTAT_NONCRITICAL;
                } else if (sdata[r] == 0) {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                        ssc->ses_objmap[oid].encstat[3] = 0;
                } else {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_UNSUPPORTED;
                        ssc->ses_objmap[oid].encstat[3] = 0;
                        SES_LOG(ssc, CE_NOTE, "unknown spkr status 0x%x",
                            sdata[r] & 0xff);
                }
                ssc->ses_objmap[oid++].svalid = 1;
                mutex_exit(&ssc->ses_devp->sd_mutex);
        }
        r++;

        for (i = 0; i < cc->Ntherm; i++) {
                BAIL(r, hiwater, sdata, buflen, driveids, id_size);
                /*
                 * Status is a range from -10 to 245 deg Celsius,
                 * which we need to normalize to -20 to -235 according
                 * to the latest SCSI spec.
                 */
                mutex_enter(&ssc->ses_devp->sd_mutex);
                ssc->ses_objmap[oid].encstat[1] = 0;
                ssc->ses_objmap[oid].encstat[2] =
                    ((unsigned int) sdata[r]) - 10;
                if (sdata[r] < 20) {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_CRIT;
                        /*
                         * Set 'under temperature' failure.
                         */
                        ssc->ses_objmap[oid].encstat[3] = 2;
                        ssc->ses_encstat |= ENCSTAT_CRITICAL;
                } else if (sdata[r] > 30) {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_CRIT;
                        /*
                         * Set 'over temperature' failure.
                         */
                        ssc->ses_objmap[oid].encstat[3] = 8;
                        ssc->ses_encstat |= ENCSTAT_CRITICAL;
                } else {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                }
                ssc->ses_objmap[oid++].svalid = 1;
                mutex_exit(&ssc->ses_devp->sd_mutex);
                r++;
        }

        /*
         * Now, for "pseudo" thermometers, we have two bytes
         * of information in enclosure status- 16 bits. Actually,
         * the MSB is a single TEMP ALERT flag indicating whether
         * any other bits are set, but, thanks to fuzzy thinking,
         * in the SAF-TE spec, this can also be set even if no
         * other bits are set, thus making this really another
         * binary temperature sensor.
         */

        BAIL(r, hiwater, sdata, buflen, driveids, id_size);
        tempflags = sdata[r++];
        BAIL(r, hiwater, sdata, buflen, driveids, id_size);
        tempflags |= (tempflags << 8) | sdata[r++];
        mutex_enter(&ssc->ses_devp->sd_mutex);

#if     NPSEUDO_THERM == 1
        ssc->ses_objmap[oid].encstat[1] = 0;
        if (tempflags) {
                /* Set 'over temperature' failure. */
                ssc->ses_objmap[oid].encstat[0] = SESSTAT_CRIT;
                ssc->ses_objmap[oid].encstat[3] = 8;
                ssc->ses_encstat |= ENCSTAT_CRITICAL;
        } else {
                /* Set 'nominal' temperature. */
                ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
        }
        ssc->ses_objmap[oid++].svalid = 1;

#else   /* NPSEUDO_THERM == 1 */
        for (i = 0; i < NPSEUDO_THERM; i++) {
                ssc->ses_objmap[oid].encstat[1] = 0;
                if (tempflags & (1 << (NPSEUDO_THERM - i - 1))) {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_CRIT;
                        /* ssc->ses_objmap[oid].encstat[2] = 0; */

                        /*
                         * Set 'over temperature' failure.
                         */
                        ssc->ses_objmap[oid].encstat[3] = 8;
                        ssc->ses_encstat |= ENCSTAT_CRITICAL;
                } else {
                        /*
                         * Set 'nominal' temperature.
                         */
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                        /* ssc->ses_objmap[oid].encstat[2] = 0; */
                }
                ssc->ses_objmap[oid++].svalid = 1;
        }
#endif  /* NPSEUDO_THERM == 1 */


        /*
         * Get alarm status.
         */
        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
        ssc->ses_objmap[oid].encstat[3] = ssc->ses_objmap[oid].priv;
        ssc->ses_objmap[oid++].svalid = 1;
        mutex_exit(&ssc->ses_devp->sd_mutex);

        /*
         * Now get drive slot status
         */
        cdb[2] = SAFTE_RD_RDDSTS;
        err = ses_runcmd(ssc, lp);
        if (err) {
                kmem_free(sdata, buflen);
                kmem_free(driveids, id_size);
                return (err);
        }
        hiwater = lp->uscsi_buflen - lp->uscsi_resid;
        for (r = i = 0; i < cc->Nslots; i++, r += 4) {
                BAIL(r+3, hiwater, sdata, buflen, driveids, id_size);
                mutex_enter(&ssc->ses_devp->sd_mutex);
                ssc->ses_objmap[oid].encstat[0] = SESSTAT_UNSUPPORTED;
                ssc->ses_objmap[oid].encstat[1] = (uchar_t)driveids[i];
                ssc->ses_objmap[oid].encstat[2] = 0;
                ssc->ses_objmap[oid].encstat[3] = 0;
                status = sdata[r+3];
                if ((status & 0x1) == 0) {      /* no device */
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_NOTINSTALLED;
                } else {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                }
                if (status & 0x2) {
                        ssc->ses_objmap[oid].encstat[2] = 0x8;
                }
                if ((status & 0x4) == 0) {
                        ssc->ses_objmap[oid].encstat[3] = 0x10;
                }
                ssc->ses_objmap[oid++].svalid = 1;
                mutex_exit(&ssc->ses_devp->sd_mutex);
        }
        mutex_enter(&ssc->ses_devp->sd_mutex);
        /* see comment below about sticky enclosure status */
        ssc->ses_encstat |= ENCI_SVALID | oencstat;
        mutex_exit(&ssc->ses_devp->sd_mutex);
        kmem_free(sdata, buflen);
        kmem_free(driveids, id_size);
        return (0);
}

int
safte_get_encstat(ses_softc_t *ssc, int slpflg)
{
        return (safte_rdstat(ssc, slpflg));
}

int
safte_set_encstat(ses_softc_t *ssc, uchar_t encstat, int slpflg)
{
        struct scfg *cc = ssc->ses_private;
        if (cc == NULL)
                return (0);
        mutex_enter(&ssc->ses_devp->sd_mutex);
        /*
         * Since SAF-TE devices aren't necessarily sticky in terms
         * of state, make our soft copy of enclosure status 'sticky'-
         * that is, things set in enclosure status stay set (as implied
         * by conditions set in reading object status) until cleared.
         */
        ssc->ses_encstat &= ~ALL_ENC_STAT;
        ssc->ses_encstat |= (encstat & ALL_ENC_STAT);
        ssc->ses_encstat |= ENCI_SVALID;
        cc->flag1 &= ~(FLG1_ALARM|FLG1_GLOBFAIL|FLG1_GLOBWARN);
        if ((encstat & (ENCSTAT_CRITICAL|ENCSTAT_UNRECOV)) != 0) {
                cc->flag1 |= FLG1_ALARM|FLG1_GLOBFAIL;
        } else if ((encstat & ENCSTAT_NONCRITICAL) != 0) {
                cc->flag1 |= FLG1_GLOBWARN;
        }
        mutex_exit(&ssc->ses_devp->sd_mutex);
        return (wrbuf16(ssc, SAFTE_WT_GLOBAL, cc->flag1, cc->flag2, 0, slpflg));
}

int
safte_get_objstat(ses_softc_t *ssc, ses_objarg *obp, int slpflg)
{
        int i = (int)obp->obj_id;

        if ((ssc->ses_encstat & ENCI_SVALID) == 0 ||
            (ssc->ses_objmap[i].svalid) == 0) {
                int r = safte_rdstat(ssc, slpflg);
                if (r)
                        return (r);
        }
        obp->cstat[0] = ssc->ses_objmap[i].encstat[0];
        obp->cstat[1] = ssc->ses_objmap[i].encstat[1];
        obp->cstat[2] = ssc->ses_objmap[i].encstat[2];
        obp->cstat[3] = ssc->ses_objmap[i].encstat[3];
        return (0);
}


int
safte_set_objstat(ses_softc_t *ssc, ses_objarg *obp, int slp)
{
        int idx, err;
        encobj *ep;
        struct scfg *cc;


        SES_LOG(ssc, SES_CE_DEBUG2, "safte_set_objstat(%d): %x %x %x %x",
            (int)obp->obj_id, obp->cstat[0], obp->cstat[1], obp->cstat[2],
            obp->cstat[3]);

        /*
         * If this is clear, we don't do diddly.
         */
        if ((obp->cstat[0] & SESCTL_CSEL) == 0) {
                return (0);
        }

        err = 0;
        /*
         * Check to see if the common bits are set and do them first.
         */
        if (obp->cstat[0] & ~SESCTL_CSEL) {
                err = set_objstat_sel(ssc, obp, slp);
                if (err)
                        return (err);
        }

        cc = ssc->ses_private;
        if (cc == NULL)
                return (0);

        idx = (int)obp->obj_id;
        ep = &ssc->ses_objmap[idx];

        switch (ep->enctype) {
        case SESTYP_DEVICE:
        {
                uchar_t slotop = 0;
                /*
                 * XXX: I should probably cache the previous state
                 * XXX: of SESCTL_DEVOFF so that when it goes from
                 * XXX: true to false I can then set PREPARE FOR OPERATION
                 * XXX: flag in PERFORM SLOT OPERATION write buffer command.
                 */
                if (obp->cstat[2] & (SESCTL_RQSINS|SESCTL_RQSRMV)) {
                        slotop |= 0x2;
                }
                if (obp->cstat[2] & SESCTL_RQSID) {
                        slotop |= 0x4;
                }
                err = perf_slotop(ssc, (uchar_t)idx - (uchar_t)cc->slotoff,
                    slotop, slp);
                if (err)
                        return (err);
                mutex_enter(&ssc->ses_devp->sd_mutex);
                if (obp->cstat[3] & SESCTL_RQSFLT) {
                        ep->priv |= 0x2;
                } else {
                        ep->priv &= ~0x2;
                }
                if (ep->priv & 0xc6) {
                        ep->priv &= ~0x1;
                } else {
                        ep->priv |= 0x1;        /* no errors */
                }
                mutex_exit(&ssc->ses_devp->sd_mutex);
                wrslot_stat(ssc, slp);
                break;
        }
        case SESTYP_POWER:
                mutex_enter(&ssc->ses_devp->sd_mutex);
                if (obp->cstat[3] & SESCTL_RQSTFAIL) {
                        cc->flag1 |= FLG1_ENCPWRFAIL;
                } else {
                        cc->flag1 &= ~FLG1_ENCPWRFAIL;
                }
                mutex_exit(&ssc->ses_devp->sd_mutex);
                err = wrbuf16(ssc, SAFTE_WT_GLOBAL, cc->flag1,
                    cc->flag2, 0, slp);
                if (err)
                        return (err);
                if (obp->cstat[3] & SESCTL_RQSTON) {
                        (void) wrbuf16(ssc, SAFTE_WT_ACTPWS,
                            idx - cc->pwroff, 0, 0, slp);
                } else {
                        (void) wrbuf16(ssc, SAFTE_WT_ACTPWS,
                            idx - cc->pwroff, 0, 1, slp);
                }
                break;
        case SESTYP_FAN:
                mutex_enter(&ssc->ses_devp->sd_mutex);
                if (obp->cstat[3] & SESCTL_RQSTFAIL) {
                        cc->flag1 |= FLG1_ENCFANFAIL;
                } else {
                        cc->flag1 &= ~FLG1_ENCFANFAIL;
                }
                mutex_exit(&ssc->ses_devp->sd_mutex);
                err = wrbuf16(ssc, SAFTE_WT_GLOBAL, cc->flag1,
                    cc->flag2, 0, slp);
                if (err)
                        return (err);
                if (obp->cstat[3] & SESCTL_RQSTON) {
                        uchar_t fsp;
                        if ((obp->cstat[3] & 0x7) == 7) {
                                fsp = 4;
                        } else if ((obp->cstat[3] & 0x7) == 6) {
                                fsp = 3;
                        } else if ((obp->cstat[3] & 0x7) == 4) {
                                fsp = 2;
                        } else {
                                fsp = 1;
                        }
                        (void) wrbuf16(ssc, SAFTE_WT_FANSPD, idx, fsp, 0, slp);
                } else {
                        (void) wrbuf16(ssc, SAFTE_WT_FANSPD, idx, 0, 0, slp);
                }
                break;
        case SESTYP_DOORLOCK:
                mutex_enter(&ssc->ses_devp->sd_mutex);
                if (obp->cstat[3] & 0x1) {
                        cc->flag2 &= ~FLG2_LOCKDOOR;
                } else {
                        cc->flag2 |= FLG2_LOCKDOOR;
                }
                mutex_exit(&ssc->ses_devp->sd_mutex);
                (void) wrbuf16(ssc, SAFTE_WT_GLOBAL, cc->flag1,
                    cc->flag2, 0, slp);
                break;
        case SESTYP_ALARM:
                /*
                 * On all nonzero but the 'muted' bit, we turn on the alarm,
                 */
                mutex_enter(&ssc->ses_devp->sd_mutex);
                obp->cstat[3] &= ~0xa;
                if (obp->cstat[3] & 0x40) {
                        cc->flag2 &= ~FLG1_ALARM;
                } else if (obp->cstat[3] != 0) {
                        cc->flag2 |= FLG1_ALARM;
                } else {
                        cc->flag2 &= ~FLG1_ALARM;
                }
                ep->priv = obp->cstat[3];
                mutex_exit(&ssc->ses_devp->sd_mutex);
                (void) wrbuf16(ssc, SAFTE_WT_GLOBAL, cc->flag1,
                    cc->flag2, 0, slp);
                break;
        default:
                break;
        }
        mutex_enter(&ssc->ses_devp->sd_mutex);
        ep->svalid = 0;
        mutex_exit(&ssc->ses_devp->sd_mutex);
        return (0);
}

static int
set_objstat_sel(ses_softc_t *ssc, ses_objarg *obp, int slp)
{
        int idx;
        encobj *ep;
        struct scfg *cc = ssc->ses_private;

        if (cc == NULL)
                return (0);

        idx = (int)obp->obj_id;
        ep = &ssc->ses_objmap[idx];

        switch (ep->enctype) {
        case SESTYP_DEVICE:
                mutex_enter(&ssc->ses_devp->sd_mutex);
                if (obp->cstat[0] & SESCTL_PRDFAIL) {
                        ep->priv |= 0x40;
                }
                /* SESCTL_RSTSWAP has no correspondence in SAF-TE */
                if (obp->cstat[0] & SESCTL_DISABLE) {
                        ep->priv |= 0x80;
                        /*
                         * Hmm. Try to set the 'No Drive' flag.
                         * Maybe that will count as a 'disable'.
                         */
                }
                if (ep->priv & 0xc6) {
                        ep->priv &= ~0x1;
                } else {
                        ep->priv |= 0x1;        /* no errors */
                }
                mutex_exit(&ssc->ses_devp->sd_mutex);
                wrslot_stat(ssc, slp);
                break;
        case SESTYP_POWER:
                /*
                 * Okay- the only one that makes sense here is to
                 * do the 'disable' for a power supply.
                 */
                if (obp->cstat[0] & SESCTL_DISABLE) {
                        (void) wrbuf16(ssc, SAFTE_WT_ACTPWS,
                            idx - cc->pwroff, 0, 0, slp);
                }
                break;
        case SESTYP_FAN:
                /*
                 * Okay- the only one that makes sense here is to
                 * set fan speed to zero on disable.
                 */
                if (obp->cstat[0] & SESCTL_DISABLE) {
                        /* remember- fans are the first items, so idx works */
                        (void) wrbuf16(ssc, SAFTE_WT_FANSPD, idx, 0, 0, slp);
                }
                break;
        case SESTYP_DOORLOCK:
                /*
                 * Well, we can 'disable' the lock.
                 */
                if (obp->cstat[0] & SESCTL_DISABLE) {
                        mutex_enter(&ssc->ses_devp->sd_mutex);
                        cc->flag2 &= ~FLG2_LOCKDOOR;
                        mutex_exit(&ssc->ses_devp->sd_mutex);
                        (void) wrbuf16(ssc, SAFTE_WT_GLOBAL, cc->flag1,
                            cc->flag2, 0, slp);
                }
                break;
        case SESTYP_ALARM:
                /*
                 * Well, we can 'disable' the alarm.
                 */
                if (obp->cstat[0] & SESCTL_DISABLE) {
                        mutex_enter(&ssc->ses_devp->sd_mutex);
                        cc->flag2 &= ~FLG1_ALARM;
                        ep->priv |= 0x40;       /* Muted */
                        mutex_exit(&ssc->ses_devp->sd_mutex);
                        (void) wrbuf16(ssc, SAFTE_WT_GLOBAL, cc->flag1,
                            cc->flag2, 0, slp);
                }
                break;
        default:
                break;
        }
        mutex_enter(&ssc->ses_devp->sd_mutex);
        ep->svalid = 0;
        mutex_exit(&ssc->ses_devp->sd_mutex);
        return (0);
}

/*
 * This function handles all of the 16 byte WRITE BUFFER commands.
 */
static int
wrbuf16(ses_softc_t *ssc, uchar_t op, uchar_t b1, uchar_t b2,
    uchar_t b3, int slp)
{
        int err;
        Uscmd local, *lp = &local;
        char rqbuf[SENSE_LENGTH], *sdata;
        struct scfg *cc = ssc->ses_private;
        static char cdb[CDB_GROUP1] =
            { SCMD_WRITE_BUFFER, 1, 0, 0, 0, 0, 0, 0, 16, 0 };

        if (cc == NULL)
                return (0);

        sdata = kmem_alloc(16, slp);
        if (sdata == NULL)
                return (ENOMEM);

        lp->uscsi_flags = USCSI_WRITE|USCSI_RQENABLE;
        lp->uscsi_timeout = ses_io_time;
        lp->uscsi_cdb = cdb;
        lp->uscsi_bufaddr = sdata;
        lp->uscsi_buflen = SCRATCH;
        lp->uscsi_cdblen = sizeof (cdb);
        lp->uscsi_rqbuf = rqbuf;
        lp->uscsi_rqlen = sizeof (rqbuf);

        sdata[0] = op;
        sdata[1] = b1;
        sdata[2] = b2;
        sdata[3] = b3;
        SES_LOG(ssc, SES_CE_DEBUG2, "saf_wrbuf16 %x %x %x %x", op, b1, b2, b3);
        bzero(&sdata[4], 12);
        err = ses_runcmd(ssc, lp);
        kmem_free(sdata, 16);
        return (err);
}

/*
 * This function updates the status byte for the device slot described.
 *
 * Since this is an optional SAF-TE command, there's no point in
 * returning an error.
 */
static void
wrslot_stat(ses_softc_t *ssc, int slp)
{
        int i;
        encobj *ep;
        Uscmd local, *lp = &local;
        char rqbuf[SENSE_LENGTH], cdb[CDB_GROUP1], *sdata;
        struct scfg *cc = ssc->ses_private;

        if (cc == NULL)
                return;

        SES_LOG(ssc, SES_CE_DEBUG2, "saf_wrslot");
        cdb[0] = SCMD_WRITE_BUFFER;
        cdb[1] = 1;
        cdb[2] = 0;
        cdb[3] = 0;
        cdb[4] = 0;
        cdb[5] = 0;
        cdb[6] = 0;
        cdb[7] = 0;
        cdb[8] = cc->Nslots * 3 + 1;
        cdb[9] = 0;

        sdata = kmem_zalloc(cc->Nslots * 3 + 1, slp);
        if (sdata == NULL)
                return;

        lp->uscsi_flags = USCSI_WRITE|USCSI_RQENABLE;
        lp->uscsi_timeout = ses_io_time;
        lp->uscsi_cdb = cdb;
        lp->uscsi_bufaddr = sdata;
        lp->uscsi_buflen = cc->Nslots * 3 + 1;
        lp->uscsi_cdblen = sizeof (cdb);
        lp->uscsi_rqbuf = rqbuf;
        lp->uscsi_rqlen = sizeof (rqbuf);

        sdata[0] = SAFTE_WT_DSTAT;
        for (i = 0; i < cc->Nslots; i++) {
                ep = &ssc->ses_objmap[cc->slotoff + i];
                SES_LOG(ssc, SES_CE_DEBUG2, "saf_wrslot %d <- %x", i,
                    ep->priv & 0xff);
                sdata[1 + (3 * i)] = ep->priv & 0xff;
        }
        (void) ses_runcmd(ssc, lp);
        kmem_free(sdata, cc->Nslots * 3 + 1);
}

/*
 * This function issues the "PERFORM SLOT OPERATION" command.
 */
static int
perf_slotop(ses_softc_t *ssc, uchar_t slot, uchar_t opflag, int slp)
{
        int err;
        Uscmd local, *lp = &local;
        char rqbuf[SENSE_LENGTH], *sdata;
        struct scfg *cc = ssc->ses_private;
        static char cdb[CDB_GROUP1] =
            { SCMD_WRITE_BUFFER, 1, 0, 0, 0, 0, 0, 0, SCRATCH, 0 };

        if (cc == NULL)
                return (0);

        sdata = kmem_zalloc(SCRATCH, slp);
        if (sdata == NULL)
                return (ENOMEM);

        lp->uscsi_flags = USCSI_WRITE|USCSI_RQENABLE;
        lp->uscsi_timeout = ses_io_time;
        lp->uscsi_cdb = cdb;
        lp->uscsi_bufaddr = sdata;
        lp->uscsi_buflen = SCRATCH;
        lp->uscsi_cdblen = sizeof (cdb);
        lp->uscsi_rqbuf = rqbuf;
        lp->uscsi_rqlen = sizeof (rqbuf);

        sdata[0] = SAFTE_WT_SLTOP;
        sdata[1] = slot;
        sdata[2] = opflag;
        SES_LOG(ssc, SES_CE_DEBUG2, "saf_slotop slot %d op %x", slot, opflag);
        err = ses_runcmd(ssc, lp);
        kmem_free(sdata, SCRATCH);
        return (err);
}

/*
 * mode: c
 * Local variables:
 * c-indent-level: 8
 * c-brace-imaginary-offset: 0
 * c-brace-offset: -8
 * c-argdecl-indent: 8
 * c-label-offset: -8
 * c-continued-statement-offset: 8
 * c-continued-brace-offset: 0
 * End:
 */