root/usr/src/uts/common/io/scsi/targets/ses_sen.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, SEN 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>


/*
 * The SEN unit is wired to support 7 disk units,
 * two power supplies, one fan module, one overtemp sensor,
 * and one alarm.
 */
#define NOBJECTS        (7+2+1+1+1)
#define DRVOFF  0
#define SDRVOFF 20
#define NDRV    7

#define PWROFF  NDRV
#define SPWROFF 28
#define NPWR    2

#define FANOFF  (PWROFF + NPWR)
#define SFANOFF 30
#define NFAN    1

#define THMOFF  (FANOFF + NFAN)
#define STHMOFF 31
#define NTHM    1

#define ALRMOFF (THMOFF + NTHM)
#define NALRM   1
#define SALRMOFF        8

#define SENPGINSIZE     32
#define SENPGOUTSIZE    22

int
sen_softc_init(ses_softc_t *ssc, int doinit)
{
        int i;
        if (doinit == 0) {
                mutex_enter(&ssc->ses_devp->sd_mutex);
                if (ssc->ses_nobjects) {
                        kmem_free(ssc->ses_objmap,
                            ssc->ses_nobjects * sizeof (encobj));
                        ssc->ses_objmap = NULL;
                        ssc->ses_nobjects = 0;
                }
                mutex_exit(&ssc->ses_devp->sd_mutex);
                return (0);
        }
        mutex_enter(&ssc->ses_devp->sd_mutex);
        ssc->ses_nobjects = 0;
        ssc->ses_encstat = 0;
        ssc->ses_objmap = (encobj *)
            kmem_zalloc(NOBJECTS * sizeof (encobj), KM_SLEEP);
        if (ssc->ses_objmap == NULL) {
                mutex_exit(&ssc->ses_devp->sd_mutex);
                return (ENOMEM);
        }
        for (i = DRVOFF; i < DRVOFF + NDRV; i++) {
                ssc->ses_objmap[i].enctype = SESTYP_DEVICE;
        }
        for (i = PWROFF; i < PWROFF + NPWR; i++) {
                ssc->ses_objmap[i].enctype = SESTYP_POWER;
        }
        for (i = FANOFF; i < FANOFF + NFAN; i++) {
                ssc->ses_objmap[i].enctype = SESTYP_FAN;
        }
        for (i = THMOFF; i < THMOFF + NTHM; i++) {
                ssc->ses_objmap[i].enctype = SESTYP_THERM;
        }
        for (i = ALRMOFF; i < ALRMOFF + NALRM; i++) {
                ssc->ses_objmap[i].enctype = SESTYP_ALARM;
        }
        ssc->ses_nobjects = NOBJECTS;
        mutex_exit(&ssc->ses_devp->sd_mutex);
        return (0);
}

int
sen_init_enc(ses_softc_t *ssc)
{
        UNUSED_PARAMETER(ssc);
        return (0);
}

