root/usr/src/uts/common/io/scsi/conf/scsi_confsubr.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
 */
/*
 * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Utility SCSI configuration routines
 */
/*
 * Many routines in this file have built in parallel bus assumption
 * which might need to change as other interconnect evolve.
 */

#include <sys/scsi/scsi.h>
#include <sys/modctl.h>
#include <sys/bitmap.h>
#include <sys/fm/protocol.h>

/*
 * macro for filling in lun value for scsi-1 support
 */

#define FILL_SCSI1_LUN(sd, pkt) \
        if ((sd->sd_address.a_lun > 0) && \
            (sd->sd_inq->inq_ansi == 0x1)) { \
                ((union scsi_cdb *)(pkt)->pkt_cdbp)->scc_lun = \
                    sd->sd_address.a_lun; \
        }

extern struct mod_ops mod_miscops;

static struct modlmisc modlmisc = {
        &mod_miscops,   /* Type of module */
        "SCSI Bus Utility Routines"
};

static struct modlinkage modlinkage = {
        MODREV_1, (void *)&modlmisc, NULL
};

/*
 * Contexts from which we call scsi_test
 */
enum scsi_test_ctxt {
        /*
         * Those in scsi_hba_probe_pi()
         */
        STC_PROBE_FIRST_INQ,
        STC_PROBE_FIRST_INQ_RETRY,
        STC_PROBE_PARTIAL_SUCCESS,
        STC_PROBE_RQSENSE1,
        STC_PROBE_CHK_CLEARED,
        STC_PROBE_RQSENSE2,
        STC_PROBE_INQ_FINAL,
        /*
         * Those in check_vpd_page_support8083()
         */
        STC_VPD_CHECK,
        /*
         * Those in scsi_device_identity()
         */
        STC_IDENTITY_PG80,
        STC_IDENTITY_PG83,
};

static void create_inquiry_props(struct scsi_device *);

static int scsi_check_ss2_LUN_limit(struct scsi_device *);
static void scsi_establish_LUN_limit(struct scsi_device *);
static void scsi_update_parent_ss2_prop(dev_info_t *, int, int);

static int check_vpd_page_support8083(struct scsi_device *sd,
                int (*callback)(), int *, int *);
static int send_scsi_INQUIRY(struct scsi_device *sd,
                int (*callback)(), uchar_t *bufaddr, size_t buflen,
                uchar_t evpd, uchar_t page_code, size_t *lenp,
                enum scsi_test_ctxt);

/*
 * this int-array HBA-node property keeps track of strictly SCSI-2
 * target IDs
 */
#define SS2_LUN0_TGT_LIST_PROP  "ss2-targets"

/*
 * for keeping track of nodes for which we do *NOT* want to probe above LUN 7
 * (i.e. strict SCSI-2 targets)
 *
 * note that we could also keep track of dtype (SCSI device type) and
 * ANSI (SCSI standard conformance level), but all currently-known cases of
 * this problem are on SCSI-2 PROCESSOR device types
 */
typedef struct ss2_lun0_info {
        const char      *sli_vid;       /* SCSI inquiry VID */
        const char      *sli_pid;       /* SCSI inquiry PID */
        const char      *sli_rev;       /* SCSI inquiry REV */
} ss2_lun0_info_t;

/*
 * these two workarounds are for the SCSI-2 GEM2* chips used in the
 * D1000 and D240
 */
#define SES_D1000_VID           "SYMBIOS"
#define SES_D1000_PID           "D1000"         /* the D1000 */
#define SES_D1000_REV           "2"

#define SES_D240_VID            "SUN"
#define SES_D240_PID            "D240"          /* the D240 */
#define SES_D240_REV            "2"

/*
 * a static list of targets where we do *not* want to probe above LUN 7
 */
static const ss2_lun0_info_t    scsi_probe_strict_s2_list[] = {
        {SES_D1000_VID, SES_D1000_PID, SES_D1000_REV},
        {SES_D240_VID, SES_D240_PID, SES_D240_REV},
};

static const int                scsi_probe_strict_s2_size =
        sizeof (scsi_probe_strict_s2_list) / sizeof (struct ss2_lun0_info);


#ifdef  DEBUG

int     scsi_probe_debug = 0;

#define SCSI_PROBE_DEBUG0(l, s)         \
                if (scsi_probe_debug >= (l)) printf(s)
#define SCSI_PROBE_DEBUG1(l, s, a1)     \
                if (scsi_probe_debug >= (l)) printf(s, a1)
#define SCSI_PROBE_DEBUG2(l, s, a1, a2) \
                if (scsi_probe_debug >= (l)) printf(s, a1, a2)
#define SCSI_PROBE_DEBUG3(l, s, a1, a2, a3)     \
                if (scsi_probe_debug >= (l)) printf(s, a1, a2, a3)

#else   /* DEBUG */

#define SCSI_PROBE_DEBUG0(l, s)
#define SCSI_PROBE_DEBUG1(l, s, a1)
#define SCSI_PROBE_DEBUG2(l, s, a1, a2)
#define SCSI_PROBE_DEBUG3(l, s, a1, a2, a3)

#endif  /* DEBUG */

int     scsi_test_busy_timeout = SCSI_POLL_TIMEOUT;     /* in seconds */
int     scsi_test_busy_delay = 10000;                   /* 10msec in usec */


/*
 * Returns from scsi_test.
 *
 * SCSI_TEST_CMPLT_GOOD => TRAN_ACCEPT, CMD_CMPLT, STATUS_GOOD
 *
 * SCSI_TEST_CMPLT_BUSY => TRAN_ACCEPT, CMD_CMPLT, STATUS_BUSY
 *
 * SCSI_TEST_CMPLT_CHECK => TRAN_ACCEPT, CMD_CMPLT, STATUS_CHECK
 *
 * SCSI_TEST_CMPLT_OTHER => TRAN_ACCEPT, CMD_CMPLT, !STATUS_{GOOD,BUSY,CHECK}
 *
 * SCSI_TEST_CMD_INCOMPLETE => TRAN_ACCEPT, CMD_INCOMPLETE
 *
 * SCSI_TEST_NOTCMPLT => TRAN_ACCEPT, pkt_reason != CMD_{CMPLT,INCOMPLETE}
 *
 * SCSI_TEST_TRAN_BUSY => (Repeated) TRAN_BUSY from attempt scsi_transport
 *
 * SCSI_TEST_TRAN_REJECT => TRAN_BADPKT or TRAN_FATAL_ERROR
 *
 */
#define SCSI_TEST_CMPLT_GOOD            0x01U
#define SCSI_TEST_CMPLT_BUSY            0x02U
#define SCSI_TEST_CMPLT_CHECK           0x04U
#define SCSI_TEST_CMPLT_OTHER           0x08U

#define SCSI_TEST_CMPLTMASK \
        (SCSI_TEST_CMPLT_GOOD | SCSI_TEST_CMPLT_BUSY | \
        SCSI_TEST_CMPLT_CHECK | SCSI_TEST_CMPLT_OTHER)

#define SCSI_TEST_PARTCMPLTMASK \
        (SCSI_TEST_CMPLTMASK & ~SCSI_TEST_CMPLT_GOOD)

#define SCSI_TEST_CMD_INCOMPLETE        0x10U
#define SCSI_TEST_NOTCMPLT              0x20U
#define SCSI_TEST_TRAN_BUSY             0x40U
#define SCSI_TEST_TRAN_REJECT           0x80U

#define SCSI_TEST_FAILMASK \
        (SCSI_TEST_CMD_INCOMPLETE | SCSI_TEST_NOTCMPLT | \
        SCSI_TEST_TRAN_BUSY | SCSI_TEST_TRAN_REJECT)

#define SCSI_TEST_FAILURE(x) (((x) & SCSI_TEST_FAILMASK) != 0)

/*
 * architecture dependent allocation restrictions. For x86, we'll set
 * dma_attr_addr_hi to scsi_max_phys_addr and dma_attr_sgllen to
 * scsi_sgl_size during _init().
 */
#if defined(__sparc)
ddi_dma_attr_t scsi_alloc_attr = {
        DMA_ATTR_V0,    /* version number */
        0x0,            /* lowest usable address */
        0xFFFFFFFFull,  /* high DMA address range */
        0xFFFFFFFFull,  /* DMA counter register */
        1,              /* DMA address alignment */
        1,              /* DMA burstsizes */
        1,              /* min effective DMA size */
        0xFFFFFFFFull,  /* max DMA xfer size */
        0xFFFFFFFFull,  /* segment boundary */
        1,              /* s/g list length */
        512,            /* granularity of device */
        0               /* DMA transfer flags */
};
#elif defined(__x86)
ddi_dma_attr_t scsi_alloc_attr = {
        DMA_ATTR_V0,    /* version number */
        0x0,            /* lowest usable address */
        0x0,            /* high DMA address range [set in _init()] */
        0xFFFFull,      /* DMA counter register */
        1,              /* DMA address alignment */
        1,              /* DMA burstsizes */
        1,              /* min effective DMA size */
        0xFFFFFFFFull,  /* max DMA xfer size */
        0xFFFFFFFFull,  /* segment boundary */
        0,              /* s/g list length */
        512,            /* granularity of device [set in _init()] */
        0               /* DMA transfer flags */
};
uint64_t scsi_max_phys_addr = 0xFFFFFFFFull;
int scsi_sgl_size = 0xFF;
#endif

ulong_t *scsi_pkt_bad_alloc_bitmap;

int
_init()
{
        scsi_initialize_hba_interface();
        scsi_watch_init();

#if defined(__x86)
        /* set the max physical address for iob allocs on x86 */
        scsi_alloc_attr.dma_attr_addr_hi = scsi_max_phys_addr;

        /*
         * set the sgllen for iob allocs on x86. If this is set less than
         * the number of pages the buffer will take (taking into account
         * alignment), it would force the allocator to try and allocate
         * contiguous pages.
         */
        scsi_alloc_attr.dma_attr_sgllen = scsi_sgl_size;
#endif

        /* bitmap to limit scsi_pkt allocation violation messages */
        scsi_pkt_bad_alloc_bitmap = kmem_zalloc(BT_SIZEOFMAP(devcnt), KM_SLEEP);

        return (mod_install(&modlinkage));
}

