root/usr/src/uts/common/io/scsi/targets/ses.c
/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright (c) 2011 Bayard G. Bell. All rights reserved.
 * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
 * Copyright 2020 RackTop Systems, Inc.
 */

#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 void ses_register_dev_id(ses_softc_t *, dev_info_t *);

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
 */
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  */
        D_MP | D_NEW | D_HOTPLUG,       /* Driver compatibility flag */
        CB_REV,                         /* cb_ops version number */
        nodev,                          /* aread */
        nodev                           /* awrite */
};

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 */
        NULL,                   /* bus operations */
        NULL,                   /* power */
        ddi_quiesce_not_needed,         /* quiesce */
};

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.
 */

static struct modldrv modldrv = {
        &mod_driverops,
        "SCSI Enclosure Services",
        &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);
        /* 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);

        /* Legacy: prevent driver.conf specified ses nodes on atapi. */
        if (scsi_ifgetcap(&devp->sd_address, "interconnect-type", -1) ==
            INTERCONNECT_ATAPI)
                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);
        }

        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) {
                        /*
                         * Per SPC4 #6.4.2 Standard Inquiry Data, response
                         * data format (RDF) values of 0 and 1 are Obsolete,
                         * whereas values greater than 2 are Reserved
                         */
                        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, 0);
        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, MAX_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] = MAX_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);
        }

        /*
         * Register a device id.  This also arranges for the
         * serial number property to be set.
         */
        ses_register_dev_id(ssc, dip);

        /*
         * 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) {
                        dev_err(dip, CE_NOTE, "DDI_DETACH, no softstate");
                        return (DDI_FAILURE);
                }
                if (ISOPEN(ssc)) {
                        return (DDI_FAILURE);
                }

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

                if (ssc->ses_dev_id) {
                        ddi_devid_unregister(dip);
                        ddi_devid_free(ssc->ses_dev_id);
                }
                (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) {
                        dev_err(dip, CE_NOTE, "DDI_SUSPEND, no softstate");
                        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:
                if (drv_priv(cred_p) != 0) {
                        rv = EPERM;
                        break;
                }
                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 (drv_priv(cred_p) != 0) {
                        rv = EPERM;
                        break;
                }
                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 (drv_priv(cred_p) != 0) {
                        rv = EPERM;
                        break;
                }
                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:
                if (drv_priv(cred_p) != 0) {
                        rv = EPERM;
                        break;
                }
                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);

        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));
        }
        ssc->ses_uscsicmd.uscsi_status = 0;

        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);

        ssc->ses_retries = ses_retry_count;

        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 = 1;
        int flags = 0;

        if ((scmd->uscsi_flags & USCSI_RQENABLE) && ssc->ses_arq) {
                if (scmd->uscsi_rqlen > SENSE_LENGTH) {
                        stat_size = (int)(scmd->uscsi_rqlen) +
                            sizeof (struct scsi_arq_status) -
                            sizeof (struct scsi_extended_sense);
                        flags = PKT_XARQ;
                } else {
                        stat_size = sizeof (struct scsi_arq_status);
                }
        }

        if (bp->b_bcount) {
                pkt = scsi_init_pkt(SES_ROUTE(ssc), NULL, bp,
                    scmd->uscsi_cdblen, stat_size, 0, flags,
                    func, (caddr_t)ssc);
        } else {
                pkt = scsi_init_pkt(SES_ROUTE(ssc), NULL, NULL,
                    scmd->uscsi_cdblen, stat_size, 0, flags,
                    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);

        /* Set an upper bound timeout of ses_io_time if zero is passed in */
        pkt->pkt_time = (scmd->uscsi_timeout == 0) ?
            ses_io_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) {
                        clock_t ms_time;

                        ms_time =
                            (err == EBUSY)? SES_BUSY_TIME : SES_RESTART_TIME;
                        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(ms_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, MAX_SENSE_LENGTH);

                        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;
        uchar_t rqlen;
        int amt;

        /*
         * 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;
                amt = min(MAX_SENSE_LENGTH,
                    sbp->b_bcount - sbp->b_resid);
                rqlen = min((uchar_t)amt, scmd->uscsi_rqlen);
                bcopy(sbp->b_un.b_addr, sense, rqlen);
                scmd->uscsi_rqresid = scmd->uscsi_rqlen - rqlen;
                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);
                uchar_t *arq_status = (uchar_t *)&arq->sts_rqpkt_status;
                if (pkt->pkt_state & STATE_XARQ_DONE) {
                        amt = MAX_SENSE_LENGTH - arq->sts_rqpkt_resid;
                } else {
                        if (arq->sts_rqpkt_resid > SENSE_LENGTH) {
                                amt = MAX_SENSE_LENGTH - arq->sts_rqpkt_resid;
                        } else {
                                amt = SENSE_LENGTH - arq->sts_rqpkt_resid;
                        }
                }

                if (arq->sts_rqpkt_reason != CMD_CMPLT) {
                        return (QUE_COMMAND);
                }

                rqlen = min((uchar_t)amt, scmd->uscsi_rqlen);
                bcopy(&arq->sts_sensedata, sense, rqlen);
                scmd->uscsi_status = status;
                scmd->uscsi_rqresid = scmd->uscsi_rqlen - rqlen;
                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);

        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;
        }
}

static size_t
ses_get_vpd_page(ses_softc_t *ssc, uint8_t page, uint8_t *buf, uint16_t size)
{
        Uscmd   uc;
        uint8_t cdb[6];
        int     rv;
        int     try = 0;

        for (try = 0; try < 3; try++) {
                cdb[0] = SCMD_INQUIRY;
                cdb[1] = 0x01; /* EVPD */
                cdb[2] = page;
                cdb[3] = (size >> 8);
                cdb[4] = (size & 0xff);
                cdb[5] = 0;

                /* First we get the header, so we know the size to retrieve */
                bzero(&uc, sizeof (uc));
                uc.uscsi_cdb            = (char *)cdb;
                uc.uscsi_cdblen         = CDB_GROUP0;
                uc.uscsi_bufaddr        = (char *)buf;
                uc.uscsi_buflen         = size;
                uc.uscsi_rqbuf          = NULL;
                uc.uscsi_rqlen          = 0;
                uc.uscsi_flags          = USCSI_READ | USCSI_SILENT;
                uc.uscsi_timeout        = ses_io_time;
                uc.uscsi_status         = 0;

                if ((rv = ses_uscsi_cmd(ssc, &uc, FKIOCTL)) == 0) {
                        return (size - uc.uscsi_resid);
                }
        }

        ses_log(ssc, CE_WARN, "failed getting page header %x: %d", page, rv);
        return (0);
}