static int
sen_rdstat(ses_softc_t *ssc, int slpflag)
{
        int err, i, oid, baseid, tmp;
        Uscmd local, *lp = &local;
        char rqbuf[SENSE_LENGTH], *sdata;
        static char cdb[CDB_GROUP0] =
            { SCMD_GDIAG, 0x10, 0x4, 0, SENPGINSIZE, 0 };

        /*
         * Fetch current data
         */
        sdata = kmem_alloc(SENPGINSIZE, slpflag);
        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 = SENPGINSIZE;
        lp->uscsi_cdblen = sizeof (cdb);
        lp->uscsi_rqbuf = rqbuf;
        lp->uscsi_rqlen = sizeof (rqbuf);
        err = ses_runcmd(ssc, lp);
        if (err) {
                kmem_free(sdata, SENPGINSIZE);
                return (err);
        }

        if ((lp->uscsi_buflen - lp->uscsi_resid)  < SENPGINSIZE) {
                SES_LOG(ssc, CE_NOTE, "sen_rdstat: too little data (%ld)",
                    lp->uscsi_buflen - lp->uscsi_resid);
                kmem_free(sdata, SENPGINSIZE);
                return (EIO);
        }

        /*
         * Set base SCSI id for drives...
         */
        if (sdata[10] & 0x80)
                baseid = 8;
        else
                baseid = 0;

        oid = 0;

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

        /*
         * Do Drives...
         */
        for (i = SDRVOFF; i < SDRVOFF + NDRV; i++) {
                ssc->ses_objmap[oid].encstat[1] = baseid + i - SDRVOFF;
                ssc->ses_objmap[oid].encstat[2] = 0;
                if (sdata[i] & 0x80) {
                        /*
                         * Drive is present
                         */
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                } else {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_NOTINSTALLED;
                        ssc->ses_encstat |= ENCSTAT_INFO;
                }
                /*
                 * Is the fault LED lit?
                 */
                if (sdata[i] & 0x40) {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_CRIT;
                        ssc->ses_objmap[oid].encstat[3] = 0x40;
                        ssc->ses_encstat |= ENCSTAT_CRITICAL;
                } else {
                        ssc->ses_objmap[oid].encstat[3] = 0x0;
                }
                ssc->ses_objmap[oid++].svalid = 1;
        }

        /*
         * Do Power Supplies...
         *
         * Power supply bad, or not installed cannot be distinguished.
         * Which one to pick? Let's say 'bad' and make it NONCRITICAL
         * if only one is bad but CRITICAL if both are bad.
         */
        for (tmp = 0, i = SPWROFF; i < SPWROFF + NPWR; i++) {
                ssc->ses_objmap[oid].encstat[1] = 0;
                ssc->ses_objmap[oid].encstat[2] = 0;
                if ((sdata[i] & 0x80) == 0) {
                        /*
                         * Power supply 'ok'...
                         */
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                        tmp++;
                } else {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_CRIT;
                        ssc->ses_encstat |= ENCSTAT_NONCRITICAL;
                }
                ssc->ses_objmap[oid++].svalid = 1;
        }
        if (tmp == 0) {
                ssc->ses_encstat |= ENCSTAT_CRITICAL;
        }

        /*
         *  Do the Fan(s)
         */
        for (i = SFANOFF; i < SFANOFF + NFAN; i++) {
                ssc->ses_objmap[oid].encstat[1] = 0;
                ssc->ses_objmap[oid].encstat[2] = 0;
                if (sdata[i] & 0x20) {  /* both fans have failed */
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_CRIT;
                        ssc->ses_objmap[oid].encstat[3] = 0x40;
                        ssc->ses_encstat |= ENCSTAT_CRITICAL;
                } else if (sdata[i] & 0x80) {   /* one fan has failed */
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_NONCRIT;
                        ssc->ses_objmap[oid].encstat[3] = 0x41;
                        ssc->ses_encstat |= ENCSTAT_NONCRITICAL;
                } else {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                        ssc->ses_objmap[oid].encstat[3] = 0x6;
                }
                ssc->ses_objmap[oid++].svalid = 1;
        }

        /*
         * Do the temperature sensor...
         */
        for (i = STHMOFF; i < STHMOFF + NTHM; i++) {
                ssc->ses_objmap[oid].encstat[1] = 0;
                if (sdata[i] & 0x80) {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_CRIT;
                        /* ssc->ses_objmap[oid].encstat[2] = 0; */
                        ssc->ses_objmap[oid].encstat[3] = 0x8;
                        ssc->ses_encstat |= ENCSTAT_CRITICAL;
                } else {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                        /* ssc->ses_objmap[oid].encstat[2] = 0; */
                        ssc->ses_objmap[oid].encstat[3] = 0;
                }
                ssc->ses_objmap[oid++].svalid = 1;
        }

        /*
         * and last, but not least, check the state of the alarm.
         */
        for (i = SALRMOFF; i < SALRMOFF + NALRM; i++) {
                ssc->ses_objmap[oid].encstat[1] = 0;
                ssc->ses_objmap[oid].encstat[2] = 0;
                if (sdata[i]  & 0x80) { /* Alarm is or was sounding */
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_CRIT;
                        ssc->ses_objmap[oid].encstat[3] = 0x2;
                        if ((sdata[i] & 0xf))
                                ssc->ses_objmap[oid].encstat[3] |= 0x40;
                        ssc->ses_encstat |= ENCSTAT_CRITICAL;
                } else {
                        ssc->ses_objmap[oid].encstat[0] = SESSTAT_OK;
                        ssc->ses_objmap[oid].encstat[3] = 0;
                }
                ssc->ses_objmap[oid++].svalid = 1;
        }
        ssc->ses_encstat |= ENCI_SVALID;
        mutex_exit(&ssc->ses_devp->sd_mutex);
        kmem_free(sdata, SENPGINSIZE);
        return (0);
}

int
sen_get_encstat(ses_softc_t *ssc, int slpflag)
{
        return (sen_rdstat(ssc, slpflag));
}