/*
 * there is no _fini() routine because this module is never unloaded
 */

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

#define ROUTE   (&sd->sd_address)

static int
scsi_slave_do_rqsense(struct scsi_device *sd, int (*callback)())
{
        struct scsi_pkt *rq_pkt = NULL;
        struct buf *rq_bp = NULL;
        int rval = SCSIPROBE_EXISTS;

        /*
         * prepare rqsense packet
         */
        rq_bp = scsi_alloc_consistent_buf(ROUTE, (struct buf *)NULL,
            (uint_t)SENSE_LENGTH, B_READ, callback, NULL);
        if (rq_bp == NULL) {
                rval = SCSIPROBE_NOMEM;
                goto out;
        }

        rq_pkt = scsi_init_pkt(ROUTE, (struct scsi_pkt *)NULL,
            rq_bp, CDB_GROUP0, 1, 0, PKT_CONSISTENT,
            callback, NULL);

        if (rq_pkt == NULL) {
                if (rq_bp->b_error == 0)
                        rval = SCSIPROBE_NOMEM_CB;
                else
                        rval = SCSIPROBE_NOMEM;
                goto out;
        }
        ASSERT(rq_bp->b_error == 0);

        (void) scsi_setup_cdb((union scsi_cdb *)rq_pkt->
            pkt_cdbp, SCMD_REQUEST_SENSE, 0, SENSE_LENGTH, 0);
        FILL_SCSI1_LUN(sd, rq_pkt);
        rq_pkt->pkt_flags = FLAG_NOINTR|FLAG_NOPARITY|FLAG_SENSING;

        /*
         * The controller type is as yet unknown, so we
         * have to do a throwaway non-extended request sense,
         * and hope that that clears the check condition
         * for that unit until we can find out what kind
         * of drive it is. A non-extended request sense
         * is specified by stating that the sense block
         * has 0 length, which is taken to mean that it
         * is four bytes in length.
         */
        if (scsi_poll(rq_pkt) < 0) {
                rval = SCSIPROBE_FAILURE;
        }

out:
        if (rq_pkt) {
                scsi_destroy_pkt(rq_pkt);
        }
        if (rq_bp) {
                scsi_free_consistent_buf(rq_bp);
        }

        return (rval);
}

/*
 *
 * SCSI slave probe routine - provided as a service to target drivers
 *
 * Mostly attempts to allocate and fill sd inquiry data..
 */

int
scsi_slave(struct scsi_device *sd, int (*callback)())
{
        struct scsi_pkt *pkt;
        int             rval = SCSIPROBE_EXISTS;

        /*
         * the first test unit ready will tell us whether a target
         * responded and if there was one, it will clear the unit attention
         * condition
         */
        pkt = scsi_init_pkt(ROUTE, (struct scsi_pkt *)NULL, NULL,
            CDB_GROUP0, sizeof (struct scsi_arq_status), 0, 0, callback, NULL);

        if (pkt == NULL) {
                return (SCSIPROBE_NOMEM_CB);
        }

        (void) scsi_setup_cdb((union scsi_cdb *)pkt->pkt_cdbp,
            SCMD_TEST_UNIT_READY, 0, 0, 0);
        FILL_SCSI1_LUN(sd, pkt);
        pkt->pkt_flags = FLAG_NOINTR|FLAG_NOPARITY;

        if (scsi_poll(pkt) < 0) {
                if (pkt->pkt_reason == CMD_INCOMPLETE)
                        rval = SCSIPROBE_NORESP;
                else
                        rval = SCSIPROBE_FAILURE;

                if ((pkt->pkt_state & STATE_ARQ_DONE) == 0) {
                        if (((struct scsi_status *)pkt->pkt_scbp)->sts_chk)
                                /*
                                 * scanner and processor devices can return a
                                 * check condition here
                                 */
                                rval = scsi_slave_do_rqsense(sd, callback);
                }

                if (rval != SCSIPROBE_EXISTS) {
                        scsi_destroy_pkt(pkt);
                        return (rval);
                }
        }

        /*
         * the second test unit ready, allows the host adapter to negotiate
         * synchronous transfer period and offset
         */
        if (scsi_poll(pkt) < 0) {
                if (pkt->pkt_reason == CMD_INCOMPLETE)
                        rval = SCSIPROBE_NORESP;
                else
                        rval = SCSIPROBE_FAILURE;
        }

        /*
         * do a rqsense if there was a check condition and ARQ was not done
         */
        if ((pkt->pkt_state & STATE_ARQ_DONE) == 0) {
                if (((struct scsi_status *)pkt->pkt_scbp)->sts_chk) {
                        rval = scsi_slave_do_rqsense(sd, callback);
                }
        }

        /*
         * call scsi_probe to do the inquiry
         *
         * NOTE: there is minor difference with the old scsi_slave
         * implementation: busy conditions are not handled in scsi_probe.
         */
        scsi_destroy_pkt(pkt);
        if (rval == SCSIPROBE_EXISTS) {
                return (scsi_probe(sd, callback));
        } else {
                return (rval);
        }
}

/*
 * Undo scsi_slave - older interface, but still supported
 *
 * NOTE: The 'sd_inq' inquiry data is now freed by scsi_hba/scsi_vhci code
 * as part of free of scsi_device(9S).
 */
/*ARGSUSED*/
void
scsi_unslave(struct scsi_device *sd)
{
}

/*
 * Undo scsi_probe
 *
 * NOTE: The 'sd_inq' inquiry data is now freed by scsi_hba/scsi_vhci code
 * as part of free of scsi_device(9S).
 */
/*ARGSUSED*/
void
scsi_unprobe(struct scsi_device *sd)
{
}

/*
 * We log all scsi_test failures (as long as we are SE_HP etc).  The
 * following table controls the "driver-assessment" payload item
 * in the ereports we raise.  If a scsi_test return features in the
 * retry mask then the calling context will retry; if it features in
 * the fatal mask then the caller will not retry (although higher-level
 * software might); if in neither (which shouldn't happen - you either
 * retry or give up) default to 'retry'.
 */
static const struct scsi_test_profile {
        enum scsi_test_ctxt stp_ctxt;   /* Calling context */
        uint32_t stp_retrymask;         /* Returns caller will retry for */
        uint32_t stp_fatalmask;         /* Returns caller considers fatal */
} scsi_test_profile[] = {
        /*
         * This caller will retry on SCSI_TEST_FAILMASK as long as it was
         * not SCSI_TEST_CMD_INCOMPLETE which is terminal.  A return from
         * SCSI_TEST_PARTCMPLTMASK (command complete but status other than
         * STATUS_GOOD) is not terminal and we'll move on to the context
         * of STC_PROBE_PARTIAL_SUCCESS so that's a retry, too.
         */
        {
                STC_PROBE_FIRST_INQ,
                SCSI_TEST_FAILMASK & ~SCSI_TEST_CMD_INCOMPLETE |
                    SCSI_TEST_PARTCMPLTMASK,
                SCSI_TEST_CMD_INCOMPLETE
        },

        /*
         * If the first inquiry fails outright we always retry just once
         * (except for SCSI_TEST_CMD_INCOMPLETE as above).  A return in
         * SCSI_TEST_FAILMASK is terminal; for SCSI_TEST_PARTCMPLTMASK
         * we will retry at STC_PROBE_PARTIAL_SUCCESS.
         */
        {
                STC_PROBE_FIRST_INQ_RETRY,
                SCSI_TEST_PARTCMPLTMASK,
                SCSI_TEST_FAILMASK
        },

        /*
         * If we've met with partial success we retry at caller context
         * STC_PROBE_PARTIAL_SUCCESS.  Any SCSI_TEST_FAILMASK return
         * here is terminal, as too is SCSI_TEST_CMPLT_BUSY.  A return in
         * SCSI_TEST_PARTCMPLTMASK and we will continue with further
         * inquiry attempts.
         */
        {
                STC_PROBE_PARTIAL_SUCCESS,
                SCSI_TEST_PARTCMPLTMASK & ~SCSI_TEST_CMPLT_BUSY,
                SCSI_TEST_FAILMASK | SCSI_TEST_CMPLT_BUSY
        },

        /*
         * If we get past the above target busy case then we will
         * perform a sense request if scsi_test indicates STATUS_CHECK
         * and ARQ was not done.  We are not interested in logging telemetry
         * for transports that do not perform ARQ automatically.
         */
        {
                STC_PROBE_RQSENSE1,
                0,
                0
        },

        /*
         * If "something" responded to the probe but then the next inquiry
         * sees a change of heart then we fail the probe on any of
         * SCSI_TEST_FAILMASK or SCSI_TEST_CMPLT_BUSY.  For other values
         * in SCSI_TEST_PARTCMPLTMASK we soldier on.
         */
        {
                STC_PROBE_CHK_CLEARED,
                SCSI_TEST_PARTCMPLTMASK & ~SCSI_TEST_CMPLT_BUSY,
                SCSI_TEST_FAILMASK | SCSI_TEST_CMPLT_BUSY
        },

        /*
         * If after all that there we still have STATUS_CHECK from the
         * inquiry status then we resend the sense request but the
         * result is ignored (just clearing the condition).  Do not
         * log.
         */
        {
                STC_PROBE_RQSENSE2,
                0,
                0
        },

        /*
         * After the above sense request we once again send an inquiry.
         * If it fails outright or STATUS_CHECK persists we give up.
         * Any partial result is considered success.
         */
        {
                STC_PROBE_INQ_FINAL,
                0,
                SCSI_TEST_FAILMASK | SCSI_TEST_CMPLT_CHECK
        },

        /*
         * check_vpd_page_support8083 called from scsi_device_identity
         * performs an inquiry with EVPD set (and page necessarily 0)
         * to see what pages are supported.
         *
         * Some devices do not support this command and therefore
         * check_vpd_page_support8083 only returns an error of kmem_zalloc
         * fails.  If the send_scsi_INQUIRY does not meet with complete
         * success (SCSI_TEST_CMPLT_GOOD) it returns -1, othewise 0.
         * So any scsi_test failure here will cause us to assume no page
         * 80/83 support, and we will proceed without devid support.
         * So -1 returns from send_scsi_INQUIRY are not terminal.
         */
        {
                STC_VPD_CHECK,
                0,
                0
        },

        /*
         * If the above inquiry claims pg80 support then scsi_device_identity
         * will perform a send_scsi_INQUIRY to retrieve that page.
         * Anything other than SCSI_TEST_CMPLT_GOOD is a failure and will
         * cause scsi_device_identity to return non-zero at which point the
         * caller goes to SCSIPROBE_FAILURE.
         */
        {
                STC_IDENTITY_PG80,
                0,
                SCSI_TEST_FAILMASK | SCSI_TEST_CMPLTMASK
        },

        /*
         * Similarly for pg83
         */
        {
                STC_IDENTITY_PG83,
                0,
                SCSI_TEST_FAILMASK | SCSI_TEST_CMPLTMASK
        }
};

