/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Enclosure Services Device target driver
 *
 * Copyright 2007 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/scsi/generic/status.h>
#include <sys/scsi/targets/sesio.h>
#include <sys/scsi/targets/ses.h>



/*
 * Power management defines (should be in a common include file?)
 */
#define	PM_HARDWARE_STATE_PROP		"pm-hardware-state"
#define	PM_NEEDS_SUSPEND_RESUME		"needs-suspend-resume"


/*
 * Global Driver Data
 */
int ses_io_time = SES_IO_TIME;

static int ses_retry_count = SES_RETRY_COUNT * SES_RETRY_MULTIPLIER;

#ifdef	DEBUG
int ses_debug = 0;
#else	/* DEBUG */
#define	ses_debug	0
#endif	/* DEBUG */


/*
 * External Enclosure Functions
 */
extern int ses_softc_init(ses_softc_t *, int);
extern int ses_init_enc(ses_softc_t *);
extern int ses_get_encstat(ses_softc_t *, int);
extern int ses_set_encstat(ses_softc_t *, uchar_t, int);
extern int ses_get_objstat(ses_softc_t *, ses_objarg *, int);
extern int ses_set_objstat(ses_softc_t *, ses_objarg *, int);

extern int safte_softc_init(ses_softc_t *, int);
extern int safte_init_enc(ses_softc_t *);
extern int safte_get_encstat(ses_softc_t *, int);
extern int safte_set_encstat(ses_softc_t *, uchar_t, int);
extern int safte_get_objstat(ses_softc_t *, ses_objarg *, int);
extern int safte_set_objstat(ses_softc_t *, ses_objarg *, int);

extern int sen_softc_init(ses_softc_t *, int);
extern int sen_init_enc(ses_softc_t *);
extern int sen_get_encstat(ses_softc_t *, int);
extern int sen_set_encstat(ses_softc_t *, uchar_t, int);
extern int sen_get_objstat(ses_softc_t *, ses_objarg *, int);
extern int sen_set_objstat(ses_softc_t *, ses_objarg *, int);

/*
 * Local Function prototypes
 */
static int ses_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int ses_probe(dev_info_t *);
static int ses_attach(dev_info_t *, ddi_attach_cmd_t);
static int ses_detach(dev_info_t *, ddi_detach_cmd_t);

static int is_enc_dev(ses_softc_t *, struct scsi_inquiry *, int, enctyp *);
static int ses_doattach(dev_info_t *dip);

static int  ses_open(dev_t *, int, int, cred_t *);
static int  ses_close(dev_t, int, int, cred_t *);
static int  ses_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);

static encvec vecs[3] = {
{
	ses_softc_init, ses_init_enc, ses_get_encstat,
	ses_set_encstat, ses_get_objstat, ses_set_objstat
},
{
	safte_softc_init, safte_init_enc, safte_get_encstat,
	safte_set_encstat, safte_get_objstat, safte_set_objstat,
},
{
	sen_softc_init, sen_init_enc, sen_get_encstat,
	sen_set_encstat, sen_get_objstat, sen_set_objstat
}
};


/*
 * Local Functions
 */
static int ses_start(struct buf *bp);
static int ses_decode_sense(struct scsi_pkt *pkt, int *err);

static void ses_get_pkt(struct buf *bp, int (*func)(opaque_t));
static void ses_callback(struct scsi_pkt *pkt);
static void ses_restart(void *arg);


/*
 * Local Static Data
 */
#ifndef	D_HOTPLUG
#define	D_HOTPLUG	0
#endif /* D_HOTPLUG */

static struct cb_ops ses_cb_ops = {
	ses_open,			/* open */
	ses_close,			/* close */
	nodev,				/* strategy */
	nodev,				/* print */
	nodev,				/* dump */
	nodev,				/* read */
	nodev,				/* write */
	ses_ioctl,			/* ioctl */
	nodev,				/* devmap */
	nodev,				/* mmap */
	nodev,				/* segmap */
	nochpoll,			/* poll */
	ddi_prop_op,			/* cb_prop_op */
	0,				/* streamtab  */
#if	!defined(CB_REV)
	D_MP | D_NEW | D_HOTPLUG	/* Driver compatibility flag */
#else	/* !defined(CB_REV) */
	D_MP | D_NEW | D_HOTPLUG,	/* Driver compatibility flag */
	CB_REV,				/* cb_ops version number */
	nodev,				/* aread */
	nodev				/* awrite */
#endif	/* !defined(CB_REV) */
};

static struct dev_ops ses_dev_ops = {
	DEVO_REV,		/* devo_rev, */
	0,			/* refcnt  */
	ses_info,		/* info */
	nulldev,		/* identify */
	ses_probe,		/* probe */
	ses_attach,		/* attach */
	ses_detach,		/* detach */
	nodev,			/* reset */
	&ses_cb_ops,		/* driver operations */
	(struct bus_ops *)NULL,	/* bus operations */
	NULL			/* power */
};

static void *estate  = NULL;
static const char *Snm = "ses";
static const char *Str = "%s\n";
static const char *efl = "copyin/copyout EFAULT @ line %d";
static const char *fail_msg = "%stransport failed: reason '%s': %s";



/*
 * autoconfiguration routines.
 */
char _depends_on[] = "misc/scsi";

static struct modldrv modldrv = {
	&mod_driverops, "SCSI Enclosure Services %I%", &ses_dev_ops
};

static struct modlinkage modlinkage = {
	MODREV_1, &modldrv, NULL
};


int
_init(void)
{
	int status;
	status = ddi_soft_state_init(&estate, sizeof (ses_softc_t), 0);
	if (status == 0) {
		if ((status = mod_install(&modlinkage)) != 0) {
			ddi_soft_state_fini(&estate);
		}
	}
	return (status);
}