int
sen_set_encstat(ses_softc_t *ssc, uchar_t encstat, int slpflag)
{
        UNUSED_PARAMETER(ssc);
        UNUSED_PARAMETER(encstat);
        UNUSED_PARAMETER(slpflag);
        return (0);
}

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

        if ((ssc->ses_encstat & ENCI_SVALID) == 0 ||
            (ssc->ses_objmap[i].svalid) == 0) {
                int r = sen_rdstat(ssc, slpflag);
                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
sen_set_objstat(ses_softc_t *ssc, ses_objarg *obp, int slpflag)
{
        encobj *ep;
        int err, runcmd, idx;
        Uscmd local, *lp = &local;
        char rqbuf[SENSE_LENGTH], *sdata;
        static char cdb[CDB_GROUP0] =
            { SCMD_GDIAG, 0x10, 0x4, 0, SENPGINSIZE, 0 };
        static char cdb1[CDB_GROUP0] =
            { SCMD_SDIAG, 0x10, 0, 0, SENPGOUTSIZE, 0 };

        /*
         * If this is clear, we don't do diddly.
         */
        if ((obp->cstat[0] & SESCTL_CSEL) == 0) {
                return (0);
        }
        /*
         * Fetch current data
         */
        sdata = kmem_alloc(SENPGINSIZE, slpflag);
        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 = SENPGINSIZE;
        lp->uscsi_cdblen = sizeof (cdb);
        lp->uscsi_rqbuf = rqbuf;
        lp->uscsi_rqlen = sizeof (rqbuf);
        err = ses_runcmd(ssc, lp);
        if (err) {
                kmem_free(sdata, SENPGINSIZE);
                return (err);
        }
        if ((lp->uscsi_buflen - lp->uscsi_resid)  < SENPGINSIZE) {
                SES_LOG(ssc, CE_NOTE, "Too Little Data Returned (%ld)",
                    lp->uscsi_buflen - lp->uscsi_resid);
                kmem_free(sdata, SENPGINSIZE);
                return (EIO);
        }
        /*
         * Okay, now convert the input page to the output page.
         */
        sdata[1] = 0;
        sdata[3] = 0x12;
        sdata[6] = 1;
        sdata[8] &= ~0x80;
        sdata[10] = 0;
        sdata[14] = sdata[20] & ~0x80;
        sdata[15] = sdata[21] & ~0x80;
        sdata[16] = sdata[22] & ~0x80;
        sdata[17] = sdata[23] & ~0x80;
        sdata[18] = sdata[24] & ~0x80;
        sdata[19] = sdata[25] & ~0x80;
        sdata[20] = sdata[26] & ~0x80;
        sdata[21] = 0;

        runcmd = 0;

        idx = (int)obp->obj_id;
        ep = &ssc->ses_objmap[idx];
        switch (ep->enctype) {
        case SESTYP_DEVICE:
                if (idx < 0 || idx >= NDRV) {
                        err = EINVAL;
                } else if ((obp->cstat[3] & SESCTL_RQSFLT) != 0) {
                        SES_LOG(ssc, SES_CE_DEBUG1, "faulted %d", idx);
                        sdata[14 + idx] |= 0x40;
                        runcmd++;
                } else {
                        SES_LOG(ssc, SES_CE_DEBUG1, "clrd fault on %d", idx);
                        sdata[14 + idx] &= ~0x40;
                        runcmd++;
                }
                break;
        case SESTYP_POWER:
                if ((obp->cstat[3] & SESCTL_RQSTFAIL) ||
                    (obp->cstat[0] & SESCTL_DISABLE)) {
                        SES_LOG(ssc, CE_WARN, "Commanding Off Power Supply!");
                        sdata[10] |= 0x40;      /* Seppuku!!!! */
                        runcmd++;
                }
                break;
        case SESTYP_ALARM:
                /*
                 * On all nonzero but the 'muted' bit,
                 * we turn on the alarm,
                 */
                obp->cstat[3] &= ~0xa;
                if ((obp->cstat[3] & 0x40) ||
                    (obp->cstat[0] & SESCTL_DISABLE)) {
                        sdata[8] = 0;
                } else if (obp->cstat[3] != 0) {
                        sdata[8] = 0x40;
                } else {
                        sdata[8] = 0;
                }
                runcmd++;
                SES_LOG(ssc, SES_CE_DEBUG1, "%sabling alarm",
                    (sdata[8] & 0x40)? "en" : "dis");
                break;
        default:
                break;
        }

        if (runcmd) {
                lp->uscsi_flags = USCSI_WRITE|USCSI_RQENABLE;
                lp->uscsi_timeout = ses_io_time;
                lp->uscsi_cdb = cdb1;
                lp->uscsi_bufaddr = sdata;
                lp->uscsi_buflen = SENPGOUTSIZE;
                lp->uscsi_cdblen = sizeof (cdb);
                lp->uscsi_rqbuf = rqbuf;
                lp->uscsi_rqlen = sizeof (rqbuf);
                err = ses_runcmd(ssc, lp);
                /* preserve error across the rest of the action */
        } else {
                err = 0;
        }

        mutex_enter(&ssc->ses_devp->sd_mutex);
        ep->svalid = 0;
        mutex_exit(&ssc->ses_devp->sd_mutex);
        kmem_free(sdata, SENPGINSIZE);
        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:
 */