int scsi_test_ereport_disable = 0;

extern int e_devid_cache_path_to_devid(char *, char *, char *, ddi_devid_t *);

static void
scsi_test_ereport_post(struct scsi_pkt *pkt, enum scsi_test_ctxt ctxt,
    uint32_t stresult)
{
        char *nodename = NULL, *devidstr_buf = NULL, *devidstr = NULL;
        const struct scsi_test_profile *tp = &scsi_test_profile[ctxt];
        char ua[SCSI_MAXNAMELEN], nodenamebuf[SCSI_MAXNAMELEN];
        union scsi_cdb *cdbp = (union scsi_cdb *)pkt->pkt_cdbp;
        struct scsi_address *ap = &pkt->pkt_address;
        char *tgt_port, *tpl0 = NULL;
        ddi_devid_t devid = NULL;
        dev_info_t *probe, *hba;
        struct scsi_device *sd;
        scsi_lun64_t lun64;
        const char *d_ass;
        const char *class;
        char *pathbuf;
        nvlist_t *pl;
        uint64_t wwn;
        int err = 0;
        int dad = 0;
        size_t len;
        int lun;

        if (scsi_test_ereport_disable)
                return;

        ASSERT(tp->stp_ctxt == ctxt);

        if ((sd = scsi_address_device(ap)) == NULL)
                return;         /* Not SCSI_HBA_ADDR_COMPLEX */

        probe = sd->sd_dev;
        hba = ddi_get_parent(probe);

        /*
         * We only raise telemetry for SE_HP style enumeration
         */
        if (!ndi_dev_is_hotplug_node(hba))
                return;

        /*
         * scsi_fm_ereport_post will use the hba for the fm-enabled devinfo
         */
        if (!DDI_FM_EREPORT_CAP(ddi_fm_capable(hba)))
                return;

        /*
         * Retrieve the unit address we were probing and the target
         * port component thereof.
         */
        if (!scsi_ua_get(sd, ua, sizeof (ua)) ||
            scsi_device_prop_lookup_string(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_TARGET_PORT, &tgt_port) != DDI_PROP_SUCCESS)
                return;

        /*
         * Determine whether unit address is location based or identity (wwn)
         * based.  If we can't convert the target port address to a wwn then
         * we're location based.
         */
        if (scsi_wwnstr_to_wwn(tgt_port, &wwn) == DDI_FAILURE)
                return;

        /*
         * Get lun and lun64
         */
        lun = scsi_device_prop_get_int(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_LUN, 0);
        lun64 = scsi_device_prop_get_int64(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_LUN64, lun);

        /*
         * We are guaranteed not to be in interrupt or any other
         * problematic context.  So instead of repeated varargs
         * style calls to scsi_fm_ereport_post for each flavour of
         * ereport we have the luxury of being able to allocate
         * and build an nvlist here.
         *
         * The ereports we raise here are all under the category
         * ereport.io.scsi.cmd.disk category, namely
         *
         *      ereport.io.scsi.cmd.disk.
         *                      {dev.rqs.derr,dev.serr,tran}.
         *
         * For all ereports we also add the scsi_test specific payload.
         * If we have it then we always include the devid in the payload
         * (but only in the detector for device-as-detector ereports).
         *
         * Inherited From       Member Name
         * -------------------- -------------------
         *      .cmd            driver-assessment
         *      .cmd            op-code
         *      .cmd            cdb
         *      .cmd            pkt-reason
         *      .cmd            pkt-state
         *      .cmd            pkt-stats
         *      .cmd.disk       stat-code
         *      -               scsi-test-return
         *      -               scsi-test-context
         */

        if (nvlist_alloc(&pl, NV_UNIQUE_NAME, 0) != 0)
                return;

        err |= nvlist_add_uint8(pl, "op-code", cdbp->scc_cmd);
        err |= nvlist_add_uint8_array(pl, "cdb", pkt->pkt_cdbp,
            pkt->pkt_cdblen);
        err |= nvlist_add_uint8(pl, "pkt-reason", pkt->pkt_reason);
        err |= nvlist_add_uint32(pl, "pkt-state", pkt->pkt_state);
        err |= nvlist_add_uint32(pl, "pkt-stats", pkt->pkt_statistics);
        err |= nvlist_add_uint32(pl, "stat-code", *pkt->pkt_scbp);
        err |= nvlist_add_uint32(pl, "scsi-test-return", stresult);
        err |= nvlist_add_int32(pl, "scsi-test-context", ctxt);

        switch (stresult) {
        case SCSI_TEST_CMPLT_BUSY:
                dad = 1;
                class = "cmd.disk.dev.serr";
                break;

        case SCSI_TEST_CMPLT_CHECK:
                dad = 1;

                if ((pkt->pkt_state & STATE_ARQ_DONE)) {
                        struct scsi_arq_status *arqstat;
                        uint8_t key, asc, ascq;
                        uint8_t *sensep;

                        class = "cmd.disk.dev.rqs.derr";
                        arqstat = (struct scsi_arq_status *)pkt->pkt_scbp;
                        sensep = (uint8_t *)&arqstat->sts_sensedata;
                        key = scsi_sense_key(sensep);
                        asc = scsi_sense_asc(sensep);
                        ascq = scsi_sense_ascq(sensep);

                        /*
                         * Add to payload.
                         */
                        err |= nvlist_add_uint8(pl, "key", key);
                        err |= nvlist_add_uint8(pl, "asc", asc);
                        err |= nvlist_add_uint8(pl, "ascq", ascq);
                        err |= nvlist_add_uint8_array(pl, "sense-data",
                            sensep, sizeof (arqstat->sts_sensedata));
                } else {
                        class = "cmd.disk.dev.serr";
                }

                break;

        case SCSI_TEST_CMPLT_OTHER:
                dad = 1;
                class = "cmd.disk.dev.serr";
                break;

        case SCSI_TEST_CMD_INCOMPLETE:
        case SCSI_TEST_NOTCMPLT:
        case SCSI_TEST_TRAN_BUSY:
        case SCSI_TEST_TRAN_REJECT:
                class = "cmd.disk.tran";
                break;
        }

        /*
         * Determine driver-assessment and add to payload.
         */
        if (dad) {
                /*
                 * While higher level software can retry the enumeration
                 * the belief is that any device-as-detector style error
                 * will be persistent and will survive retries.  So we
                 * can make a local determination of driver assessment.
                 * Some day it may be more elegant to raise an ereport from
                 * scsi_tgtmap_scsi_deactivate to confirm retries failed,
                 * and correlate that ereport during diagnosis.
                 */
                if (stresult & tp->stp_fatalmask)
                        d_ass = (const char *)"fatal";
                else if (stresult & tp->stp_retrymask)
                        d_ass = (const char *)"retry";
                else
                        d_ass = (const char *)"retry";
        } else {
                /* We do not diagnose transport errors (yet) */
                        d_ass = (const char *)"retry";
        }

        err |= nvlist_add_string(pl, "driver-assessment", d_ass);

        /*
         * If we're hoping for a device-as-detector style ereport then
         * we're going to need a devid for the detector FMRI.  We
         * don't have the devid because the target won't talk to us.
         * But we do know which hba iport we were probing out of, and
         * we know the unit address that was being probed (but not
         * what type of device is or should be there).  So we
         * search the devid cache for any cached devid matching
         * path <iport-path>/<nodename>@<unit-address> with nodename
         * wildcarded.  If a match is made we are returned not only the
         * devid but also the nodename for the path that cached that
         * entry.
         *
         * We also attempt to dig up a devid even for transport errors;
         * we'll include that in the payload but not in the detector FMRI.
         */

        pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
        (void) ddi_pathname(hba, pathbuf);

        if (e_devid_cache_path_to_devid(pathbuf, ua, nodenamebuf,
            &devid) == DDI_SUCCESS) {
                nodename = nodenamebuf;
                devidstr = devidstr_buf = ddi_devid_str_encode(devid, NULL);
                kmem_free(devid, ddi_devid_sizeof(devid));
                err |= nvlist_add_string(pl, "devid", devidstr);
        }

        /*
         * If this is lun 0 we will include the target-port-l0id
         * in the dev scheme detector for device-as-detector.
         */
        if (dad && (lun == 0 || lun64 == 0))
                tpl0 = tgt_port;

        /* Construct the devpath to use in the detector */
        (void) ddi_pathname(hba, pathbuf);
        len = strlen(pathbuf);
        (void) snprintf(pathbuf + len, MAXPATHLEN - len, "/%s@%s",
            nodename ? nodename : "unknown", ua);

        /*
         * Let's review.
         *
         * Device-as-detector ereports for which the attempted lookup of
         * devid and nodename succeeded:
         *
         *      - pathbuf has the full device path including nodename we
         *        dug up from the devid cache
         *
         *      - class is one of cmd.disk.{dev.rqs.derr,dev.serr}
         *
         *      - devidstr is non NULL and a valid devid string
         *
         * Would-be device-as-detector ereport for which the attempted lookup
         * of devid failed:
         *
         *      - pathbuf has a device path with leaf nodename of "unknown"
         *        but still including the unit-address
         *      - class is one of cmd.disk.{dev.rqs.derr,dev.serr}
         *
         * Transport errors:
         *
         *      class is cmd.disk.tran
         *      devidstr is NULL
         *
         *      - we may have succeeded in looking up a devid and nodename -
         *        the devid we'll have added to the payload but we must not
         *        add to detector FMRI, and if we have have nodename then
         *        we have a full devpath otherwise one with "unknown" for
         *        nodename
         */

        if (err)
                (void) nvlist_add_boolean_value(pl, "payload-incomplete",
                    B_TRUE);

        scsi_fm_ereport_post(
            sd,
            0,                          /* path_instance - always 0 */
            pathbuf,                    /* devpath for detector */
            class,                      /* ereport class suffix */
            0,                          /* ENA - generate for us */
            dad ? devidstr : NULL,      /* dtcr devid, dev-as-det only */
            tpl0,                       /* target-port-l0id */
            DDI_SLEEP,
            pl, /* preconstructed payload */
            FM_VERSION, DATA_TYPE_UINT8, FM_EREPORT_VERS0,
            NULL);

        nvlist_free(pl);
        if (devidstr_buf)
                ddi_devid_str_free(devidstr_buf);
        kmem_free(pathbuf, MAXPATHLEN);
}