int
_fini(void)
{
	int status;
	if ((status = mod_remove(&modlinkage)) != 0) {
		return (status);
	}
	ddi_soft_state_fini(&estate);
	return (status);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

static int
ses_probe(dev_info_t *dip)
{
	int			err;
	struct scsi_device	*devp;
	enctyp			ep;

	/*
	 * I finally figured out why we return success
	 * on every probe. The devices that we attach to
	 * don't all report as being the same "device type"
	 *
	 * 1) A5x00 -- report as Enclosure Services (0xD) SES
	 * 2) A1000 -- report as Direct Access (0x0) SES
	 *    uses the same target as raid controler.
	 * 3) D1000 -- report as processor (0x3) SAFTE
	 * 3) D240  -- report as processor (0x3) SAFTE
	 *
	 * We also reportedly attach to SEN devices which I
	 * believe reside in a Tobasco tray.  I have never
	 * been able to get one to attach.
	 *
	 */


	if (dip == NULL)
		return (DDI_PROBE_FAILURE);
	/*
	 * XXX: Breakage from the x86 folks.
	 */
	if (strcmp(ddi_get_name(ddi_get_parent(dip)), "ata") == 0) {
		return (DDI_PROBE_FAILURE);
	}
	/* SES_LOG(NULL, SES_CE_DEBUG1, "ses_probe: OK"); */
	if (ddi_dev_is_sid(dip) == DDI_SUCCESS) {
	    return (DDI_PROBE_DONTCARE);
	}
	devp = ddi_get_driver_private(dip);
	switch (err = scsi_probe(devp, SLEEP_FUNC)) {
	case SCSIPROBE_EXISTS:
	    if (is_enc_dev(NULL, devp->sd_inq, SUN_INQSIZE, &ep)) {
		break;
	    }
		/* FALLTHROUGH */
	case SCSIPROBE_NORESP:
	    scsi_unprobe(devp);
	    return (DDI_PROBE_FAILURE);
	default:
	    SES_LOG(NULL, SES_CE_DEBUG9,
		"ses_probe: probe error %d", err);
	    scsi_unprobe(devp);
	    return (DDI_PROBE_FAILURE);
	}
	scsi_unprobe(devp);
	return (DDI_PROBE_SUCCESS);
}

static int
ses_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	int inst, err;
	ses_softc_t *ssc;

	inst = ddi_get_instance(dip);
	switch (cmd) {
	case DDI_ATTACH:
		SES_LOG(NULL, SES_CE_DEBUG9, "ses_attach: DDI_ATTACH ses%d",
		    inst);

		err = ses_doattach(dip);

		if (err == DDI_FAILURE) {
			return (DDI_FAILURE);
		}
		SES_LOG(NULL, SES_CE_DEBUG4,
		    "ses_attach: DDI_ATTACH OK ses%d", inst);
		break;

	case DDI_RESUME:
		if ((ssc = ddi_get_soft_state(estate, inst)) == NULL) {
			return (DDI_FAILURE);
		}
		SES_LOG(ssc, SES_CE_DEBUG1, "ses_attach: DDI_ATTACH ses%d",
		    inst);
		ssc->ses_suspended = 0;
		break;

	default:
		return (DDI_FAILURE);
	}
	return (DDI_SUCCESS);
}

static int
is_enc_dev(ses_softc_t *ssc, struct scsi_inquiry *inqp, int iqlen, enctyp *ep)
{
	uchar_t dt = (inqp->inq_dtype & DTYPE_MASK);
	uchar_t *iqd = (uchar_t *)inqp;

	if (dt == DTYPE_ESI) {
		if (strncmp(inqp->inq_vid, SEN_ID, SEN_ID_LEN) == 0) {
			SES_LOG(ssc, SES_CE_DEBUG3, "SEN device found");
			*ep = SEN_TYPE;
		} else if (inqp->inq_rdf > RDF_SCSI2) {
			SES_LOG(ssc, SES_CE_DEBUG3, "SES device found");
			*ep = SES_TYPE;
		} else {
			SES_LOG(ssc, SES_CE_DEBUG3, "Pre-SCSI3 SES device");
			*ep = SES_TYPE;
		}
		return (1);
	}
	if ((iqd[6] & 0x40) && inqp->inq_rdf >= RDF_SCSI2) {
		/*
		 * PassThrough Device.
		 */
		*ep = SES_TYPE;
		SES_LOG(ssc, SES_CE_DEBUG3, "Passthru SES device");
		return (1);
	}

	if (iqlen < 47) {
		SES_LOG(ssc, CE_NOTE,
		    "INQUIRY data too short to determine SAF-TE");
		return (0);
	}
	if (strncmp((char *)&iqd[44], "SAF-TE", 4) == 0) {
		*ep = SAFT_TYPE;
		SES_LOG(ssc, SES_CE_DEBUG3, "SAF-TE device found");
		return (1);
	}
	return (0);
}


/*
 * Attach ses device.
 *
 * XXX:  Power management is NOT supported.  A token framework
 *       is provided that will need to be extended assuming we have
 *       ses devices we can power down.  Currently, we don't have any.
 */