/*
 * No devices ever need more than a full 255 bytes for VPD.
 * page 83.  (Page 0 and 80 are limited to 255 by definition.)
 * An extra byte is added for termination and alignment.
 */
#define SES_VPD_SIZE    0x100

static void
ses_register_dev_id(ses_softc_t *ssc, dev_info_t *dip)
{
        int             rv;
        int             i;
        uint8_t         *inq;
        size_t          len80 = 0;
        size_t          len83 = 0;
        size_t          len00;
        uint8_t         *inq80;
        uint8_t         *inq83;
        uint8_t         *inq00;
        ddi_devid_t     dev_id;

        inq = (void *)ssc->ses_devp->sd_inq;

        inq00 = kmem_zalloc(SES_VPD_SIZE, KM_SLEEP);
        inq80 = kmem_zalloc(SES_VPD_SIZE, KM_SLEEP);
        inq83 = kmem_zalloc(SES_VPD_SIZE, KM_SLEEP);

        /*
         * Inquiry needs up to 255 plus 4 bytes of header.
         * We also leave an extra byte on the end so we can
         * ASCIIZ terminate the serial number.
         */
        len00 = ses_get_vpd_page(ssc, 0x00, inq00, SES_VPD_SIZE - 1);
        if (len00 <= 4) {
                /* No VPD pages, nothing we can do */
                goto done;
        }
        len00 -= 4;                     /* skip the header */
        len00 = min(len00, inq00[3]);   /* limit to page length */
        for (i = 0; i < len00; i++) {
                switch (inq00[i+4]) {
                case 0x80:
                        len80 = ses_get_vpd_page(ssc, 0x80, inq80,
                            SES_VPD_SIZE - 1);
                        break;
                case 0x83:
                        len83 = ses_get_vpd_page(ssc, 0x83, inq83,
                            SES_VPD_SIZE - 1);
                        break;
                }
        }

        /*
         * Create "inquiry-serial-number" property if not already present.
         * This is from page 0x80, which may or may not be present.
         */
        if ((len80 > 4) &&
            (!ddi_prop_exists(DDI_DEV_T_NONE, dip,
            DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, INQUIRY_SERIAL_NO))) {
                char *sn;

                /* SPC-3 7.6.10 */
                sn = (char *)&inq80[4];
                /* ensure ASCIIZ */
                inq80[0xff] = '\0';

                /* Serial num is right justified, left padded with spaces. */
                while (*sn == ' ') {
                        sn++;
                }
                if (*sn != '\0') {
                        (void) ddi_prop_update_string(DDI_DEV_T_NONE,
                            dip, INQUIRY_SERIAL_NO, sn);
                }
        }

        if ((rv = ddi_devid_get(dip, &dev_id)) == DDI_SUCCESS) {
                ddi_devid_free(dev_id);
                goto done;
        }

        rv = ddi_devid_scsi_encode(DEVID_SCSI_ENCODE_VERSION_LATEST,
            (char *)ddi_driver_name(dip), inq, SUN_INQSIZE,
            inq80, len80, inq83, len83, &ssc->ses_dev_id);

        if (rv != DDI_SUCCESS) {
                ses_log(ssc, CE_WARN, "failed to encode device id");
                goto done;
        }

        rv = ddi_devid_register(dip, ssc->ses_dev_id);
        if (rv != DDI_SUCCESS) {
                ddi_devid_free(ssc->ses_dev_id);
                ssc->ses_dev_id = NULL;
                ses_log(ssc, CE_WARN, "failed to register device id");
        }

done:
        /* clean up pages */
        kmem_free(inq83, SES_VPD_SIZE);
        kmem_free(inq80, SES_VPD_SIZE);
        kmem_free(inq00, SES_VPD_SIZE);
}


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