#ifdef  DEBUG
/*
 * Testing - fake scsi_test fails
 */
char scsi_test_fail_ua[SCSI_MAXNAMELEN];        /* unit address to object to */
int scsi_test_fail_rc = TRAN_ACCEPT;            /* scsi_transport return */
uchar_t scsi_test_fail_pkt_reason = CMD_CMPLT;  /* pkt_reason */
uchar_t scsi_test_fail_status = STATUS_BUSY;    /* status */
uint_t scsi_test_fail_repeat = (uint_t)-1;      /* number of times to fail ua */
#endif

/*
 * This is like scsi_poll, but only does retry for TRAN_BUSY.
 */
static uint32_t
scsi_test(struct scsi_pkt *pkt, enum scsi_test_ctxt ctxt)
{
        uint32_t        rval;
        int             wait_usec;
        int             rc;
        extern int      do_polled_io;

        pkt->pkt_flags |= FLAG_NOINTR;
        pkt->pkt_time = SCSI_POLL_TIMEOUT;      /* in seconds */

        if (scsi_ifgetcap(&pkt->pkt_address, "tagged-qing", 1) == 1) {
                pkt->pkt_flags |= FLAG_STAG;
        }

        /*
         * Each TRAN_BUSY response waits scsi_test_busy_delay usec up to a
         * maximum of scsi_test_busy_timeout.
         */
        for (wait_usec = 0; (wait_usec / 1000000) <= scsi_test_busy_timeout;
            wait_usec += scsi_test_busy_delay) {

                /* Initialize pkt status variables */
                *pkt->pkt_scbp = pkt->pkt_reason = pkt->pkt_state = 0;

                rc = scsi_transport(pkt);
                if ((rc != TRAN_BUSY) || (scsi_test_busy_delay == 0) ||
                    (scsi_test_busy_timeout == 0))
                        break;

                /* transport busy, wait */
                if ((curthread->t_flag & T_INTR_THREAD) == 0 && !do_polled_io) {
                        delay(drv_usectohz(scsi_test_busy_delay));
                } else {
                        /* we busy wait during cpr_dump or interrupt threads */
                        drv_usecwait(scsi_test_busy_delay);
                }
        }

#ifdef  DEBUG
        if (scsi_test_fail_ua[0] != '\0' && scsi_test_fail_repeat > 0) {
                struct scsi_address *ap = &pkt->pkt_address;
                struct scsi_device *sd;
                dev_info_t *probe;
                char ua[SCSI_MAXNAMELEN];

                if ((sd = scsi_address_device(ap)) != NULL) {
                        probe = sd->sd_dev;

                        if (probe && scsi_ua_get(sd, ua, sizeof (ua)) &&
                            strncmp(ua, scsi_test_fail_ua, sizeof (ua)) == 0) {
                                scsi_test_fail_repeat--;
                                rc = scsi_test_fail_rc;
                                if (rc == TRAN_ACCEPT)
                                        pkt->pkt_reason =
                                            scsi_test_fail_pkt_reason;
                                *pkt->pkt_scbp = scsi_test_fail_status;
                                if (scsi_test_fail_status == STATUS_CHECK)
                                        pkt->pkt_state |= STATE_ARQ_DONE;

                        }
                }
        }
#endif

        switch (rc) {
        case TRAN_ACCEPT:
                switch (pkt->pkt_reason) {
                case CMD_CMPLT:
                        switch ((*pkt->pkt_scbp) & STATUS_MASK) {
                        case STATUS_GOOD:
                                rval = SCSI_TEST_CMPLT_GOOD;
                                break;

                        case STATUS_BUSY:
                                rval = SCSI_TEST_CMPLT_BUSY;
                                break;

                        case STATUS_CHECK:
                                rval = SCSI_TEST_CMPLT_CHECK;
                                break;

                        default:
                                rval = SCSI_TEST_CMPLT_OTHER;
                                break;
                        }
                        break;

                case CMD_INCOMPLETE:
                        rval = SCSI_TEST_CMD_INCOMPLETE;
                        break;

                default:
                        rval = SCSI_TEST_NOTCMPLT;
                        break;
                }
                break;

        case TRAN_BUSY:
                rval = SCSI_TEST_TRAN_BUSY;
                break;

        default:
                rval = SCSI_TEST_TRAN_REJECT;
                break;
        }

        if (rval != SCSI_TEST_CMPLT_GOOD)
                scsi_test_ereport_post(pkt, ctxt, rval);

        return (rval);
}

/*
 * The implementation of scsi_probe now allows a particular
 * HBA to intercept the call, for any post- or pre-processing
 * it may need.  The default, if the HBA does not override it,
 * is to call scsi_hba_probe(), which retains the old functionality
 * intact.
 */
int
scsi_probe(struct scsi_device *sd, int (*callback)())
{
        int                     ret, retry = 0;
        int                     lr_cap, sr_ret;
        scsi_hba_tran_t         *tran = sd->sd_address.a_hba_tran;

        if (scsi_check_ss2_LUN_limit(sd) != 0) {
                /*
                 * caller is trying to probe a strictly-SCSI-2 device
                 * with a LUN that is too large, so do not allow it
                 */
                return (SCSIPROBE_NORESP);      /* skip probing this one */
        }
again:
        ret = lr_cap = sr_ret = -1;
        if (tran->tran_tgt_probe != NULL) {
                ret = (*tran->tran_tgt_probe)(sd, callback);
        } else {
                ret = scsi_hba_probe(sd, callback);
        }

        if ((ret != SCSIPROBE_EXISTS) && (retry == 0)) {
                lr_cap = (*tran->tran_getcap)(&sd->sd_address, "lun-reset", 1);
                sr_ret = scsi_reset(&sd->sd_address, RESET_LUN);
                if ((sr_ret != 1) && (lr_cap == 1)) {
                        cmn_err(CE_WARN, "scsi_probe(%d): scsi_reset failed(%d)"
                            " lun-reset cap(%d)", ret, sr_ret, lr_cap);
                }
                retry = 1;
                goto again;
        }

        if (ret == SCSIPROBE_EXISTS) {
                create_inquiry_props(sd);
                /* is this a strictly-SCSI-2 node ?? */
                scsi_establish_LUN_limit(sd);
        }

        return (ret);
}
/*
 * probe scsi device using any available path
 *
 */
int
scsi_hba_probe(struct scsi_device *sd, int (*callback)())
{
        return (scsi_hba_probe_pi(sd, callback, 0));
}

/*
 * probe scsi device using specific path
 *
 * scsi_hba_probe_pi does not do any test unit ready's which access the medium
 * and could cause busy or not ready conditions.
 * scsi_hba_probe_pi does 2 inquiries and a rqsense to clear unit attention
 * and to allow sync negotiation to take place
 * finally, scsi_hba_probe_pi does one more inquiry which should
 * reliably tell us what kind of target we have.
 * A scsi-2 compliant target should be able to  return inquiry with 250ms
 * and we actually wait more than a second after reset.
 */