static int
ses_doattach(dev_info_t *dip)
{
	int inst, err;
	Scsidevp devp;
	ses_softc_t *ssc;
	enctyp etyp;

	inst = ddi_get_instance(dip);
	/*
	 * Workaround for bug #4154979- for some reason we can
	 * be called with identical instance numbers but for
	 * different dev_info_t-s- all but one are bogus.
	 *
	 * Bad Dog! No Biscuit!
	 *
	 * A quick workaround might be to call ddi_soft_state_zalloc
	 * unconditionally, as the implementation fails these calls
	 * if there's an item already allocated. A more reasonable
	 * and longer term change is to move the allocation past
	 * the probe for the device's existence as most of these
	 * 'bogus' calls are for nonexistent devices.
	 */

	devp  = ddi_get_driver_private(dip);
	devp->sd_dev = dip;

	/*
	 * Determine whether the { i, t, l } we're called
	 * to start is an enclosure services device.
	 */

	/*
	 * Call the scsi_probe routine to see whether
	 * we actually have an Enclosure Services device at
	 * this address.
	 */
	err = scsi_probe(devp, SLEEP_FUNC);
	if (err != SCSIPROBE_EXISTS) {
		SES_LOG(NULL, SES_CE_DEBUG9,
		    "ses_doattach: probe error %d", err);
		scsi_unprobe(devp);
		return (DDI_FAILURE);
	}
	/* Call is_enc_dev() to get the etyp */
	if (!(is_enc_dev(NULL, devp->sd_inq, SUN_INQSIZE, &etyp))) {
		SES_LOG(NULL, CE_WARN,
		    "ses_doattach: ses%d: is_enc_dev failure", inst);
		scsi_unprobe(devp);
		return (DDI_FAILURE);
	}

	if (ddi_soft_state_zalloc(estate, inst) != DDI_SUCCESS) {
		scsi_unprobe(devp);
		SES_LOG(NULL, CE_NOTE, "ses%d: softalloc fails", inst);
		return (DDI_FAILURE);
	}
	ssc = ddi_get_soft_state(estate, inst);
	if (ssc == NULL) {
		scsi_unprobe(devp);
		SES_LOG(NULL, CE_NOTE, "ses%d: get_soft_state fails", inst);
		return (DDI_FAILURE);
	}
	devp->sd_private = (opaque_t)ssc;
	ssc->ses_devp = devp;
	err = ddi_create_minor_node(dip, "0", S_IFCHR, inst,
		DDI_NT_SCSI_ENCLOSURE, NULL);
	if (err == DDI_FAILURE) {
		ddi_remove_minor_node(dip, NULL);
		SES_LOG(ssc, CE_NOTE, "minor node creation failed");
		ddi_soft_state_free(estate, inst);
		scsi_unprobe(devp);
		return (DDI_FAILURE);
	}

	ssc->ses_type = etyp;
	ssc->ses_vec = vecs[etyp];

	/* Call SoftC Init Routine A bit later... */

	ssc->ses_rqbp = scsi_alloc_consistent_buf(SES_ROUTE(ssc),
	    NULL, SENSE_LENGTH, B_READ, SLEEP_FUNC, NULL);
	if (ssc->ses_rqbp != NULL) {
		ssc->ses_rqpkt = scsi_init_pkt(SES_ROUTE(ssc), NULL,
		    ssc->ses_rqbp, CDB_GROUP0, 1, 0, PKT_CONSISTENT,
		    SLEEP_FUNC, NULL);
	}
	if (ssc->ses_rqbp == NULL || ssc->ses_rqpkt == NULL) {
		ddi_remove_minor_node(dip, NULL);
		SES_LOG(ssc, CE_NOTE, "scsi_init_pkt of rqbuf failed");
		if (ssc->ses_rqbp != NULL) {
			scsi_free_consistent_buf(ssc->ses_rqbp);
			ssc->ses_rqbp = NULL;
		}
		ddi_soft_state_free(estate, inst);
		scsi_unprobe(devp);
		return (DDI_FAILURE);
	}
	ssc->ses_rqpkt->pkt_private = (opaque_t)ssc;
	ssc->ses_rqpkt->pkt_address = *(SES_ROUTE(ssc));
	ssc->ses_rqpkt->pkt_comp = ses_callback;
	ssc->ses_rqpkt->pkt_time = ses_io_time;
	ssc->ses_rqpkt->pkt_flags = FLAG_NOPARITY|FLAG_NODISCON|FLAG_SENSING;
	ssc->ses_rqpkt->pkt_cdbp[0] = SCMD_REQUEST_SENSE;
	ssc->ses_rqpkt->pkt_cdbp[1] = 0;
	ssc->ses_rqpkt->pkt_cdbp[2] = 0;
	ssc->ses_rqpkt->pkt_cdbp[3] = 0;
	ssc->ses_rqpkt->pkt_cdbp[4] = SENSE_LENGTH;
	ssc->ses_rqpkt->pkt_cdbp[5] = 0;

	switch (scsi_ifgetcap(SES_ROUTE(ssc), "auto-rqsense", 1)) {
	case 1:
		/* if already set, don't reset it */
		ssc->ses_arq = 1;
		break;
	case 0:
		/* try and set it */
		ssc->ses_arq = ((scsi_ifsetcap(SES_ROUTE(ssc),
		    "auto-rqsense", 1, 1) == 1) ? 1 : 0);
		break;
	default:
		/* probably undefined, so zero it out */
		ssc->ses_arq = 0;
		break;
	}

	ssc->ses_sbufp = getrbuf(KM_SLEEP);
	cv_init(&ssc->ses_sbufcv, NULL, CV_DRIVER, NULL);

	/*
	 * If the HBA supports wide, tell it to use wide.
	 */
	if (scsi_ifgetcap(SES_ROUTE(ssc), "wide-xfer", 1) != -1) {
		int wd = ((devp->sd_inq->inq_rdf == RDF_SCSI2) &&
		    (devp->sd_inq->inq_wbus16 || devp->sd_inq->inq_wbus32))
		    ? 1 : 0;
		(void) scsi_ifsetcap(SES_ROUTE(ssc), "wide-xfer", wd, 1);
	}

	/*
	 * Now do ssc init of enclosure specifics.
	 * At the same time, check to make sure getrbuf
	 * actually succeeded.
	 */
	if ((*ssc->ses_vec.softc_init)(ssc, 1)) {
		SES_LOG(ssc, SES_CE_DEBUG3, "failed softc init");
		(void) (*ssc->ses_vec.softc_init)(ssc, 0);
		ddi_remove_minor_node(dip, NULL);
		scsi_destroy_pkt(ssc->ses_rqpkt);
		scsi_free_consistent_buf(ssc->ses_rqbp);
		if (ssc->ses_sbufp) {
			freerbuf(ssc->ses_sbufp);
		}
		cv_destroy(&ssc->ses_sbufcv);
		ddi_soft_state_free(estate, inst);
		scsi_unprobe(devp);
		return (DDI_FAILURE);
	}

	/*
	 * create this property so that PM code knows we want
	 * to be suspended at PM time
	 */
	(void) ddi_prop_update_string(DDI_DEV_T_NONE, dip,
	    PM_HARDWARE_STATE_PROP, PM_NEEDS_SUSPEND_RESUME);

	/* announce the existence of this device */
	ddi_report_dev(dip);
	return (DDI_SUCCESS);
}


/*
 * Detach ses device.
 *
 * XXX:  Power management is NOT supported.  A token framework
 *       is provided that will need to be extended assuming we have
 *       ses devices we can power down.  Currently, we don't have any.
 */
static int
ses_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	ses_softc_t *ssc;
	int inst;

	switch (cmd) {
	case DDI_DETACH:
		inst = ddi_get_instance(dip);
		ssc = ddi_get_soft_state(estate, inst);
		if (ssc == NULL) {
			cmn_err(CE_NOTE,
			    "ses%d: DDI_DETACH, no softstate found", inst);
			return (DDI_FAILURE);
		}
		if (ISOPEN(ssc)) {
			return (DDI_FAILURE);
		}

#if		!defined(lint)
		/* LINTED */
		_NOTE(COMPETING_THREADS_NOW);
#endif		/* !defined(lint) */

		if (ssc->ses_vec.softc_init)
			(void) (*ssc->ses_vec.softc_init)(ssc, 0);

#if		!defined(lint)
		_NOTE(NO_COMPETING_THREADS_NOW);
#endif 		/* !defined(lint) */

		(void) scsi_ifsetcap(SES_ROUTE(ssc), "auto-rqsense", 1, 0);
		scsi_destroy_pkt(ssc->ses_rqpkt);
		scsi_free_consistent_buf(ssc->ses_rqbp);
		freerbuf(ssc->ses_sbufp);
		cv_destroy(&ssc->ses_sbufcv);
		ddi_soft_state_free(estate, inst);
		ddi_prop_remove_all(dip);
		ddi_remove_minor_node(dip, NULL);
		scsi_unprobe(ddi_get_driver_private(dip));
		break;

	case DDI_SUSPEND:
		inst = ddi_get_instance(dip);
		if ((ssc = ddi_get_soft_state(estate, inst)) == NULL) {
			cmn_err(CE_NOTE,
			    "ses%d: DDI_SUSPEND, no softstate found", inst);
			return (DDI_FAILURE);
		}

		/*
		 * If driver idle, accept suspend request.
		 * If it's busy, reject it.  This keeps things simple!
		 */
		mutex_enter(SES_MUTEX);
		if (ssc->ses_sbufbsy) {
			mutex_exit(SES_MUTEX);
			return (DDI_FAILURE);
		}
		ssc->ses_suspended = 1;
		mutex_exit(SES_MUTEX);
		break;

	default:
		return (DDI_FAILURE);
	}
	return (DDI_SUCCESS);
}

