/*
 * 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.
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"

#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:
 */