int
scsi_hba_probe_pi(struct scsi_device *sd, int (*callback)(), int pi)
{
        struct scsi_pkt         *inq_pkt = NULL;
        struct scsi_pkt         *rq_pkt = NULL;
        int                     rval = SCSIPROBE_NOMEM;
        struct buf              *inq_bp = NULL;
        struct buf              *rq_bp = NULL;
        int                     (*cb_flag)();
        int                     pass = 1;
        uint32_t                str;

        if (sd->sd_inq == NULL) {
                sd->sd_inq = (struct scsi_inquiry *)
                    kmem_alloc(SUN_INQSIZE, ((callback == SLEEP_FUNC) ?
                    KM_SLEEP : KM_NOSLEEP));
                if (sd->sd_inq == NULL) {
                        goto out;
                }
        }

        if (callback != SLEEP_FUNC && callback != NULL_FUNC) {
                cb_flag = NULL_FUNC;
        } else {
                cb_flag = callback;
        }
        inq_bp = scsi_alloc_consistent_buf(ROUTE,
            (struct buf *)NULL, SUN_INQSIZE, B_READ, cb_flag, NULL);
        if (inq_bp == NULL) {
                goto out;
        }

        inq_pkt = scsi_init_pkt(ROUTE, (struct scsi_pkt *)NULL,
            inq_bp, CDB_GROUP0, sizeof (struct scsi_arq_status),
            0, PKT_CONSISTENT, callback, NULL);
        if (inq_pkt == NULL) {
                if (inq_bp->b_error == 0)
                        rval = SCSIPROBE_NOMEM_CB;
                goto out;
        }
        ASSERT(inq_bp->b_error == 0);

        (void) scsi_setup_cdb((union scsi_cdb *)inq_pkt->pkt_cdbp,
            SCMD_INQUIRY, 0, SUN_INQSIZE, 0);
        inq_pkt->pkt_flags = FLAG_NOINTR|FLAG_NOPARITY;

        /*
         * set transport path
         */
        if (pi && scsi_pkt_allocated_correctly(inq_pkt)) {
                inq_pkt->pkt_path_instance = pi;
                inq_pkt->pkt_flags |= FLAG_PKT_PATH_INSTANCE;
        }

        /*
         * the first inquiry will tell us whether a target
         * responded
         *
         * The FILL_SCSI1_LUN below will find "ansi_ver != 1" on first pass
         * because of bzero initilization. If this assumption turns out to be
         * incorrect after we have real sd_inq data (for lun0) we will do a
         * second pass during which FILL_SCSI1_LUN will place lun in CDB.
         */
        bzero((caddr_t)sd->sd_inq, SUN_INQSIZE);
again:  FILL_SCSI1_LUN(sd, inq_pkt);

        str = scsi_test(inq_pkt, STC_PROBE_FIRST_INQ);
        if (SCSI_TEST_FAILURE(str)) {
                if (str == SCSI_TEST_CMD_INCOMPLETE) {
                        rval = SCSIPROBE_NORESP;
                        goto out;
                }

                /*
                 * Retry one more time for anything other than CMD_INCOMPLETE.
                 */
                str = scsi_test(inq_pkt, STC_PROBE_FIRST_INQ_RETRY);
                if (SCSI_TEST_FAILURE(str)) {
                        rval = SCSIPROBE_FAILURE;
                        goto out;
                }
        }

        /*
         * Did the inquiry complete and transfer inquiry information,
         * perhaps after retry?
         */
        if (str == SCSI_TEST_CMPLT_GOOD)
                goto done;

        /*
         * We get here for SCSI_TEST_CMPLT_{BUSY,CHECK,OTHER}. We term
         * this "partial success" in that at least something is talking
         * to us.
         *
         * A second inquiry allows the host adapter to negotiate
         * synchronous transfer period and offset
         */
        str = scsi_test(inq_pkt, STC_PROBE_PARTIAL_SUCCESS);
        if (SCSI_TEST_FAILURE(str)) {
                if (str == SCSI_TEST_CMD_INCOMPLETE)
                        rval = SCSIPROBE_NORESP;
                else
                        rval = SCSIPROBE_FAILURE;
                goto out;
        }

        /*
         * If target is still busy, give up now.
         * XXX There's no interval between retries - scsi_test should
         * probably have a builtin retry on target busy.
         */
        if (str == SCSI_TEST_CMPLT_BUSY) {
                rval = SCSIPROBE_BUSY;
                goto out;
        }

        /*
         * At this point we are SCSI_TEST_CMPLT_GOOD, SCSI_TEST_CMPLT_CHECK
         * or SCSI_TEST_CMPLT_OTHER.
         *
         * Do a rqsense if there was a check condition and ARQ was not done
         */
        if (str == SCSI_TEST_CMPLT_CHECK &&
            (inq_pkt->pkt_state & STATE_ARQ_DONE) == 0) {
                /*
                 * prepare rqsense packet
                 * there is no real need for this because the
                 * check condition should have been cleared by now.
                 */
                rq_bp = scsi_alloc_consistent_buf(ROUTE, (struct buf *)NULL,
                    (uint_t)SENSE_LENGTH, B_READ, cb_flag, NULL);
                if (rq_bp == NULL) {
                        goto out;
                }

                rq_pkt = scsi_init_pkt(ROUTE, (struct scsi_pkt *)NULL,
                    rq_bp, CDB_GROUP0, 1, 0, PKT_CONSISTENT, callback, NULL);

                if (rq_pkt == NULL) {
                        if (rq_bp->b_error == 0)
                                rval = SCSIPROBE_NOMEM_CB;
                        goto out;
                }
                ASSERT(rq_bp->b_error == 0);

                (void) scsi_setup_cdb((union scsi_cdb *)rq_pkt->
                    pkt_cdbp, SCMD_REQUEST_SENSE, 0, SENSE_LENGTH, 0);
                FILL_SCSI1_LUN(sd, rq_pkt);
                rq_pkt->pkt_flags = FLAG_NOINTR|FLAG_NOPARITY;

                /*
                 * set transport path
                 */
                if (pi && scsi_pkt_allocated_correctly(rq_pkt)) {
                        rq_pkt->pkt_path_instance = pi;
                        rq_pkt->pkt_flags |= FLAG_PKT_PATH_INSTANCE;
                }

                /*
                 * The FILL_SCSI1_LUN above will find "inq_ansi != 1"
                 * on first pass, see "again" comment above.
                 *
                 * The controller type is as yet unknown, so we
                 * have to do a throwaway non-extended request sense,
                 * and hope that that clears the check condition for
                 * that unit until we can find out what kind of drive
                 * it is. A non-extended request sense is specified
                 * by stating that the sense block has 0 length,
                 * which is taken to mean that it is four bytes in
                 * length.
                 */
                if (SCSI_TEST_FAILURE(scsi_test(rq_pkt, STC_PROBE_RQSENSE1))) {
                        rval = SCSIPROBE_FAILURE;
                        goto out;
                }
        }

        /*
         * At this point, we are guaranteed that something responded
         * to this scsi bus target id. We don't know yet what
         * kind of device it is, or even whether there really is
         * a logical unit attached (as some SCSI target controllers
         * lie about a unit being ready, e.g., the Emulex MD21).
         */

        str = scsi_test(inq_pkt, STC_PROBE_CHK_CLEARED);
        if (SCSI_TEST_FAILURE(str)) {
                rval = SCSIPROBE_FAILURE;
                goto out;
        }

        if (str == SCSI_TEST_CMPLT_BUSY) {
                rval = SCSIPROBE_BUSY;
                goto out;
        }

        /*
         * Okay we sent the INQUIRY command.
         *
         * If enough data was transferred, we count that the
         * Inquiry command succeeded, else we have to assume
         * that this is a non-CCS scsi target (or a nonexistent
         * target/lun).
         */

        if (str == SCSI_TEST_CMPLT_CHECK) {
                /*
                 * try a request sense if we have a pkt, otherwise
                 * just retry the inquiry one more time
                 */
                if (rq_pkt)
                        (void) scsi_test(rq_pkt, STC_PROBE_RQSENSE2);

                /*
                 * retry inquiry
                 */
                str = scsi_test(inq_pkt, STC_PROBE_INQ_FINAL);
                if (SCSI_TEST_FAILURE(str)) {
                        rval = SCSIPROBE_FAILURE;
                        goto out;
                } else if (str == SCSI_TEST_CMPLT_CHECK) {
                        rval = SCSIPROBE_FAILURE;
                        goto out;
                }
        }

done:
        /*
         * If we got a parity error on receive of inquiry data,
         * we're just plain out of luck because we told the host
         * adapter to not watch for parity errors.
         */
        if ((inq_pkt->pkt_state & STATE_XFERRED_DATA) == 0 ||
            ((SUN_INQSIZE - inq_pkt->pkt_resid) < SUN_MIN_INQLEN)) {
                rval = SCSIPROBE_NONCCS;
        } else {
                ASSERT(inq_pkt->pkt_resid >= 0);
                bcopy((caddr_t)inq_bp->b_un.b_addr,
                    (caddr_t)sd->sd_inq, (SUN_INQSIZE - inq_pkt->pkt_resid));
                rval = SCSIPROBE_EXISTS;
        }

out:
        /*
         * If lun > 0 we need to figure out if this is a scsi-1 device where
         * the "real" lun needs to be embedded into the cdb.
         */
        if ((rval == SCSIPROBE_EXISTS) && (pass == 1) &&
            (sd->sd_address.a_lun > 0) && (sd->sd_inq->inq_ansi == 0x1)) {
                pass++;
                if (sd->sd_address.a_lun <= 7)
                        goto again;

                /*
                 * invalid lun for scsi-1,
                 * return probe failure.
                 */
                rval = SCSIPROBE_FAILURE;
        }

        if (rq_pkt) {
                scsi_destroy_pkt(rq_pkt);
        }
        if (inq_pkt) {
                scsi_destroy_pkt(inq_pkt);
        }
        if (rq_bp) {
                scsi_free_consistent_buf(rq_bp);
        }
        if (inq_bp) {
                scsi_free_consistent_buf(inq_bp);
        }
        return (rval);
}

/*
 * Convert from a scsi_device structure pointer to a scsi_hba_tran structure
 * pointer. The correct way to do this is
 *
 *      #define DEVP_TO_TRAN(sd)        ((sd)->sd_address.a_hba_tran)
 *
 * however we have some consumers that place their own vector in a_hba_tran. To
 * avoid problems, we implement this using the sd_tran_safe. See
 * scsi_hba_initchild for more details.
 */
#define DEVP_TO_TRAN(sd)        ((sd)->sd_tran_safe)

/*
 * Function, callable from SCSA framework, to get 'human' readable REPORTDEV
 * addressing information from scsi_device properties.
 */
int
scsi_ua_get_reportdev(struct scsi_device *sd, char *ra, int len)
{
        /* use deprecated tran_get_bus_addr interface if it is defined */
        /* NOTE: tran_get_bus_addr is a poor name choice for interface */
        if (DEVP_TO_TRAN(sd)->tran_get_bus_addr)
                return ((*DEVP_TO_TRAN(sd)->tran_get_bus_addr)(sd, ra, len));
        return (scsi_hba_ua_get_reportdev(sd, ra, len));
}

/*
 * Function, callable from HBA driver's tran_get_bus_addr(9E) implementation,
 * to get standard form of human readable REPORTDEV addressing information
 * from scsi_device properties.
 */