/* ARGSUSED */
static int
ses_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
	dev_t dev;
	ses_softc_t *ssc;
	int inst, error;

	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		dev = (dev_t)arg;
		inst = getminor(dev);
		if ((ssc = ddi_get_soft_state(estate, inst)) == NULL) {
			return (DDI_FAILURE);
		}
		*result = (void *) ssc->ses_devp->sd_dev;
		error = DDI_SUCCESS;
		break;
	case DDI_INFO_DEVT2INSTANCE:
		dev = (dev_t)arg;
		inst = getminor(dev);
		*result = (void *)(uintptr_t)inst;
		error = DDI_SUCCESS;
		break;
	default:
		error = DDI_FAILURE;
	}
	return (error);
}

/*
 * Unix Entry Points
 */

/* ARGSUSED */
static int
ses_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
{
	ses_softc_t *ssc;

	if ((ssc = ddi_get_soft_state(estate, getminor(*dev_p))) == NULL) {
		return (ENXIO);
	}

	/*
	 * If the device is powered down, request it's activation.
	 * If it can't be activated, fail open.
	 */
	if (ssc->ses_suspended &&
	    ddi_dev_is_needed(SES_DEVINFO(ssc), 0, 1) != DDI_SUCCESS) {
		return (EIO);
	}

	mutex_enter(SES_MUTEX);
	if (otyp == OTYP_LYR)
		ssc->ses_lyropen++;
	else
		ssc->ses_oflag = 1;

	ssc->ses_present = (ssc->ses_present)? ssc->ses_present: SES_OPENING;
	mutex_exit(SES_MUTEX);
	return (EOK);
}

/*ARGSUSED*/
static int
ses_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
{
	ses_softc_t *ssc;
	if ((ssc = ddi_get_soft_state(estate, getminor(dev))) == NULL) {
		return (ENXIO);
	}

	if (ssc->ses_suspended) {
		(void) ddi_dev_is_needed(SES_DEVINFO(ssc), 0, 1);
	}

	mutex_enter(SES_MUTEX);
	if (otyp == OTYP_LYR)
		ssc->ses_lyropen -= (ssc->ses_lyropen)? 1: 0;
	else
		ssc->ses_oflag = 0;
	mutex_exit(SES_MUTEX);
	return (0);
}


/*ARGSUSED3*/
static int
ses_ioctl(dev_t dev, int cmd, intptr_t arg, int flg, cred_t *cred_p, int *rvalp)
{
	ses_softc_t *ssc;
	ses_object k, *up;
	ses_objarg x;
	uchar_t t;
	uchar_t i;
	int rv = 0;

	if ((ssc = ddi_get_soft_state(estate, getminor(dev))) == NULL ||
	    ssc->ses_present == SES_CLOSED) {
		return (ENXIO);
	}


	switch (cmd) {
	case SESIOC_GETNOBJ:
		if (ddi_copyout(&ssc->ses_nobjects, (void *)arg,
		    sizeof (int), flg)) {
			rv = EFAULT;
			break;
		}
		break;

	case SESIOC_GETOBJMAP:
		up = (ses_object *) arg;
		mutex_enter(SES_MUTEX);
		for (i = 0; i != ssc->ses_nobjects; i++) {
			k.obj_id = i;
			k.subencid = ssc->ses_objmap[i].subenclosure;
			k.elem_type = ssc->ses_objmap[i].enctype;
			if (ddi_copyout(&k, up, sizeof (k), flg)) {
				rv = EFAULT;
				break;
			}
			up++;
		}
		mutex_exit(SES_MUTEX);
		break;

	case SESIOC_INIT:
		rv = (*ssc->ses_vec.init_enc)(ssc);
		break;

	case SESIOC_GETENCSTAT:
		if ((ssc->ses_encstat & ENCI_SVALID) == 0) {
			rv = (*ssc->ses_vec.get_encstat)(ssc, KM_SLEEP);
			if (rv) {
				break;
			}
		}
		t = ssc->ses_encstat & 0xf;
		if (ddi_copyout(&t, (void *)arg, sizeof (t), flg))
			rv = EFAULT;
		/*
		 * And always invalidate enclosure status on the way out.
		 */
		mutex_enter(SES_MUTEX);
		ssc->ses_encstat &= ~ENCI_SVALID;
		mutex_exit(SES_MUTEX);
		break;

	case SESIOC_SETENCSTAT:
		if (ddi_copyin((void *)arg, &t, sizeof (t), flg))
			rv = EFAULT;
		else
			rv = (*ssc->ses_vec.set_encstat)(ssc, t, KM_SLEEP);
		mutex_enter(SES_MUTEX);
		ssc->ses_encstat &= ~ENCI_SVALID;
		mutex_exit(SES_MUTEX);
		break;

	case SESIOC_GETOBJSTAT:
		if (ddi_copyin((void *)arg, &x, sizeof (x), flg)) {
			rv = EFAULT;
			break;
		}
		if (x.obj_id >= ssc->ses_nobjects) {
			rv = EINVAL;
			break;
		}
		if ((rv = (*ssc->ses_vec.get_objstat)(ssc, &x, KM_SLEEP)) != 0)
			break;
		if (ddi_copyout(&x, (void *)arg, sizeof (x), flg))
			rv = EFAULT;
		else {
			/*
			 * Now that we no longer poll, svalid never stays true.
			 */
			mutex_enter(SES_MUTEX);
			ssc->ses_objmap[x.obj_id].svalid = 0;
			mutex_exit(SES_MUTEX);
		}
		break;

	case SESIOC_SETOBJSTAT:
		if (ddi_copyin((void *)arg, &x, sizeof (x), flg)) {
			rv = EFAULT;
			break;
		}
		if (x.obj_id >= ssc->ses_nobjects) {
			rv = EINVAL;
			break;
		}
		rv = (*ssc->ses_vec.set_objstat)(ssc, &x, KM_SLEEP);
		if (rv == 0) {
			mutex_enter(SES_MUTEX);
			ssc->ses_objmap[x.obj_id].svalid = 0;
			mutex_exit(SES_MUTEX);
		}
		break;

	case USCSICMD:
		rv = ses_uscsi_cmd(ssc, (Uscmd *)arg, flg);
		break;

	default:
		rv = ENOTTY;
		break;
	}
	return (rv);
}


/*
 * Loop on running a kernel based command
 *
 * FIXME:  This routine is not really needed.
 */
int
ses_runcmd(ses_softc_t *ssc, Uscmd *lp)
{
	int e;

	lp->uscsi_status = 0;
	e = ses_uscsi_cmd(ssc, lp, FKIOCTL);

#ifdef	not
	/*
	 * Debug:  Nice cross-check code for verifying consistent status.
	 */
	if (lp->uscsi_status) {
		if (lp->uscsi_status == STATUS_CHECK) {
			SES_LOG(ssc, CE_NOTE, "runcmd<cdb[0]="
			    "0x%x->%s ASC/ASCQ=0x%x/0x%x>",
			    lp->uscsi_cdb[0],
			    scsi_sname(lp->uscsi_rqbuf[2] & 0xf),
			    lp->uscsi_rqbuf[12] & 0xff,
			    lp->uscsi_rqbuf[13] & 0xff);
		} else {
			SES_LOG(ssc, CE_NOTE, "runcmd<cdb[0]="
			    "0x%x -> Status 0x%x", lp->uscsi_cdb[0],
			    lp->uscsi_status);
		}
	}
#endif	/* not */
	return (e);
}


