/*
 * Enclosure Services Devices, SAF-TE Enclosure Routines
 *
 * Copyright (c) 1998, 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */
#pragma ident	"@(#)ses_safte.c	1.21	99/02/10 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>


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 (%d) 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) \
	if (r >= x) { \
		SES_LOG(ssc, CE_NOTE, toolittle, x, __LINE__); \
		kmem_free(k, l); \
		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];


	/*
	 * The number of objects overstates things a bit,
	 * both for the bogus 'thermometer' entries and
	 * the drive status (which isn't read at the same
	 * time as the enclosure status), but that's okay.
	 */
	buflen = 4 * cc->Nslots;
	if (ssc->ses_nobjects > buflen)
		buflen = ssc->ses_nobjects;
	sdata = kmem_alloc(buflen, slpflg);
	if (sdata == NULL)
		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);
		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);
		/*
		 * 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 ((int)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);
		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 ((int)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_encstat |= ENCSTAT_INFO;
			break;
		case 0x10:	/* pws is malfunctioning and commanded on */
			ssc->ses_objmap[oid].encstat[3] = 0x61;
			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;
			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++;
	}

	/*
	 * Skip over Slot SCSI IDs
	 */
	r += cc->Nslots;

	/*
	 * We always have doorlock status, no matter what,
	 * but we only save the status if we have one.
	 */
	BAIL(r, hiwater, sdata, buflen);
	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 ((int)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);
	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);
		/*
		 * 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);
	tempflags = sdata[r++];
	BAIL(r, hiwater, sdata, buflen);
	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);
		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);
		mutex_enter(&ssc->ses_devp->sd_mutex);
		ssc->ses_objmap[oid].encstat[0] = SESSTAT_UNSUPPORTED;
		ssc->ses_objmap[oid].encstat[1] = (uchar_t)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);
	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:
 */