int
scsi_hba_ua_get_reportdev(struct scsi_device *sd, char *ra, int len)
{
        int             tgt, lun, sfunc;
        char            *tgt_port;
        scsi_lun64_t    lun64;

        /* get device unit-address properties */
        tgt = scsi_device_prop_get_int(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_TARGET, -1);
        if (scsi_device_prop_lookup_string(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_TARGET_PORT, &tgt_port) != DDI_PROP_SUCCESS)
                tgt_port = NULL;
        if ((tgt == -1) && (tgt_port == NULL))
                return (0);             /* no target */

        lun = scsi_device_prop_get_int(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_LUN, 0);
        lun64 = scsi_device_prop_get_int64(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_LUN64, lun);
        sfunc = scsi_device_prop_get_int(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_SFUNC, -1);

        /*
         * XXX should the default be to print this in decimal for
         * "human readable" form, so it matches conf files?
         */
        if (tgt_port) {
                if (sfunc == -1)
                        (void) snprintf(ra, len,
                            "%s %s lun %" PRIx64,
                            SCSI_ADDR_PROP_TARGET_PORT, tgt_port, lun64);
                else
                        (void) snprintf(ra, len,
                            "%s %s lun %" PRIx64 " sfunc %x",
                            SCSI_ADDR_PROP_TARGET_PORT, tgt_port, lun64, sfunc);
                scsi_device_prop_free(sd, SCSI_DEVICE_PROP_PATH, tgt_port);
        } else {
                if (sfunc == -1)
                        (void) snprintf(ra, len,
                            "%s %x lun %" PRIx64,
                            SCSI_ADDR_PROP_TARGET, tgt, lun64);
                else
                        (void) snprintf(ra, len,
                            "%s %x lun %" PRIx64 " sfunc %x",
                            SCSI_ADDR_PROP_TARGET, tgt, lun64, sfunc);
        }

        return (1);
}

/*
 * scsi_ua_get: using properties, return "unit-address" string.
 * Called by SCSA framework, may call HBAs tran function.
 */
int
scsi_ua_get(struct scsi_device *sd, char *ua, int len)
{
        char            *eua;

        /* See if we already have established the unit-address. */
        if ((eua = scsi_device_unit_address(sd)) != NULL) {
                (void) strlcpy(ua, eua, len);
                return (1);
        }

        /* Use deprecated tran_get_name interface if it is defined. */
        /* NOTE: tran_get_name is a poor name choice for interface */
        if (DEVP_TO_TRAN(sd)->tran_get_name)
                return ((*DEVP_TO_TRAN(sd)->tran_get_name)(sd, ua, len));

        /* Use generic property implementation */
        return (scsi_hba_ua_get(sd, ua, len));
}

/*
 * scsi_hba_ua_get: using properties, return "unit-address" string.
 * This function may be called from an HBAs tran function.
 *
 * Function to get "unit-address" in "name@unit-address" /devices path
 * component form from the scsi_device unit-address properties on a node.
 *
 * NOTE: This function works in conjunction with scsi_hba_ua_set().
 */
int
scsi_hba_ua_get(struct scsi_device *sd, char *ua, int len)
{
        int             tgt, lun, sfunc;
        char            *tgt_port;
        scsi_lun64_t    lun64;

        /* get device unit-address properties */
        tgt = scsi_device_prop_get_int(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_TARGET, -1);
        if (scsi_device_prop_lookup_string(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_TARGET_PORT, &tgt_port) != DDI_PROP_SUCCESS)
                tgt_port = NULL;
        if ((tgt == -1) && (tgt_port == NULL))
                return (0);             /* no target */

        lun = scsi_device_prop_get_int(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_LUN, 0);
        lun64 = scsi_device_prop_get_int64(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_LUN64, lun);
        sfunc = scsi_device_prop_get_int(sd, SCSI_DEVICE_PROP_PATH,
            SCSI_ADDR_PROP_SFUNC, -1);
        if (tgt_port) {
                if (sfunc == -1)
                        (void) snprintf(ua, len, "%s,%" PRIx64,
                            tgt_port, lun64);
                else
                        (void) snprintf(ua, len, "%s,%" PRIx64 ",%x",
                            tgt_port, lun64, sfunc);
                scsi_device_prop_free(sd, SCSI_DEVICE_PROP_PATH, tgt_port);
        } else {
                if (sfunc == -1)
                        (void) snprintf(ua, len, "%x,%" PRIx64, tgt, lun64);
                else
                        (void) snprintf(ua, len, "%x,%" PRIx64 ",%x",
                            tgt, lun64, sfunc);
        }
        return (1);
}

static void
create_inquiry_props(struct scsi_device *sd)
{
        struct scsi_inquiry *inq = sd->sd_inq;

        (void) ndi_prop_update_int(DDI_DEV_T_NONE, sd->sd_dev,
            INQUIRY_DEVICE_TYPE, (int)inq->inq_dtype);

        /*
         * Create the following properties:
         *
         * inquiry-vendor-id    Vendor id (INQUIRY data bytes 8-15)
         * inquiry-product-id   Product id (INQUIRY data bytes 16-31)
         * inquiry-revision-id  Product Rev level (INQUIRY data bytes 32-35)
         *
         * NOTE: We don't support creation of these properties for scsi-1
         * devices (as the vid, pid and revision were not defined) and we
         * don't create the property if they are of zero length when
         * stripped of Nulls and spaces.
         *
         * NOTE: The first definition of these properties sticks. This gives
         * a transport the ability to provide a higher-quality definition
         * than the standard SCSI INQUIRY data.
         */
        if (inq->inq_ansi != 1) {
                if (ddi_prop_exists(DDI_DEV_T_NONE, sd->sd_dev,
                    DDI_PROP_TYPE_STRING, INQUIRY_VENDOR_ID) == 0)
                        (void) scsi_device_prop_update_inqstring(sd,
                            INQUIRY_VENDOR_ID,
                            inq->inq_vid, sizeof (inq->inq_vid));

                if (ddi_prop_exists(DDI_DEV_T_NONE, sd->sd_dev,
                    DDI_PROP_TYPE_STRING, INQUIRY_PRODUCT_ID) == 0)
                        (void) scsi_device_prop_update_inqstring(sd,
                            INQUIRY_PRODUCT_ID,
                            inq->inq_pid, sizeof (inq->inq_pid));

                if (ddi_prop_exists(DDI_DEV_T_NONE, sd->sd_dev,
                    DDI_PROP_TYPE_STRING, INQUIRY_REVISION_ID) == 0)
                        (void) scsi_device_prop_update_inqstring(sd,
                            INQUIRY_REVISION_ID,
                            inq->inq_revision, sizeof (inq->inq_revision));
        }
}

/*
 * Create 'inquiry' string properties.  An 'inquiry' string gets special
 * treatment to trim trailing blanks (etc) and ensure null termination.
 */
int
scsi_device_prop_update_inqstring(struct scsi_device *sd,
    char *name, char *data, size_t len)
{
        int     ilen;
        char    *data_string;
        int     rv;

        ilen = scsi_ascii_inquiry_len(data, len);
        ASSERT(ilen <= (int)len);
        if (ilen <= 0)
                return (DDI_PROP_INVAL_ARG);

        /* ensure null termination */
        data_string = kmem_zalloc(ilen + 1, KM_SLEEP);
        bcopy(data, data_string, ilen);
        rv = ndi_prop_update_string(DDI_DEV_T_NONE,
            sd->sd_dev, name, data_string);
        kmem_free(data_string, ilen + 1);
        return (rv);
}

/*
 * Interfaces associated with SCSI_HBA_ADDR_COMPLEX
 * per-scsi_device HBA private data support.
 *
 * scsi_address_device returns NULL if we're not SCSI_HBA_ADDR_COMPLEX,
 * thereby allowing use of scsi_address_device as a test for
 * SCSI_HBA_ADDR_COMPLEX.
 */
struct scsi_device *
scsi_address_device(struct scsi_address *sa)
{
        return ((sa->a_hba_tran->tran_hba_flags & SCSI_HBA_ADDR_COMPLEX) ?
            sa->a.a_sd : NULL);
}

void
scsi_device_hba_private_set(struct scsi_device *sd, void *data)
{
        ASSERT(sd->sd_address.a_hba_tran->tran_hba_flags &
            SCSI_HBA_ADDR_COMPLEX);
        sd->sd_hba_private = data;
}

void *
scsi_device_hba_private_get(struct scsi_device *sd)
{
        ASSERT(sd->sd_address.a_hba_tran->tran_hba_flags &
            SCSI_HBA_ADDR_COMPLEX);
        return (sd->sd_hba_private);
}

/*
 * This routine is called from the start of scsi_probe() if a tgt/LUN to be
 * probed *may* be a request to probe a strictly SCSI-2 target (with respect
 * to LUNs) -- and this probe may be for a LUN number greater than 7,
 * which can cause a hardware hang
 *
 * return 0 if the probe can proceed,
 * else return 1, meaning do *NOT* probe this target/LUN
 */