/*
 * Run a scsi command.
 */
int
ses_uscsi_cmd(ses_softc_t *ssc, Uscmd *Uc, int Uf)
{
	Uscmd	*uscmd;
	struct buf	*bp;
	enum uio_seg	uioseg;
	int	err;

	/*
	 * Grab local 'special' buffer
	 */
	mutex_enter(SES_MUTEX);
	while (ssc->ses_sbufbsy) {
		cv_wait(&ssc->ses_sbufcv, &ssc->ses_devp->sd_mutex);
	}
	ssc->ses_sbufbsy = 1;
	mutex_exit(SES_MUTEX);

	/*
	 * If the device is powered down, request it's activation.
	 * This check must be done after setting ses_sbufbsy!
	 */
	if (ssc->ses_suspended &&
	    ddi_dev_is_needed(SES_DEVINFO(ssc), 0, 1) != DDI_SUCCESS) {
		mutex_enter(SES_MUTEX);
		ssc->ses_sbufbsy = 0;
		mutex_exit(SES_MUTEX);
		return (EIO);
	}

	err = scsi_uscsi_alloc_and_copyin((intptr_t)Uc, Uf,
	    SES_ROUTE(ssc), &uscmd);
	if (err != 0) {
		SES_LOG(ssc, SES_CE_DEBUG1, "ses_uscsi_cmd: "
		    "scsi_uscsi_alloc_and_copyin failed\n");
		mutex_enter(SES_MUTEX);
		ssc->ses_sbufbsy = 0;
		cv_signal(&ssc->ses_sbufcv);
		mutex_exit(SES_MUTEX);
		SES_LOG(ssc, SES_CE_DEBUG2, efl, __LINE__);
		return (err);
	}

	/*
	 * Copy the uscsi command related infos to ssc for use in ses_start()
	 * and ses_callback().
	 */
	bcopy(uscmd, &ssc->ses_uscsicmd, sizeof (Uscmd));
	if (uscmd->uscsi_cdb != NULL) {
		bcopy(uscmd->uscsi_cdb, &ssc->ses_srqcdb,
		    (size_t)(uscmd->uscsi_cdblen));
	}

	bp = ssc->ses_sbufp;
	bp->av_back = (struct buf *)NULL;
	bp->av_forw = (struct buf *)NULL;
	bp->b_back = (struct buf *)ssc;
	bp->b_edev = NODEV;

	if (uscmd->uscsi_cdb != NULL) {
		if (uscmd->uscsi_cdblen == CDB_GROUP0) {
			SES_LOG(ssc, SES_CE_DEBUG7,
			    "scsi_cmd: %x %x %x %x %x %x",
			    ((char *)uscmd->uscsi_cdb)[0],
			    ((char *)uscmd->uscsi_cdb)[1],
			    ((char *)uscmd->uscsi_cdb)[2],
			    ((char *)uscmd->uscsi_cdb)[3],
			    ((char *)uscmd->uscsi_cdb)[4],
			    ((char *)uscmd->uscsi_cdb)[5]);
		} else {
			SES_LOG(ssc, SES_CE_DEBUG7,
			    "scsi cmd: %x %x %x %x %x %x %x %x %x %x",
			    ((char *)uscmd->uscsi_cdb)[0],
			    ((char *)uscmd->uscsi_cdb)[1],
			    ((char *)uscmd->uscsi_cdb)[2],
			    ((char *)uscmd->uscsi_cdb)[3],
			    ((char *)uscmd->uscsi_cdb)[4],
			    ((char *)uscmd->uscsi_cdb)[5],
			    ((char *)uscmd->uscsi_cdb)[6],
			    ((char *)uscmd->uscsi_cdb)[7],
			    ((char *)uscmd->uscsi_cdb)[8],
			    ((char *)uscmd->uscsi_cdb)[9]);
		}
	}

	uioseg = (Uf & FKIOCTL) ? UIO_SYSSPACE : UIO_USERSPACE;
	err = scsi_uscsi_handle_cmd(NODEV, uioseg, uscmd,
	    ses_start, bp, NULL);

	/*
	 * ses_callback() may set values for ssc->ses_uscsicmd or
	 * ssc->ses_srqsbuf, so copy them back to uscmd.
	 */
	if (uscmd->uscsi_rqbuf != NULL) {
		bcopy(&ssc->ses_srqsbuf, uscmd->uscsi_rqbuf,
		    (size_t)(uscmd->uscsi_rqlen));
		uscmd->uscsi_rqresid = ssc->ses_uscsicmd.uscsi_rqresid;
	}
	uscmd->uscsi_status = ssc->ses_uscsicmd.uscsi_status;

	(void) scsi_uscsi_copyout_and_free((intptr_t)Uc, uscmd);
	mutex_enter(SES_MUTEX);
	ssc->ses_sbufbsy = 0;
	cv_signal(&ssc->ses_sbufcv);
	mutex_exit(SES_MUTEX);

	return (err);
}



/*
 * Command start and done functions.
 */
static int
ses_start(struct buf *bp)
{
	ses_softc_t *ssc = (ses_softc_t *)bp->b_back;

	SES_LOG(ssc, SES_CE_DEBUG9, "ses_start");
	if (!BP_PKT(bp)) {
		/*
		 * Allocate a packet.
		 */
		ses_get_pkt(bp, SLEEP_FUNC);
		if (!BP_PKT(bp)) {
			int err;
			bp->b_resid = bp->b_bcount;
			if (geterror(bp) == 0)
				SET_BP_ERROR(bp, EIO);
			err = geterror(bp);
			biodone(bp);
			return (err);
		}
	}

	/*
	 * Initialize the transfer residue, error code, and retry count.
	 */
	bp->b_resid = 0;
	SET_BP_ERROR(bp, 0);

#if	!defined(lint)
	_NOTE(NO_COMPETING_THREADS_NOW);
#endif 	/* !defined(lint) */
	ssc->ses_retries = ses_retry_count;

#if	!defined(lint)
	/* LINTED */
	_NOTE(COMPETING_THREADS_NOW);
#endif	/* !defined(lint) */

	SES_LOG(ssc, SES_CE_DEBUG9, "ses_start -> scsi_transport");
	switch (scsi_transport(BP_PKT(bp))) {
	case TRAN_ACCEPT:
		return (0);
		/* break; */

	case TRAN_BUSY:
		SES_LOG(ssc, SES_CE_DEBUG2,
			"ses_start: TRANSPORT BUSY");
		SES_ENABLE_RESTART(SES_RESTART_TIME, BP_PKT(bp));
		return (0);
		/* break; */

	default:
		SES_LOG(ssc, SES_CE_DEBUG2, "TRANSPORT ERROR\n");
		SET_BP_ERROR(bp, EIO);
		scsi_destroy_pkt(BP_PKT(bp));
		SET_BP_PKT(bp, NULL);
		biodone(bp);
		return (EIO);
		/* break; */
	}
}