static int
scsi_check_ss2_LUN_limit(struct scsi_device *sd)
{
        struct scsi_address     *ap = &(sd->sd_address);
        dev_info_t              *pdevi =
            (dev_info_t *)DEVI(sd->sd_dev)->devi_parent;
        int                     ret_val = 0;    /* default return value */
        uchar_t                 *tgt_list;
        uint_t                  tgt_nelements;
        int                     i;


        /*
         * check for what *might* be a problem probe, only we don't
         * know yet what's really at the destination target/LUN
         */
        if ((ap->a_target >= NTARGETS_WIDE) ||
            (ap->a_lun < NLUNS_PER_TARGET)) {
                return (0);             /* okay to probe this target */
        }

        /*
         * this *might* be a problematic probe, so look to see
         * if the inquiry data matches
         */
        SCSI_PROBE_DEBUG2(1, "SCSA pre-probe: checking tgt.LUN=%d.%d\n",
            ap->a_target, ap->a_lun);
        SCSI_PROBE_DEBUG1(2,
            "SCSA pre-probe: scanning parent node name: %s ...\n",
            ddi_node_name(pdevi));

        /*
         * look for a special property of our parent node that lists
         * the targets under it for which we do *NOT* want to probe
         * if LUN>7 -- if the property is found, look to see if our
         * target ID is on that list
         */
        if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, pdevi,
            DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, SS2_LUN0_TGT_LIST_PROP,
            &tgt_list, &tgt_nelements) != DDI_PROP_SUCCESS) {
                /*
                 * no list, so it must be okay to probe this target.LUN
                 */
                SCSI_PROBE_DEBUG0(3,
                    "SCSA pre-probe: NO parent prop found\n");
        } else {
                for (i = 0; i < tgt_nelements; i++) {
                        if (tgt_list[i] == ap->a_target) {
                                /*
                                 * we found a match, which means we do *NOT*
                                 * want to probe the specified target.LUN
                                 */
                                ret_val = 1;
                                break;
                        }
                }
                ddi_prop_free(tgt_list);
#ifdef  DEBUG
                if (ret_val == 1) {
                        SCSI_PROBE_DEBUG2(1,
                            "SCSA pre-probe: marker node FOUND for "
                            "tgt.LUN=%d.%d, so SKIPPING it\n",
                            ap->a_target, ap->a_lun);
                } else {
                        SCSI_PROBE_DEBUG0(2,
                            "SCSA pre-probe: NO marker node found"
                            " -- OK to probe\n");
                }
#endif
        }
        return (ret_val);
}


/*
 * this routine is called from near the end of scsi_probe(),
 * to see if the just-probed node is on our list of strictly-SCSI-2 nodes,
 * and if it is we mark our parent node with this information
 */
static void
scsi_establish_LUN_limit(struct scsi_device *sd)
{
        struct scsi_address     *ap = &(sd->sd_address);
        struct scsi_inquiry     *inq = sd->sd_inq;
        dev_info_t              *devi = sd->sd_dev;
        char                    *vid = NULL;
        char                    *pid = NULL;
        char                    *rev = NULL;
        int                     i;
        const ss2_lun0_info_t   *p;
        int                     bad_target_found = 0;


        /*
         * if this inquiry data shows that we have a strictly-SCSI-2 device
         * at LUN 0, then add it to our list of strictly-SCSI-2 devices,
         * so that we can avoid probes where LUN>7 on this device later
         */
        if ((ap->a_lun != 0) ||
            (ap->a_target >= NTARGETS_WIDE) ||
            (inq->inq_dtype != DTYPE_PROCESSOR) ||
            (inq->inq_ansi != 2)) {
                /*
                 * this can't possibly be a node we want to look at, since
                 * either LUN is greater than 0, target is greater than or
                 * equal to 16, device type
                 * is not processor, or SCSI level is not SCSI-2,
                 * so don't bother checking for a strictly SCSI-2
                 * (only 8 LUN) target
                 */
                return;                         /* don't care */
        }

        SCSI_PROBE_DEBUG2(1, "SCSA post-probe: LUN limit on tgt.LUN=%d.%d, "
            "SCSI-2 PROCESSOR?\n", ap->a_target, ap->a_lun);

        ASSERT(devi != NULL);

        /*
         * we have a node that has been probed that is: LUN=0, target<16,
         * PROCESSOR-type SCSI target, and at the SCSI-2 level, so
         * check INQ properties to see if it's in our list of strictly
         * SCSI-2 targets
         *
         * first we have to get the VID/PID/REV INQUIRY properties for
         * comparison
         */
        if (ddi_prop_lookup_string(DDI_DEV_T_ANY, devi,
            DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
            INQUIRY_VENDOR_ID, &vid) != DDI_PROP_SUCCESS) {
                SCSI_PROBE_DEBUG1(2, "SCSA post-probe: prop \"%s\" missing\n",
                    INQUIRY_VENDOR_ID);
                goto dun;
        }
        if (ddi_prop_lookup_string(DDI_DEV_T_ANY, devi,
            DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
            INQUIRY_PRODUCT_ID, &pid) != DDI_PROP_SUCCESS) {
                SCSI_PROBE_DEBUG1(2, "SCSA post-probe: prop \"%s\" missing\n",
                    INQUIRY_PRODUCT_ID);
                goto dun;
        }
        if (ddi_prop_lookup_string(DDI_DEV_T_ANY, devi,
            DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
            INQUIRY_REVISION_ID, &rev) != DDI_PROP_SUCCESS) {
                SCSI_PROBE_DEBUG1(2, "SCSA post-probe: prop \"%s\" missing\n",
                    INQUIRY_REVISION_ID);
                goto dun;
        }

        SCSI_PROBE_DEBUG3(3, "SCSA post-probe: looking for vid/pid/rev = "
            "\"%s\"/\"%s\"/\"%s\"\n", vid, pid, rev);

        /*
         * now that we have the INQUIRY properties from the device node,
         * compare them with our known offenders
         *
         * Note: comparison is *CASE* *SENSITIVE*
         */
        for (i = 0; i < scsi_probe_strict_s2_size; i++) {
                p = &scsi_probe_strict_s2_list[i];

                if ((strcmp(p->sli_vid, vid) == 0) &&
                    (strcmp(p->sli_pid, pid) == 0) &&
                    (strcmp(p->sli_rev, rev) == 0)) {
                        /*
                         * we found a match -- do NOT want to probe this one
                         */
                        SCSI_PROBE_DEBUG3(1,
                            "SCSA post-probe: recording strict SCSI-2 node "
                            "vid/pid/rev = \"%s\"/\"%s\"/\"%s\"\n",
                            vid, pid, rev);

                        /*
                         * set/update private parent-node property,
                         * so we can find out about this node later
                         */
                        bad_target_found = 1;
                        break;
                }
        }

        /*
         * either add remove target number from parent property
         */
        scsi_update_parent_ss2_prop(devi, ap->a_target, bad_target_found);

dun:
        if (vid != NULL) {
                ddi_prop_free(vid);
        }
        if (pid != NULL) {
                ddi_prop_free(pid);
        }
        if (rev != NULL) {
                ddi_prop_free(rev);
        }
}


/*
 * update the parent node to add in the supplied tgt number to the target
 * list property already present (if any)
 *
 * since the target list can never be longer than 16, and each target
 * number is also small, we can save having to alloc memory by putting
 * a 16-byte array on the stack and using it for property memory
 *
 * if "add_tgt" is set then add the target to the parent's property, else
 * remove it (if present)
 */
static void
scsi_update_parent_ss2_prop(dev_info_t *devi, int tgt, int add_tgt)
{
        dev_info_t      *pdevi = (dev_info_t *)DEVI(devi)->devi_parent;
        uchar_t         *tgt_list;
        uint_t          nelements;
        uint_t          new_nelements;
        int             i;
        int             update_result;
        uchar_t         new_tgt_list[NTARGETS_WIDE];


        ASSERT(pdevi != NULL);

        SCSI_PROBE_DEBUG3(3,
            "SCSA post-probe: updating parent=%s property to %s tgt=%d\n",
            ddi_node_name(pdevi), add_tgt ? "add" : "remove", tgt);

        if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, pdevi,
            DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
            SS2_LUN0_TGT_LIST_PROP, &tgt_list, &nelements) ==
            DDI_PROP_SUCCESS) {

                if (add_tgt) {
                        /*
                         * we found an existing property -- we might need
                         *      to add to it
                         */
                        for (i = 0; i < nelements; i++) {
                                if (tgt_list[i] == tgt) {
                                        /* target already in list */
                                        SCSI_PROBE_DEBUG1(2, "SCSA post-probe:"
                                            " tgt %d already listed\n", tgt);
                                        ddi_prop_free(tgt_list);
                                        return;
                                }
                        }

                        /*
                         * need to append our target number to end of list
                         *      (no need sorting list, as it's so short)
                         */

                        /*
                         * will this new entry fit ?? -- it should, since
                         *      the array is 16-wide and only keep track of
                         *      16 targets, but check just in case
                         */
                        new_nelements = nelements + 1;
                        if (new_nelements >= NTARGETS_WIDE) {
                                SCSI_PROBE_DEBUG0(1, "SCSA post-probe: "
                                    "internal error: no room "
                                    "for more targets?\n");
                                ddi_prop_free(tgt_list);
                                return;
                        }

                        /* copy existing list then add our tgt number to end */
                        bcopy((void *)tgt_list, (void *)new_tgt_list,
                            sizeof (uchar_t) * nelements);
                        new_tgt_list[new_nelements - 1] = (uchar_t)tgt;
                } else {
                        /*
                         * we need to remove our target number from the list,
                         *      so copy all of the other target numbers,
                         *      skipping ours
                         */
                        int     tgt_removed = 0;

                        new_nelements = 0;
                        for (i = 0; i < nelements; i++) {
                                if (tgt_list[i] != tgt) {
                                        new_tgt_list[new_nelements++] =
                                            tgt_list[i];
                                } else {
                                        /* skip this target */
                                        tgt_removed++;
                                }
                        }

                        if (!tgt_removed) {
                                SCSI_PROBE_DEBUG1(2, "SCSA post-probe:"
                                    " no need to remove tgt %d\n", tgt);
                                ddi_prop_free(tgt_list);
                                return;
                        }
                }

                update_result = ddi_prop_update_byte_array(DDI_DEV_T_NONE,
                    pdevi, SS2_LUN0_TGT_LIST_PROP, new_tgt_list,
                    new_nelements);

                ddi_prop_free(tgt_list);
        } else {
                /*
                 * no property yet
                 */
                if (add_tgt) {
                        /*
                         * create a property with just our tgt
                         */
                        new_tgt_list[0] = (uchar_t)tgt;
                        new_nelements = 1;      /* just one element */

                        update_result = ddi_prop_update_byte_array(
                            DDI_DEV_T_NONE, pdevi, SS2_LUN0_TGT_LIST_PROP,
                            new_tgt_list, new_nelements);
                } else {
                        /*
                         * no list so no need to remove tgt from that list
                         */
                        return;
                }
        }

#ifdef  DEBUG
        /*
         * if we get here we have tried to add/update properties
         */
        if (update_result != DDI_PROP_SUCCESS) {
                SCSI_PROBE_DEBUG2(1, "SCSA post-probe: can't update parent "
                    "property with tgt=%d (%d)\n", tgt, update_result);
        } else {
                if (add_tgt) {
                        SCSI_PROBE_DEBUG3(2,
                            "SCSA post-probe: added tgt=%d to parent "
                            "prop=\"%s\" (now %d entries)\n",
                            tgt, SS2_LUN0_TGT_LIST_PROP, new_nelements);
                } else {
                        SCSI_PROBE_DEBUG3(2,
                            "SCSA post-probe: removed tgt=%d from parent "
                            "prop=\"%s\" (now %d entries)\n",
                            tgt, SS2_LUN0_TGT_LIST_PROP, new_nelements);
                }
        }
#endif
}


/* XXX BEGIN: find a better place for this: inquiry.h? */
/*
 * Definitions used by device id registration routines
 */
#define VPD_HEAD_OFFSET         3       /* size of head for vpd page */
#define VPD_PAGE_LENGTH         3       /* offset for pge length data */
#define VPD_MODE_PAGE           1       /* offset into vpd pg for "page code" */

/* size for devid inquiries */
#define MAX_INQUIRY_SIZE        0xF0
#define MAX_INQUIRY_SIZE_EVPD   0xFF    /* XXX why is this longer */
/* XXX END: find a better place for these */


/*
 * Decorate devinfo node with identity properties using information obtained
 * from device. These properties are used by device enumeration code to derive
 * the devid, and guid for the device. These properties are also used to
 * determine if a device should be enumerated under the physical HBA (PHCI) or
 * the virtual HBA (VHCI, for mpxio support).
 *
 * Return zero on success. If commands that should succeed fail or allocations
 * fail then return failure (non-zero). It is possible for this function to
 * return success and not have decorated the node with any additional identity
 * information if the device correctly responds indicating that they are not
 * supported.  When failure occurs the caller should consider not making the
 * device accessible.
 */
int
scsi_device_identity(struct scsi_device *sd, int (*callback)())
{
        dev_info_t      *devi           = sd->sd_dev;
        uchar_t         *inq80          = NULL;
        uchar_t         *inq83          = NULL;
        int             rval;
        size_t          len;
        int             pg80, pg83;

        /* find out what pages are supported by device */
        if (check_vpd_page_support8083(sd, callback, &pg80, &pg83) == -1)
                return (-1);

        /* if available, collect page 80 data and add as property */
        if (pg80) {
                inq80 = kmem_zalloc(MAX_INQUIRY_SIZE,
                    ((callback == SLEEP_FUNC) ? KM_SLEEP : KM_NOSLEEP));
                if (inq80 == NULL) {
                        rval = -1;
                        goto out;
                }

                rval = send_scsi_INQUIRY(sd, callback, inq80,
                    MAX_INQUIRY_SIZE, 0x01, 0x80, &len, STC_IDENTITY_PG80);
                if (rval)
                        goto out;               /* should have worked */

                if (len && (ndi_prop_update_byte_array(DDI_DEV_T_NONE, devi,
                    "inquiry-page-80", inq80, len) != DDI_PROP_SUCCESS)) {
                        cmn_err(CE_WARN, "scsi_device_identity: "
                            "failed to add page80 prop");
                        rval = -1;
                        goto out;
                }
        }

        /* if available, collect page 83 data and add as property */
        if (pg83) {
                inq83 = kmem_zalloc(MAX_INQUIRY_SIZE,
                    ((callback == SLEEP_FUNC) ? KM_SLEEP : KM_NOSLEEP));
                if (inq83 == NULL) {
                        rval = -1;
                        goto out;
                }

                rval = send_scsi_INQUIRY(sd, callback, inq83,
                    MAX_INQUIRY_SIZE, 0x01, 0x83, &len, STC_IDENTITY_PG83);
                if (rval)
                        goto out;               /* should have worked */

                if (len && (ndi_prop_update_byte_array(DDI_DEV_T_NONE, devi,
                    "inquiry-page-83", inq83, len) != DDI_PROP_SUCCESS)) {
                        cmn_err(CE_WARN, "scsi_device_identity: "
                            "failed to add page83 prop");
                        rval = -1;
                        goto out;
                }
        }

        /* Commands worked, identity information that exists has been added. */
        rval = 0;

        /* clean up resources */
out:    if (inq80 != NULL)
                kmem_free(inq80, MAX_INQUIRY_SIZE);
        if (inq83 != NULL)
                kmem_free(inq83, MAX_INQUIRY_SIZE);

        return (rval);
}

/*
 * Send an INQUIRY command with the EVPD bit set and a page code of 0x00 to
 * the device, returning zero on success. Returned INQUIRY data is used to
 * determine which vital product pages are supported. The device idenity
 * information we are looking for is in pages 0x83 and/or 0x80. If the device
 * fails the EVPD inquiry then no pages are supported but the call succeeds.
 * Return -1 (failure) if there were memory allocation failures or if a
 * command faild that should have worked.
 */
static int
check_vpd_page_support8083(struct scsi_device *sd, int (*callback)(),
        int *ppg80, int *ppg83)
{
        uchar_t *page_list;
        int     counter;
        int     rval;

        /* pages are not supported */
        *ppg80 = 0;
        *ppg83 = 0;

        /*
         * We'll set the page length to the maximum to save figuring it out
         * with an additional call.
         */
        page_list =  kmem_zalloc(MAX_INQUIRY_SIZE_EVPD,
            ((callback == SLEEP_FUNC) ? KM_SLEEP : KM_NOSLEEP));
        if (page_list == NULL)
                return (-1);            /* memory allocation problem */

        /* issue page 0 (Supported VPD Pages) INQUIRY with evpd set */
        rval = send_scsi_INQUIRY(sd, callback,
            page_list, MAX_INQUIRY_SIZE_EVPD, 1, 0, NULL, STC_VPD_CHECK);

        /*
         * Now we must validate that the device accepted the command (some
         * devices do not support it) and if the idenity pages we are
         * interested in are supported.
         */
        if ((rval == 0) &&
            (page_list[VPD_MODE_PAGE] == 0x00)) {
                /* Loop to find one of the 2 pages we need */
                counter = 4;  /* Supported pages start at byte 4, with 0x00 */

                /*
                 * Pages are returned in ascending order, and 0x83 is the
                 * last page we are hoping to find.
                 */
                while ((page_list[counter] <= 0x83) &&
                    (counter <= (page_list[VPD_PAGE_LENGTH] +
                    VPD_HEAD_OFFSET))) {
                        /*
                         * Add 3 because page_list[3] is the number of
                         * pages minus 3
                         */

                        switch (page_list[counter]) {
                        case 0x80:
                                *ppg80 = 1;
                                break;
                        case 0x83:
                                *ppg83 = 1;
                                break;
                        }
                        counter++;
                }
        }

        kmem_free(page_list, MAX_INQUIRY_SIZE_EVPD);
        return (0);
}

/*
 * Send INQUIRY command with specified EVPD and page code.  Return
 * zero on success.  On success, the amount of data transferred
 * is returned in *lenp.
 */
static int
send_scsi_INQUIRY(struct scsi_device *sd, int (*callback)(),
    uchar_t *bufaddr, size_t buflen,
    uchar_t evpd, uchar_t page_code, size_t *lenp,
    enum scsi_test_ctxt ctxt)
{
        int             (*cb_flag)();
        struct buf      *inq_bp;
        struct scsi_pkt *inq_pkt = NULL;
        int             rval = -1;

        if (lenp)
                *lenp = 0;
        if (callback != SLEEP_FUNC && callback != NULL_FUNC)
                cb_flag = NULL_FUNC;
        else
                cb_flag = callback;
        inq_bp = scsi_alloc_consistent_buf(ROUTE,
            (struct buf *)NULL, buflen, B_READ, cb_flag, NULL);
        if (inq_bp == NULL)
                goto out;               /* memory allocation problem */

        inq_pkt = scsi_init_pkt(ROUTE, (struct scsi_pkt *)NULL,
            inq_bp, CDB_GROUP0, sizeof (struct scsi_arq_status),
            0, PKT_CONSISTENT, callback, NULL);
        if (inq_pkt == NULL)
                goto out;               /* memory allocation problem */

        ASSERT(inq_bp->b_error == 0);

        /* form INQUIRY cdb with specified EVPD and page code */
        (void) scsi_setup_cdb((union scsi_cdb *)inq_pkt->pkt_cdbp,
            SCMD_INQUIRY, 0, buflen, 0);
        inq_pkt->pkt_cdbp[1] = evpd;
        inq_pkt->pkt_cdbp[2] = page_code;

        inq_pkt->pkt_time = SCSI_POLL_TIMEOUT;  /* in seconds */
        inq_pkt->pkt_flags = FLAG_NOINTR|FLAG_NOPARITY;

        /*
         * Issue inquiry command thru scsi_test
         *
         * NOTE: This is important data about device identity, not sure why
         * NOPARITY is used. Also seems like we should check pkt_stat for
         * STATE_XFERRED_DATA.
         */
        if (scsi_test(inq_pkt, ctxt) == SCSI_TEST_CMPLT_GOOD) {
                ASSERT(inq_pkt->pkt_resid >= 0);
                ASSERT(inq_pkt->pkt_resid <= buflen);

                bcopy(inq_bp->b_un.b_addr,
                    bufaddr, buflen - inq_pkt->pkt_resid);
                if (lenp)
                        *lenp = (buflen - inq_pkt->pkt_resid);
                rval = 0;
        }

        /*
         * XXX We should retry on target busy
         */

out:    if (inq_pkt)
                scsi_destroy_pkt(inq_pkt);
        if (inq_bp)
                scsi_free_consistent_buf(inq_bp);
        return (rval);
}