static void
ses_get_pkt(struct buf *bp, int (*func)())
{
	ses_softc_t *ssc = (ses_softc_t *)bp->b_back;
	Uscmd *scmd = &ssc->ses_uscsicmd;
	struct scsi_pkt *pkt;
	int stat_size;

	if ((scmd->uscsi_flags & USCSI_RQENABLE) && ssc->ses_arq) {
		stat_size = sizeof (struct scsi_arq_status);
	} else {
		stat_size = 1;
	}

	if (bp->b_bcount) {
		pkt = scsi_init_pkt(SES_ROUTE(ssc), NULL, bp,
		    scmd->uscsi_cdblen, stat_size, 0, 0, func, (caddr_t)ssc);
	} else {
		pkt = scsi_init_pkt(SES_ROUTE(ssc), NULL, NULL,
		    scmd->uscsi_cdblen, stat_size, 0, 0, func, (caddr_t)ssc);
	}
	SET_BP_PKT(bp, pkt);
	if (pkt == (struct scsi_pkt *)NULL)
		return;
	bcopy(scmd->uscsi_cdb, pkt->pkt_cdbp, (size_t)scmd->uscsi_cdblen);
	pkt->pkt_time = scmd->uscsi_timeout;

	pkt->pkt_comp = ses_callback;
	pkt->pkt_private = (opaque_t)ssc;
}


/*
 * Restart ses command.
 */
static void
ses_restart(void *arg)
{
	struct scsi_pkt *pkt = (struct scsi_pkt *)arg;
	ses_softc_t *ssc = (ses_softc_t *)pkt->pkt_private;
	struct buf *bp = ssc->ses_sbufp;
	SES_LOG(ssc, SES_CE_DEBUG9, "ses_restart");

	ssc->ses_restart_id = NULL;

	switch (scsi_transport(pkt)) {
	case TRAN_ACCEPT:
		SES_LOG(ssc, SES_CE_DEBUG9,
		    "RESTART %d ok", ssc->ses_retries);
		return;
		/* break; */
	case TRAN_BUSY:
		SES_LOG(ssc, SES_CE_DEBUG1,
		    "RESTART %d TRANSPORT BUSY\n", ssc->ses_retries);
		if (ssc->ses_retries > SES_NO_RETRY) {
		    ssc->ses_retries -= SES_BUSY_RETRY;
		    SES_ENABLE_RESTART(SES_RESTART_TIME, pkt);
		    return;
		}
		SET_BP_ERROR(bp, EBUSY);
		break;
	default:
		SET_BP_ERROR(bp, EIO);
		break;
	}
	SES_LOG(ssc, SES_CE_DEBUG1,
	    "RESTART %d TRANSPORT FAILED\n", ssc->ses_retries);

	pkt = (struct scsi_pkt *)bp->av_back;
	scsi_destroy_pkt(pkt);
	bp->b_resid = bp->b_bcount;
	SET_BP_PKT(bp, NULL);
	biodone(bp);
}


/*
 * Command completion processing
 */
#define	HBA_RESET	(STAT_BUS_RESET|STAT_DEV_RESET|STAT_ABORTED)
static void
ses_callback(struct scsi_pkt *pkt)
{
	ses_softc_t *ssc = (ses_softc_t *)pkt->pkt_private;
	struct buf *bp;
	Uscmd *scmd;
	int err;
	char action;

	bp = ssc->ses_sbufp;
	scmd = &ssc->ses_uscsicmd;
	/* SES_LOG(ssc, SES_CE_DEBUG9, "ses_callback"); */

	/*
	 * Optimization: Normal completion.
	 */
	if (pkt->pkt_reason == CMD_CMPLT &&
	    !SCBP_C(pkt) &&
	    !(pkt->pkt_flags & FLAG_SENSING) &&
	    !pkt->pkt_resid) {
	    scsi_destroy_pkt(pkt);
	    SET_BP_PKT(bp, NULL);
	    biodone(bp);
	    return;
	}


	/*
	 * Abnormal completion.
	 *
	 * Assume most common error initially.
	 */
	err = EIO;
	action = COMMAND_DONE;
	if (scmd->uscsi_flags & USCSI_DIAGNOSE) {
		ssc->ses_retries = SES_NO_RETRY;
	}

CHECK_PKT:
	if (pkt->pkt_reason != CMD_CMPLT) {
		/* Process transport errors. */
		switch (pkt->pkt_reason) {
		case CMD_TIMEOUT:
			/*
			 * If the transport layer didn't clear the problem,
			 * reset the target.
			 */
			if (! (pkt->pkt_statistics & HBA_RESET)) {
			    (void) scsi_reset(&pkt->pkt_address, RESET_TARGET);
			}
			err = ETIMEDOUT;
			break;

		case CMD_INCOMPLETE:
		case CMD_UNX_BUS_FREE:
			/*
			 * No response?  If probing, give up.
			 * Otherwise, keep trying until retries exhausted.
			 * Then lockdown the driver as the device is
			 * unplugged.
			 */
			if (ssc->ses_retries <= SES_NO_RETRY &&
			    !(scmd->uscsi_flags & USCSI_DIAGNOSE)) {
				ssc->ses_present = SES_CLOSED;
			}
			/* Inhibit retries to speed probe/attach. */
			if (ssc->ses_present < SES_OPEN) {
				ssc->ses_retries = SES_NO_RETRY;
			}
			/* SES_CMD_RETRY4(ssc->ses_retries); */
			err = ENXIO;
			break;

		case CMD_DATA_OVR:
			/*
			 * XXX:	Some HBA's (e.g. Adaptec 1740 and
			 *	earlier ISP revs) report a DATA OVERRUN
			 *	error instead of a transfer residue.  So,
			 *	we convert the error and restart.
			 */
			if ((bp->b_bcount - pkt->pkt_resid) > 0) {
				SES_LOG(ssc, SES_CE_DEBUG6,
					"ignoring overrun");
				pkt->pkt_reason = CMD_CMPLT;
				err = EOK;
				goto CHECK_PKT;
			}
			ssc->ses_retries = SES_NO_RETRY;
			/* err = EIO; */
			break;

		case CMD_DMA_DERR:
			ssc->ses_retries = SES_NO_RETRY;
			err = EFAULT;
			break;

		default:
			/* err = EIO; */
			break;
		}
		if (pkt == ssc->ses_rqpkt) {
			SES_LOG(ssc, CE_WARN, fail_msg,
				"Request Sense ",
				scsi_rname(pkt->pkt_reason),
				(ssc->ses_retries > 0)?
					"retrying": "giving up");
			pkt = (struct scsi_pkt *)bp->av_back;
			action = QUE_SENSE;
		} else {
			SES_LOG(ssc, CE_WARN, fail_msg,
				"", scsi_rname(pkt->pkt_reason),
				(ssc->ses_retries > 0)?
					"retrying": "giving up");
			action = QUE_COMMAND;
		}
		/* Device exists, allow full error recovery. */
		if (ssc->ses_retries > SES_NO_RETRY) {
			ssc->ses_present = SES_OPEN;
		}


	/*
	 * Process status and sense data errors.
	 */
	} else {
		ssc->ses_present = SES_OPEN;
		action = ses_decode_sense(pkt, &err);
	}


	/*
	 * Initiate error recovery action, as needed.
	 */
	switch (action) {
	case QUE_COMMAND_NOW:
		/* SES_LOG(ssc, SES_CE_DEBUG1, "retrying cmd now"); */
		if (ssc->ses_retries > SES_NO_RETRY) {
		    ssc->ses_retries -= SES_CMD_RETRY;
		    scmd->uscsi_status = 0;
		    if (ssc->ses_arq)
			bzero(pkt->pkt_scbp, sizeof (struct scsi_arq_status));

		    if (scsi_transport((struct scsi_pkt *)bp->av_back)
			!= TRAN_ACCEPT) {
			SES_ENABLE_RESTART(SES_RESTART_TIME,
				(struct scsi_pkt *)bp->av_back);
		    }
		    return;
		}
		break;

	case QUE_COMMAND:
		SES_LOG(ssc, SES_CE_DEBUG1, "retrying cmd");
		if (ssc->ses_retries > SES_NO_RETRY) {
		    ssc->ses_retries -=
			(err == EBUSY)? SES_BUSY_RETRY: SES_CMD_RETRY;
		    scmd->uscsi_status = 0;
		    if (ssc->ses_arq)
			bzero(pkt->pkt_scbp, sizeof (struct scsi_arq_status));

		    SES_ENABLE_RESTART(
			(err == EBUSY)? SES_BUSY_TIME: SES_RESTART_TIME,
			(struct scsi_pkt *)bp->av_back);
		    return;
		}
		break;

	case QUE_SENSE:
		SES_LOG(ssc, SES_CE_DEBUG1, "retrying sense");
		if (ssc->ses_retries > SES_NO_RETRY) {
			ssc->ses_retries -= SES_SENSE_RETRY;
			scmd->uscsi_status = 0;
			bzero(&ssc->ses_srqsbuf,
			    sizeof (struct scsi_extended_sense));

			if (scsi_transport(ssc->ses_rqpkt) != TRAN_ACCEPT) {
				SES_ENABLE_RESTART(SES_RESTART_TIME,
				    ssc->ses_rqpkt);
			}
		    return;
		}
		break;

	case COMMAND_DONE:
		SES_LOG(ssc, SES_CE_DEBUG4, "cmd done");
		pkt = (struct scsi_pkt *)bp->av_back;
		bp->b_resid = pkt->pkt_resid;
		if (bp->b_resid) {
		    SES_LOG(ssc, SES_CE_DEBUG6,
			"transfer residue %ld(%ld)",
			bp->b_bcount - bp->b_resid, bp->b_bcount);
		}
		break;
	}
	pkt = (struct scsi_pkt *)bp->av_back;
	if (err) {
		SES_LOG(ssc, SES_CE_DEBUG1, "SES: ERROR %d\n", err);
		SET_BP_ERROR(bp, err);
		bp->b_resid = bp->b_bcount;
	}
	scsi_destroy_pkt(pkt);
	SET_BP_PKT(bp, NULL);
	biodone(bp);
}


/*
 * Check status and sense data and determine recovery.
 */
static int
ses_decode_sense(struct scsi_pkt *pkt, int *err)
{
	ses_softc_t *ssc = (ses_softc_t *)pkt->pkt_private;
	struct	scsi_extended_sense *sense =
	    (struct scsi_extended_sense *)&ssc->ses_srqsbuf;
	Uscmd *scmd = &ssc->ses_uscsicmd;
	char sense_flag = 0;
	uchar_t status = SCBP_C(pkt) & STATUS_MASK;
	char *err_action;
	char action;

	/*
	 * Process manual request sense.
	 * Copy manual request sense to sense buffer.
	 *
	 * This is done if auto request sense is not enabled.
	 * Or the auto request sense failed and the request
	 * sense needs to be retried.
	 */
	if (pkt->pkt_flags & FLAG_SENSING) {
		struct buf *sbp = ssc->ses_rqbp;
		int amt = min(SENSE_LENGTH,
		    sbp->b_bcount - sbp->b_resid);

		bcopy(sbp->b_un.b_addr, sense, amt);
		scmd->uscsi_rqresid = scmd->uscsi_rqlen - amt;
		sense_flag = 1;

	/*
	 * Process auto request sense.
	 * Copy auto request sense to sense buffer.
	 *
	 * If auto request sense failed due to transport error,
	 * retry the command.  Otherwise process the status and
	 * sense data.
	 */
	} else if (ssc->ses_arq && pkt->pkt_state & STATE_ARQ_DONE) {
		struct scsi_arq_status *arq =
			(struct scsi_arq_status *)(pkt->pkt_scbp);
		int amt = min(sizeof (arq->sts_sensedata), SENSE_LENGTH);
		uchar_t *arq_status = (uchar_t *)&arq->sts_rqpkt_status;

		if (arq->sts_rqpkt_reason != CMD_CMPLT) {
			return (QUE_COMMAND);
		}
		bcopy(&arq->sts_sensedata, sense, amt);
		scmd->uscsi_status = status;
		scmd->uscsi_rqresid = scmd->uscsi_rqlen - amt;
		status = *arq_status & STATUS_MASK;
		pkt->pkt_state &= ~STATE_ARQ_DONE;
		sense_flag = 1;
	}


	/*
	 * Check status of REQUEST SENSE or command.
	 *
	 * If it's not successful, try retrying the original command
	 * and hope that it goes away.  If not, we'll eventually run
	 * out of retries and die.
	 */
	switch (status) {
	case STATUS_GOOD:
	case STATUS_INTERMEDIATE:
	case STATUS_MET:
		/*
		 * If the command status is ok, we're done.
		 * Otherwise, examine the request sense data.
		 */
		if (! sense_flag) {
			*err = EOK;
			return (COMMAND_DONE);
		}
		break;

	case STATUS_CHECK:
		SES_LOG(ssc, SES_CE_DEBUG3, "status decode: check");
		*err = EIO;
		return (QUE_SENSE);
		/* break; */

	case STATUS_BUSY:
		SES_LOG(ssc, SES_CE_DEBUG1, "status decode: busy");
		/* SES_CMD_RETRY2(ssc->ses_retries); */
		*err = EBUSY;
		return (QUE_COMMAND);
		/* break; */

	case STATUS_RESERVATION_CONFLICT:
		SES_LOG(ssc, SES_CE_DEBUG1, "status decode: reserved");
		*err = EACCES;
		return (COMMAND_DONE_ERROR);
		/* break; */

	case STATUS_TERMINATED:
		SES_LOG(ssc, SES_CE_DEBUG1, "status decode: terminated");
		*err = ECANCELED;
		return (COMMAND_DONE_ERROR);
		/* break; */

	default:
		SES_LOG(ssc, SES_CE_DEBUG1, "status 0x%x", status);
		*err = EIO;
		return (QUE_COMMAND);
		/* break; */
	}


	/*
	 * Check REQUEST SENSE error code.
	 *
	 * Either there's no error, a retryable error,
	 * or it's dead.  SES devices aren't very complex.
	 */
	err_action = "retrying";
	switch (sense->es_key) {
	case KEY_RECOVERABLE_ERROR:
		*err = EOK;
		err_action = "recovered";
		action = COMMAND_DONE;
		break;

	case KEY_UNIT_ATTENTION:
		/*
		 * This is common for RAID!
		 */
		/* *err = EIO; */
		SES_CMD_RETRY1(ssc->ses_retries);
		action = QUE_COMMAND_NOW;
		break;

	case KEY_NOT_READY:
	case KEY_NO_SENSE:
		/* *err = EIO; */
		action = QUE_COMMAND;
		break;

	default:
		/* *err = EIO; */
		err_action = "fatal";
		action = COMMAND_DONE_ERROR;
		break;
	}
	SES_LOG(ssc, SES_CE_DEBUG1,
		"cdb[0]= 0x%x %s,  key=0x%x, ASC/ASCQ=0x%x/0x%x",
		scmd->uscsi_cdb[0], err_action,
		sense->es_key, sense->es_add_code, sense->es_qual_code);

#ifdef 	not
	/*
	 * Dump cdb and sense data stat's for manufacturing.
	 */
	if (DEBUGGING_ERR || sd_error_level == SDERR_ALL) {
		auto buf[128];

		p = pkt->pkt_cdbp;
		if ((j = scsi_cdb_size[CDB_GROUPID(*p)]) == 0)
			j = CDB_SIZE;

		/* Print cdb */
		(void) sprintf(buf, "cmd:");
		for (i = 0; i < j; i++) {
			(void) sprintf(&buf[strlen(buf)],
			    hex, (uchar_t)*p++);
		}
		SES_LOG(ssc, SES_CE_DEBUG3, "%s", buf);

		/* Suppress trailing zero's in sense data */
		if (amt > 3) {
			p = (char *)devp->sd_sense + amt;
			for (j = amt; j > 3; j--) {
				if (*(--p))  break;
			}
		} else {
			j = amt;
		}

		/* Print sense data. */
		(void) sprintf(buf, "sense:");
		p = (char *)devp->sd_sense;
		for (i = 0; i < j; i++) {
			(void) sprintf(&buf[strlen(buf)],
			    hex, (uchar_t)*p++);
		}
		SES_LOG(ssc, SES_CE_DEBUG3, "%s", buf);
	}
#endif 	/* not */
	return (action);
}


/*PRINTFLIKE3*/
void
ses_log(ses_softc_t *ssc, int level, const char *fmt, ...)
{
	va_list	ap;
	char buf[256];

	va_start(ap, fmt);
	(void) vsprintf(buf, fmt, ap);
	va_end(ap);

	if (ssc == (ses_softc_t *)NULL) {
		switch (level) {
		case SES_CE_DEBUG1:
			if (ses_debug > 1)
				cmn_err(CE_NOTE, "%s", buf);
			break;
		case SES_CE_DEBUG2:
			if (ses_debug > 2)
				cmn_err(CE_NOTE, "%s", buf);
			break;
		case SES_CE_DEBUG3:
			if (ses_debug > 3)
				cmn_err(CE_NOTE, "%s", buf);
			break;
		case SES_CE_DEBUG4:
			if (ses_debug > 4)
				cmn_err(CE_NOTE, "%s", buf);
			break;
		case SES_CE_DEBUG5:
			if (ses_debug > 5)
				cmn_err(CE_NOTE, "%s", buf);
			break;
		case SES_CE_DEBUG6:
			if (ses_debug > 6)
				cmn_err(CE_NOTE, "%s", buf);
			break;
		case SES_CE_DEBUG7:
			if (ses_debug > 7)
				cmn_err(CE_NOTE, "%s", buf);
			break;
		case SES_CE_DEBUG8:
			if (ses_debug > 8)
				cmn_err(CE_NOTE, "%s", buf);
			break;
		case SES_CE_DEBUG9:
			if (ses_debug > 9)
				cmn_err(CE_NOTE, "%s", buf);
			break;
		case CE_NOTE:
		case CE_WARN:
		case CE_PANIC:
			cmn_err(level, "%s", buf);
			break;
		case SES_CE_DEBUG:
		default:
			cmn_err(CE_NOTE, "%s", buf);
		break;
		}
		return;
	}

	switch (level) {
	case CE_CONT:
	case CE_NOTE:
	case CE_WARN:
	case CE_PANIC:
		scsi_log(SES_DEVINFO(ssc), (char *)Snm, level, Str, buf);
		break;
	case SES_CE_DEBUG1:
		if (ses_debug > 1)
			scsi_log(SES_DEVINFO(ssc), (char *)Snm, SCSI_DEBUG,
			    Str, buf);
		break;
	case SES_CE_DEBUG2:
		if (ses_debug > 2)
			scsi_log(SES_DEVINFO(ssc), (char *)Snm, SCSI_DEBUG,
			    Str, buf);
		break;
	case SES_CE_DEBUG3:
		if (ses_debug > 3)
			scsi_log(SES_DEVINFO(ssc), (char *)Snm, SCSI_DEBUG,
			    Str, buf);
		break;
	case SES_CE_DEBUG4:
		if (ses_debug > 4)
			scsi_log(SES_DEVINFO(ssc), (char *)Snm, SCSI_DEBUG,
			    Str, buf);
		break;
	case SES_CE_DEBUG5:
		if (ses_debug > 5)
			scsi_log(SES_DEVINFO(ssc), (char *)Snm, SCSI_DEBUG,
			    Str, buf);
		break;
	case SES_CE_DEBUG6:
		if (ses_debug > 6)
			scsi_log(SES_DEVINFO(ssc), (char *)Snm, SCSI_DEBUG,
			    Str, buf);
		break;
	case SES_CE_DEBUG7:
		if (ses_debug > 7)
			scsi_log(SES_DEVINFO(ssc), (char *)Snm, SCSI_DEBUG,
			    Str, buf);
		break;
	case SES_CE_DEBUG8:
		if (ses_debug > 8)
			scsi_log(SES_DEVINFO(ssc), (char *)Snm, SCSI_DEBUG,
			    Str, buf);
		break;
	case SES_CE_DEBUG9:
		if (ses_debug > 9)
			scsi_log(SES_DEVINFO(ssc), (char *)Snm, SCSI_DEBUG,
			    Str, buf);
		break;
	case SES_CE_DEBUG:
	default:
		scsi_log(SES_DEVINFO(ssc), (char *)Snm, SCSI_DEBUG, Str, buf);
		break;
	}
}
/*
 * 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:
 */