root/usr/src/uts/common/io/usb/scsa2usb/scsa2usb.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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2016 Joyent, Inc.
 * Copyright (c) 2016 by Delphix. All rights reserved.
 * Copyright 2019 Joshua M. Clulow <josh@sysmgr.org>
 * Copyright 2022 Tintri by DDN, Inc. All rights reserved.
 * Copyright 2023 Oxide Computer Company
 */


/*
 * scsa2usb bridge nexus driver:
 *
 * This driver supports the following wire transports:
 * a. Bulk Only transport (see usb_ms_bulkonly.c)
 * b. CB transport (see usb_ms_cbi.c)
 * c. CBI transport with interrupt status completion (see usb_ms_cbi.c)
 *
 * It handles the following command sets:
 * a. SCSI
 * b. ATAPI command set (subset of SCSI command set)
 * c. UFI command set (
 *      http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf)
 *
 * For details on USB Mass Storage Class overview:
 *      http://www.usb.org/developers/devclass_docs/usbmassover_11.pdf
 */
#if defined(lint) && !defined(DEBUG)
#define DEBUG   1
#endif

#include <sys/usb/usba/usbai_version.h>
#include <sys/scsi/scsi.h>
#include <sys/cdio.h>
#include <sys/sunndi.h>
#include <sys/esunddi.h>
#include <sys/callb.h>
#include <sys/kobj.h>
#include <sys/kobj_lex.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/sysmacros.h>

#include <sys/usb/usba.h>
#include <sys/usb/clients/ugen/usb_ugen.h>
#include <sys/usb/usba/usba_ugen.h>

#include <sys/usb/usba/usba_private.h>
#include <sys/usb/usba/usba_ugend.h>
#include <sys/usb/clients/mass_storage/usb_bulkonly.h>
#include <sys/usb/scsa2usb/scsa2usb.h>

/*
 * Function Prototypes
 */
static int      scsa2usb_attach(dev_info_t *, ddi_attach_cmd_t);
static int      scsa2usb_info(dev_info_t *, ddi_info_cmd_t, void *,
                                                void **);
static int      scsa2usb_detach(dev_info_t *, ddi_detach_cmd_t);
static int      scsa2usb_cleanup(dev_info_t *, scsa2usb_state_t *);
static void     scsa2usb_detect_quirks(scsa2usb_state_t *);
static void     scsa2usb_create_luns(scsa2usb_state_t *);
static int      scsa2usb_is_usb(dev_info_t *);
static void     scsa2usb_fake_inquiry(scsa2usb_state_t *,
                    struct scsi_inquiry *);
static void     scsa2usb_do_inquiry(scsa2usb_state_t *,
                                                uint_t, uint_t);
static int      scsa2usb_do_tur(scsa2usb_state_t *, struct scsi_address *);

/* override property handling */
static void     scsa2usb_override(scsa2usb_state_t *);
static int      scsa2usb_parse_input_str(char *, scsa2usb_ov_t *,
                    scsa2usb_state_t *);
static void     scsa2usb_override_error(char *, scsa2usb_state_t *);


/* PANIC callback handling */
static void     scsa2usb_panic_callb_init(scsa2usb_state_t *);
static void     scsa2usb_panic_callb_fini(scsa2usb_state_t *);
static boolean_t scsa2usb_panic_callb(void *, int);

/* SCSA support */
static int      scsa2usb_scsi_tgt_probe(struct scsi_device *, int (*)(void));
static int      scsa2usb_scsi_tgt_init(dev_info_t *, dev_info_t *,
                    scsi_hba_tran_t *, struct scsi_device *);
static void     scsa2usb_scsi_tgt_free(dev_info_t *, dev_info_t *,
                    scsi_hba_tran_t *, struct scsi_device *);
static struct   scsi_pkt *scsa2usb_scsi_init_pkt(struct scsi_address *,
                    struct scsi_pkt *, struct buf *, int, int,
                    int, int, int (*)(), caddr_t);
static void     scsa2usb_scsi_destroy_pkt(struct scsi_address *,
                    struct scsi_pkt *);
static int      scsa2usb_scsi_start(struct scsi_address *, struct scsi_pkt *);
static int      scsa2usb_scsi_abort(struct scsi_address *, struct scsi_pkt *);
static int      scsa2usb_scsi_reset(struct scsi_address *, int);
static int      scsa2usb_scsi_getcap(struct scsi_address *, char *, int);
static int      scsa2usb_scsi_setcap(struct scsi_address *, char *, int, int);
static int      scsa2usb_scsi_bus_config(dev_info_t *, uint_t,
                    ddi_bus_config_op_t, void *, dev_info_t **);
static int      scsa2usb_scsi_bus_unconfig(dev_info_t *, uint_t,
                    ddi_bus_config_op_t, void *);

/* functions for command and transport support */
static void     scsa2usb_prepare_pkt(scsa2usb_state_t *, struct scsi_pkt *);
static int      scsa2usb_cmd_transport(scsa2usb_state_t *, scsa2usb_cmd_t *);
static int      scsa2usb_check_bulkonly_quirks(scsa2usb_state_t *,
                    scsa2usb_cmd_t *);
static int      scsa2usb_check_ufi_quirks(scsa2usb_state_t *,
                    scsa2usb_cmd_t *);
static int      scsa2usb_handle_scsi_cmd_sub_class(scsa2usb_state_t *,
                    scsa2usb_cmd_t *, struct scsi_pkt *);
static int      scsa2usb_handle_ufi_subclass_cmd(scsa2usb_state_t *,
                    scsa2usb_cmd_t *, struct scsi_pkt *);

/* waitQ handling */
static void     scsa2usb_work_thread(void *);
static void     scsa2usb_transport_request(scsa2usb_state_t *, uint_t);
static void     scsa2usb_flush_waitQ(scsa2usb_state_t *, uint_t, uchar_t);
static int      scsa2usb_all_waitQs_empty(scsa2usb_state_t *);

/* auto request sense handling */
static int      scsa2usb_create_arq_pkt(scsa2usb_state_t *,
                    struct scsi_address *);
static void     scsa2usb_delete_arq_pkt(scsa2usb_state_t *);
static void     scsa2usb_complete_arq_pkt(scsa2usb_state_t *, struct scsi_pkt *,
                    scsa2usb_cmd_t *, struct buf *);

/* utility functions for any transport */
static int      scsa2usb_open_usb_pipes(scsa2usb_state_t *);
void            scsa2usb_close_usb_pipes(scsa2usb_state_t *);

static void     scsa2usb_fill_up_cdb_len(scsa2usb_cmd_t *, int);
static void     scsa2usb_fill_up_cdb_lba(scsa2usb_cmd_t *, uint64_t);
static void     scsa2usb_fill_up_g4_cdb_lba(scsa2usb_cmd_t *, uint64_t);
static void     scsa2usb_fill_up_ReadCD_cdb_len(scsa2usb_cmd_t *, int, int);
static void     scsa2usb_fill_up_12byte_cdb_len(scsa2usb_cmd_t *, int, int);
static void     scsa2usb_fill_up_16byte_cdb_len(scsa2usb_cmd_t *, int, int);
static int      scsa2usb_read_cd_blk_size(uchar_t);
int             scsa2usb_rw_transport(scsa2usb_state_t *, struct scsi_pkt *);
void            scsa2usb_setup_next_xfer(scsa2usb_state_t *, scsa2usb_cmd_t *);

static mblk_t   *scsa2usb_bp_to_mblk(scsa2usb_state_t *);
int             scsa2usb_handle_data_start(scsa2usb_state_t *,
                    scsa2usb_cmd_t *, usb_bulk_req_t *);
void            scsa2usb_handle_data_done(scsa2usb_state_t *,
                    scsa2usb_cmd_t *cmd, usb_bulk_req_t *);

usb_bulk_req_t *scsa2usb_init_bulk_req(scsa2usb_state_t *,
                            size_t, uint_t, usb_req_attrs_t, usb_flags_t);
int             scsa2usb_bulk_timeout(int);
int             scsa2usb_clear_ept_stall(scsa2usb_state_t *, uint_t,
                    usb_pipe_handle_t, char *);
static void     scsa2usb_pkt_completion(scsa2usb_state_t *, struct scsi_pkt *);

/* event handling */
static int      scsa2usb_reconnect_event_cb(dev_info_t *);
static int      scsa2usb_disconnect_event_cb(dev_info_t *);
static int      scsa2usb_cpr_suspend(dev_info_t *);
static void     scsa2usb_cpr_resume(dev_info_t *);
static void     scsa2usb_restore_device_state(dev_info_t *, scsa2usb_state_t *);

/* PM handling */
static void     scsa2usb_create_pm_components(dev_info_t *, scsa2usb_state_t *);
static void     scsa2usb_raise_power(scsa2usb_state_t *);
static int      scsa2usb_pwrlvl0(scsa2usb_state_t *);
static int      scsa2usb_pwrlvl1(scsa2usb_state_t *);
static int      scsa2usb_pwrlvl2(scsa2usb_state_t *);
static int      scsa2usb_pwrlvl3(scsa2usb_state_t *);
static int      scsa2usb_power(dev_info_t *, int comp, int level);
static void     scsa2usb_pm_busy_component(scsa2usb_state_t *);
static void     scsa2usb_pm_idle_component(scsa2usb_state_t *);

/* external functions for Bulk only (BO) support */
extern int      scsa2usb_bulk_only_transport(scsa2usb_state_t *,
                    scsa2usb_cmd_t *);
extern int      scsa2usb_bulk_only_get_max_lun(scsa2usb_state_t *);

/* external functions for CB/CBI support */
extern int      scsa2usb_cbi_transport(scsa2usb_state_t *, scsa2usb_cmd_t *);
extern void     scsa2usb_cbi_stop_intr_polling(scsa2usb_state_t *);


/* cmd decoding */
static char *scsa2usb_cmds[] = {
        "\000tur",
        "\001rezero",
        "\003rqsense",
        "\004format",
        "\014cartprot",
        "\022inquiry",
        "\026tranlba",
        "\030fmtverify",
        "\032modesense",
        "\033start",
        "\035snddiag",
        "\036doorlock",
        "\043formatcap",
        "\045readcap",
        "\050read10",
        "\052write10",
        "\053seek10",
        "\056writeverify",
        "\057verify",
        "\065synchcache",
        "\076readlong",
        "\077writelong",
        "\102readsubchan",
        "\103readtoc",
        "\104readhdr",
        "\105playaudio10",
        "\107playaudio_msf",
        "\110playaudio_ti",
        "\111playtrk_r10",
        "\112geteventnotify",
        "\113pause_resume",
        "\116stop/play_scan",
        "\121readdiscinfo",
        "\122readtrkinfo",
        "\123reservedtrk",
        "\124sendopcinfo",
        "\125modeselect",
        "\132modesense",
        "\133closetrksession",
        "\135sendcuesheet",
        "\136prin",
        "\137prout",
        "\210read16",
        "\212write16",
        "\241blankcd",
        "\245playaudio12",
        "\250read12",
        "\251playtrk12",
        "\252write12",
        "\254getperf",
        "\271readcdmsf",
        "\273setcdspeed",
        "\275mechanism_sts",
        "\276readcd",
        NULL
};


/*
 * Mass-Storage devices masquerade as "sd" disks.  These devices may not
 * support all SCSI CDBs in their entirety due to implementation
 * limitations.
 *
 * The following table contains a list of quirks for devices that are known to
 * misbehave.  See the comments in scsa2usb.h for a description of each
 * quirk attribute.
 */

/*
 * Either the product ID (q_pid) or the revision number (q_rev) can be a
 * wildcard match using this constant:
 */
#define X       UINT16_MAX

static struct quirk {
        uint16_t        q_vid;  /* Vendor ID */
        uint16_t        q_pid;  /* Product ID */
        uint16_t        q_rev;  /* Device revision number in BCD */
        uint16_t        q_attr; /* Quirk attributes for this device */
} scsa2usb_quirks[] = {
        /* Iomega Zip100 drive (prototype) with flaky bridge */
        {MS_IOMEGA_VID, MS_IOMEGA_PID1_ZIP100, X,
            SCSA2USB_ATTRS_GET_LUN | SCSA2USB_ATTRS_PM},

        /* Iomega Zip100 drive (newer model) with flaky bridge */
        {MS_IOMEGA_VID, MS_IOMEGA_PID2_ZIP100, X,
            SCSA2USB_ATTRS_GET_LUN | SCSA2USB_ATTRS_PM},

        /* Iomega Zip100 drive (newer model) with flaky bridge */
        {MS_IOMEGA_VID, MS_IOMEGA_PID3_ZIP100, X,
            SCSA2USB_ATTRS_GET_LUN | SCSA2USB_ATTRS_PM},

        /* Iomega Zip250 drive */
        {MS_IOMEGA_VID, MS_IOMEGA_PID_ZIP250, X, SCSA2USB_ATTRS_GET_LUN},

        /* Iomega Clik! drive */
        {MS_IOMEGA_VID, MS_IOMEGA_PID_CLIK, X,
            SCSA2USB_ATTRS_GET_LUN | SCSA2USB_ATTRS_START_STOP},

        /* Kingston DataTraveler Stick / PNY Attache Stick */
        {MS_TOSHIBA_VID, MS_TOSHIBA_PID0, X,
            SCSA2USB_ATTRS_GET_LUN},

        /* PNY Floppy drive */
        {MS_PNY_VID, MS_PNY_PID0, X,
            SCSA2USB_ATTRS_GET_LUN},

        /* SMSC floppy Device - and its clones */
        {MS_SMSC_VID, X, X, SCSA2USB_ATTRS_START_STOP},

        /* Hagiwara SmartMedia Device */
        {MS_HAGIWARA_SYS_COM_VID, MS_HAGIWARA_SYSCOM_PID1, X,
            SCSA2USB_ATTRS_GET_LUN | SCSA2USB_ATTRS_START_STOP},

        /* Hagiwara CompactFlash Device */
        {MS_HAGIWARA_SYS_COM_VID, MS_HAGIWARA_SYSCOM_PID2, X,
            SCSA2USB_ATTRS_GET_LUN | SCSA2USB_ATTRS_START_STOP},

        /* Hagiwara SmartMedia/CompactFlash Combo Device */
        {MS_HAGIWARA_SYS_COM_VID, MS_HAGIWARA_SYSCOM_PID3, X,
            SCSA2USB_ATTRS_START_STOP},

        /* Hagiwara new SM Device */
        {MS_HAGIWARA_SYS_COM_VID, MS_HAGIWARA_SYSCOM_PID4, X,
            SCSA2USB_ATTRS_GET_LUN | SCSA2USB_ATTRS_START_STOP},

        /* Hagiwara new CF Device */
        {MS_HAGIWARA_SYS_COM_VID, MS_HAGIWARA_SYSCOM_PID5, X,
            SCSA2USB_ATTRS_GET_LUN | SCSA2USB_ATTRS_START_STOP},

        /* Mitsumi CD-RW Device(s) */
        {MS_MITSUMI_VID, X, X, SCSA2USB_ATTRS_BIG_TIMEOUT |
            SCSA2USB_ATTRS_GET_CONF | SCSA2USB_ATTRS_GET_PERF},

        /* Neodio Technologies Corporation SM/CF/MS/SD Combo Device */
        {MS_NEODIO_VID, MS_NEODIO_DEVICE_3050, X,
            SCSA2USB_ATTRS_MODE_SENSE },

        /* dumb flash devices */
        {MS_SONY_FLASH_VID, MS_SONY_FLASH_PID, X,
            SCSA2USB_ATTRS_REDUCED_CMD},

        {MS_TREK_FLASH_VID, MS_TREK_FLASH_PID, X,
            SCSA2USB_ATTRS_REDUCED_CMD},

        {MS_PENN_FLASH_VID, MS_PENN_FLASH_PID, X,
            SCSA2USB_ATTRS_REDUCED_CMD},

        /* SimpleTech UCF-100 CF Device */
        {MS_SIMPLETECH_VID, MS_SIMPLETECH_PID1, X,
            SCSA2USB_ATTRS_REDUCED_CMD},

        {MS_ADDONICS_CARD_READER_VID, MS_ADDONICS_CARD_READER_PID,
            X, SCSA2USB_ATTRS_REDUCED_CMD},

        /* Acomdata 80GB USB/1394 Hard Disk */
        {MS_ACOMDATA_VID, MS_ACOMDATA_PID1, X,
            SCSA2USB_ATTRS_USE_CSW_RESIDUE},

        /* OTi6828 Flash Disk */
        {MS_OTI_VID, MS_OTI_DEVICE_6828, X,
            SCSA2USB_ATTRS_USE_CSW_RESIDUE},

        /* AMI Virtual Floppy */
        {MS_AMI_VID, MS_AMI_VIRTUAL_FLOPPY, X,
            SCSA2USB_ATTRS_NO_MEDIA_CHECK},

        /* ScanLogic USB Storage Device */
        {MS_SCANLOGIC_VID, MS_SCANLOGIC_PID1, X,
            SCSA2USB_ATTRS_NO_CAP_ADJUST},

        /* Super Top USB 2.0 IDE Device */
        {MS_SUPERTOP_VID, MS_SUPERTOP_DEVICE_6600, X,
            SCSA2USB_ATTRS_USE_CSW_RESIDUE},

        /* Aigo Miniking Device NEHFSP14 */
        {MS_AIGO_VID, MS_AIGO_DEVICE_6981, X,
            SCSA2USB_ATTRS_USE_CSW_RESIDUE},

        /* Alcor Micro Corp 6387 flash disk */
        {MS_ALCOR_VID, MS_ALCOR_PID0, X,
            SCSA2USB_ATTRS_GET_LUN | SCSA2USB_ATTRS_USE_CSW_RESIDUE},

        /* Western Digital External HDD */
        {MS_WD_VID, MS_WD_PID, X,
            SCSA2USB_ATTRS_INQUIRY_EVPD},

        /* Insyde Virtual CD-ROM */
        {MS_INSYDE_VID, MS_INSYDE_PID_CDROM, X,
            SCSA2USB_ATTRS_MODE_SENSE},
};


/*
 * Attribute values can be overridden by values
 * contained in the scsa2usb.conf file.
 * These arrays define possible user input values.
 */

struct scsa2usb_subclass_protocol_override {
        char    *name;
        int     value;
};

static struct scsa2usb_subclass_protocol_override scsa2usb_protocol[] =  {
        {"CB", SCSA2USB_CB_PROTOCOL},
        {"CBI", SCSA2USB_CBI_PROTOCOL},
        {"BO", SCSA2USB_BULK_ONLY_PROTOCOL}
};

static struct scsa2usb_subclass_protocol_override scsa2usb_subclass[] = {
        {"SCSI", SCSA2USB_SCSI_CMDSET},
        {"ATAPI", SCSA2USB_ATAPI_CMDSET},
        {"UFI", SCSA2USB_UFI_CMDSET}
};


#define N_SCSA2USB_SUBC_OVERRIDE (sizeof (scsa2usb_subclass))/ \
                        sizeof (struct scsa2usb_subclass_protocol_override)

#define N_SCSA2USB_PROT_OVERRIDE (sizeof (scsa2usb_protocol))/ \
                        sizeof (struct scsa2usb_subclass_protocol_override)

/* global variables */
static void *scsa2usb_statep;                           /* for soft state */
static boolean_t scsa2usb_sync_message = B_TRUE;        /* for syncing */

/* for debug messages */
uint_t  scsa2usb_errmask        = (uint_t)DPRINT_MASK_ALL;
uint_t  scsa2usb_errlevel       = USB_LOG_L4;
uint_t  scsa2usb_instance_debug = (uint_t)-1;
uint_t  scsa2usb_scsi_bus_config_debug = 0;
uint_t  scsa2usb_long_timeout   = 50 * SCSA2USB_BULK_PIPE_TIMEOUT;


/*
 * Some devices have problems with big bulk transfers,
 * transfers >= 128kbytes hang the device.  This tunable allows to
 * limit the maximum bulk transfers rate.
 */
uint_t  scsa2usb_max_bulk_xfer_size = SCSA2USB_MAX_BULK_XFER_SIZE;


#ifdef  SCSA2USB_BULK_ONLY_TEST
/*
 * Test BO 13 cases. (See USB Mass Storage Class - Bulk Only Transport).
 * We are not covering test cases 1, 6, and 12 as these are the "good"
 * test cases and are tested as part of the normal drive access operations.
 *
 * NOTE: This is for testing only. It will be replaced by a uscsi test.
 * Some are listed here while; other test cases are moved to usb_bulkonly.c
 */
static int scsa2usb_test_case_5 = 0;
int scsa2usb_test_case_8 = 0;
int scsa2usb_test_case_10 = 0;
static int scsa2usb_test_case_11 = 0;

static void     scsa2usb_test_mblk(scsa2usb_state_t *, boolean_t);
#endif  /* SCSA2USB_BULK_ONLY_TEST */

static int      scsa2usb_ugen_open(dev_t *, int, int, cred_t *);
static int      scsa2usb_ugen_close(dev_t, int, int, cred_t *);
static int      scsa2usb_ugen_read(dev_t, struct uio *, cred_t *);
static int      scsa2usb_ugen_write(dev_t, struct uio *, cred_t *);
static int      scsa2usb_ugen_poll(dev_t, short, int,  short *,
                                                struct pollhead **);

/* scsa2usb cb_ops */
static struct cb_ops scsa2usb_cbops = {
        scsa2usb_ugen_open,     /* open  */
        scsa2usb_ugen_close,    /* close */
        nodev,                  /* strategy */
        nodev,                  /* print */
        nodev,                  /* dump */
        scsa2usb_ugen_read,     /* read */
        scsa2usb_ugen_write,    /* write */
        nodev,                  /* ioctl */
        nodev,                  /* devmap */
        nodev,                  /* mmap */
        nodev,                  /* segmap */
        scsa2usb_ugen_poll,     /* poll */
        ddi_prop_op,            /* prop_op */
        NULL,                   /* stream */
        D_MP,                   /* cb_flag */
        CB_REV,                 /* rev */
        nodev,                  /* int (*cb_aread)() */
        nodev                   /* int (*cb_awrite)() */
};

/* modloading support */
static struct dev_ops scsa2usb_ops = {
        DEVO_REV,               /* devo_rev, */
        0,                      /* refcnt  */
        scsa2usb_info,          /* info */
        nulldev,                /* identify */
        nulldev,                /* probe */
        scsa2usb_attach,        /* attach */
        scsa2usb_detach,        /* detach */
        nodev,                  /* reset */
        &scsa2usb_cbops,        /* driver operations */
        NULL,                   /* bus operations */
        scsa2usb_power,         /* power */
        ddi_quiesce_not_needed,         /* quiesce */
};

static struct modldrv modldrv = {
        &mod_driverops,                 /* Module type. This one is a driver */
        "SCSA to USB Driver",   /* Name of the module. */
        &scsa2usb_ops,                  /* driver ops */
};

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

/* event support */
static usb_event_t scsa2usb_events = {
        scsa2usb_disconnect_event_cb,
        scsa2usb_reconnect_event_cb,
        NULL, NULL
};

int
_init(void)
{
        int rval;

        if (((rval = ddi_soft_state_init(&scsa2usb_statep,
            sizeof (scsa2usb_state_t), SCSA2USB_INITIAL_ALLOC)) != 0)) {

                return (rval);
        }

        if ((rval = scsi_hba_init(&modlinkage)) != 0) {
                ddi_soft_state_fini(&scsa2usb_statep);

                return (rval);
        }

        if ((rval = mod_install(&modlinkage)) != 0) {
                scsi_hba_fini(&modlinkage);
                ddi_soft_state_fini(&scsa2usb_statep);

                return (rval);
        }

        return (rval);
}


int
_fini(void)
{
        int     rval;

        if ((rval = mod_remove(&modlinkage)) == 0) {
                scsi_hba_fini(&modlinkage);
                ddi_soft_state_fini(&scsa2usb_statep);
        }

        return (rval);
}


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


/*
 * scsa2usb_info :
 *      Get minor number, soft state structure etc.
 */
/*ARGSUSED*/
static int
scsa2usb_info(dev_info_t *dip, ddi_info_cmd_t infocmd,
    void *arg, void **result)
{
        scsa2usb_state_t *scsa2usbp = NULL;
        int error = DDI_FAILURE;
        int instance = SCSA2USB_MINOR_TO_INSTANCE(getminor((dev_t)arg));

        switch (infocmd) {
        case DDI_INFO_DEVT2DEVINFO:
                if (((scsa2usbp = ddi_get_soft_state(scsa2usb_statep,
                    instance)) != NULL) &&
                    scsa2usbp->scsa2usb_dip) {
                        *result = scsa2usbp->scsa2usb_dip;
                        error = DDI_SUCCESS;
                } else {
                        *result = NULL;
                }
                break;
        case DDI_INFO_DEVT2INSTANCE:
                *result = (void *)(uintptr_t)instance;
                error = DDI_SUCCESS;
                break;
        default:
                break;
        }

        return (error);
}


/*
 * scsa2usb_attach:
 *      Attach driver
 *      Allocate a "scsi_hba_tran" - call scsi_hba_tran_alloc()
 *      Invoke scsi_hba_attach_setup
 *      Get the serialno of the device
 *      Open bulk pipes
 *      Create disk child(ren)
 *      Register events
 *      Create and register panic callback
 *
 * NOTE: Replaced CBW_DIR_OUT with USB_EP_DIR_OUT and CBW_DIR_IN with
 * USB_EP_DIR_IN as they are the same #defines.
 */
static int
scsa2usb_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        int                     instance = ddi_get_instance(dip);
        int                     interface;
        uint_t                  lun;
        boolean_t               ept_check = B_TRUE;
        scsi_hba_tran_t         *tran;          /* scsi transport */
        scsa2usb_state_t        *scsa2usbp;
        usb_log_handle_t        log_handle;
        usb_ep_data_t           *ep_data;
        usb_client_dev_data_t   *dev_data;
        usb_alt_if_data_t       *altif_data;
        usb_ugen_info_t         usb_ugen_info;

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, NULL,
            "scsa2usb_attach: dip = 0x%p", (void *)dip);

        switch (cmd) {
        case DDI_ATTACH:
                break;
        case DDI_RESUME:
                scsa2usb_cpr_resume(dip);

                return (DDI_SUCCESS);
        default:
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, NULL,
                    "scsa2usb_attach: failed");

                return (DDI_FAILURE);
        }

        /* Allocate softc information */
        if (ddi_soft_state_zalloc(scsa2usb_statep, instance) != DDI_SUCCESS) {
                ddi_prop_remove_all(dip);

                return (DDI_FAILURE);
        }

        /* get soft state space and initialize */
        if ((scsa2usbp = ddi_get_soft_state(scsa2usb_statep,
            instance)) == NULL) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, NULL,
                    "scsa2usb%d: bad soft state", instance);
                ddi_prop_remove_all(dip);

                return (DDI_FAILURE);
        }

        scsa2usbp->scsa2usb_dip         = dip;
        scsa2usbp->scsa2usb_instance    = instance;

        /* allocate a log handle for debug/error messages */
        scsa2usbp->scsa2usb_log_handle = log_handle =
            usb_alloc_log_hdl(dip, "s2u",
            &scsa2usb_errlevel,
            &scsa2usb_errmask, &scsa2usb_instance_debug,
            0);

        /* attach to USBA */
        if (usb_client_attach(dip, USBDRV_VERSION, 0) != USB_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, log_handle,
                    "usb_client_attach failed");

                goto fail;
        }
        if (usb_get_dev_data(dip, &dev_data, USB_PARSE_LVL_IF, 0) !=
            USB_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, log_handle,
                    "usb_get_dev_data failed");

                goto fail;
        }

        /* initialize the mutex with the right cookie */
        mutex_init(&scsa2usbp->scsa2usb_mutex, NULL, MUTEX_DRIVER,
            dev_data->dev_iblock_cookie);
        cv_init(&scsa2usbp->scsa2usb_transport_busy_cv, NULL, CV_DRIVER, NULL);

        for (lun = 0; lun < SCSA2USB_MAX_LUNS; lun++) {
                usba_init_list(&scsa2usbp->scsa2usb_waitQ[lun], NULL,
                    dev_data->dev_iblock_cookie);
        }
        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        scsa2usbp->scsa2usb_dip         = dip;
        scsa2usbp->scsa2usb_instance    = instance;
        /*
         * Devices begin with all attributes enabled.  Attributes may be
         * disabled later through detected quirks or through the configuration
         * file.
         */
        scsa2usbp->scsa2usb_attrs       = SCSA2USB_ALL_ATTRS;
        scsa2usbp->scsa2usb_dev_data    = dev_data;

        /* save the default pipe handle */
        scsa2usbp->scsa2usb_default_pipe = dev_data->dev_default_ph;

        /* basic inits are done */
        scsa2usbp->scsa2usb_flags |= SCSA2USB_FLAGS_LOCKS_INIT;

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, log_handle,
            "curr_cfg=%ld, curr_if=%d",
            (long)(dev_data->dev_curr_cfg - &dev_data->dev_cfg[0]),
            dev_data->dev_curr_if);

        interface = dev_data->dev_curr_if;
        scsa2usbp->scsa2usb_intfc_num = dev_data->dev_curr_if;

        /* now find out relevant descriptors for alternate 0 */
        altif_data = &dev_data->dev_curr_cfg->cfg_if[interface].if_alt[0];

        if (altif_data->altif_n_ep == 0) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, log_handle,
                    "invalid alt 0 for interface %d", interface);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                goto fail;
        }

        /* All CB/CBI, BO devices should have this value set */
        if (altif_data->altif_descr.bInterfaceClass !=
            USB_CLASS_MASS_STORAGE) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, log_handle,
                    "invalid interface class (0x%x)",
                    altif_data->altif_descr.bInterfaceClass);
        }
        scsa2usbp->scsa2usb_intfc_descr = altif_data->altif_descr;

        /* figure out the endpoints and copy the descr */
        if ((ep_data = usb_lookup_ep_data(dip, dev_data, interface, 0, 0,
            USB_EP_ATTR_BULK, USB_EP_DIR_OUT)) != NULL) {
                if (usb_ep_xdescr_fill(USB_EP_XDESCR_CURRENT_VERSION,
                    dip, ep_data, &scsa2usbp->scsa2usb_bulkout_xept) !=
                    USB_SUCCESS) {

                        mutex_exit(&scsa2usbp->scsa2usb_mutex);

                        goto fail;
                }
        }
        if ((ep_data = usb_lookup_ep_data(dip, dev_data, interface, 0, 0,
            USB_EP_ATTR_BULK, USB_EP_DIR_IN)) != NULL) {
                if (usb_ep_xdescr_fill(USB_EP_XDESCR_CURRENT_VERSION,
                    dip, ep_data, &scsa2usbp->scsa2usb_bulkin_xept) !=
                    USB_SUCCESS) {

                        mutex_exit(&scsa2usbp->scsa2usb_mutex);

                        goto fail;
                }
        }
        if ((ep_data = usb_lookup_ep_data(dip, dev_data, interface, 0, 0,
            USB_EP_ATTR_INTR, USB_EP_DIR_IN)) != NULL) {
                if (usb_ep_xdescr_fill(USB_EP_XDESCR_CURRENT_VERSION,
                    dip, ep_data, &scsa2usbp->scsa2usb_intr_xept) !=
                    USB_SUCCESS) {

                        mutex_exit(&scsa2usbp->scsa2usb_mutex);

                        goto fail;
                }
        }

        /*
         * check here for protocol and subclass supported by this driver
         *
         * first check if conf file has override values
         * Note: override values are not used if supplied values are legal
         */
        scsa2usb_override(scsa2usbp);

        USB_DPRINTF_L3(DPRINT_MASK_SCSA, log_handle,
            "protocol=0x%x override=0x%x subclass=0x%x override=0x%x",
            scsa2usbp->scsa2usb_intfc_descr.bInterfaceProtocol,
            scsa2usbp->scsa2usb_protocol_override,
            scsa2usbp->scsa2usb_intfc_descr.bInterfaceSubClass,
            scsa2usbp->scsa2usb_subclass_override);

        switch (scsa2usbp->scsa2usb_intfc_descr.bInterfaceProtocol) {
        case USB_PROTO_MS_CBI:
                scsa2usbp->scsa2usb_cmd_protocol |= SCSA2USB_CB_PROTOCOL;
                break;
        case USB_PROTO_MS_CBI_WC:
                scsa2usbp->scsa2usb_cmd_protocol |= SCSA2USB_CBI_PROTOCOL;
                break;
        case USB_PROTO_MS_ISD_1999_SILICN:
        case USB_PROTO_MS_BULK_ONLY:
                scsa2usbp->scsa2usb_cmd_protocol |= SCSA2USB_BULK_ONLY_PROTOCOL;
                break;
        default:
                if (scsa2usbp->scsa2usb_protocol_override) {
                        scsa2usbp->scsa2usb_cmd_protocol |=
                            scsa2usbp->scsa2usb_protocol_override;
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA, log_handle,
                            "overriding protocol %x",
                            scsa2usbp->scsa2usb_intfc_descr.bInterfaceProtocol);
                        break;
                }

                USB_DPRINTF_L2(DPRINT_MASK_SCSA, log_handle,
                    "unsupported protocol = %x",
                    scsa2usbp->scsa2usb_intfc_descr.bInterfaceProtocol);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                goto fail;
        }

        switch (scsa2usbp->scsa2usb_intfc_descr.bInterfaceSubClass) {
        case USB_SUBCLS_MS_SCSI:                /* transparent SCSI */
                scsa2usbp->scsa2usb_cmd_protocol |= SCSA2USB_SCSI_CMDSET;
                break;
        case USB_SUBCLS_MS_SFF8020I:
        case USB_SUBCLS_MS_SFF8070I:
                scsa2usbp->scsa2usb_cmd_protocol |= SCSA2USB_ATAPI_CMDSET;
                break;
        case USB_SUBCLS_MS_UFI:         /* UFI */
                scsa2usbp->scsa2usb_cmd_protocol |= SCSA2USB_UFI_CMDSET;
                break;
        default:
                if (scsa2usbp->scsa2usb_subclass_override) {
                        scsa2usbp->scsa2usb_cmd_protocol |=
                            scsa2usbp->scsa2usb_subclass_override;
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA, log_handle,
                            "overriding subclass %x",
                            scsa2usbp->scsa2usb_intfc_descr.bInterfaceSubClass);
                        break;
                }

                USB_DPRINTF_L2(DPRINT_MASK_SCSA, log_handle,
                    "unsupported subclass = %x",
                    scsa2usbp->scsa2usb_intfc_descr.bInterfaceSubClass);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                goto fail;
        }

        /* check that we have the right set of endpoint descriptors */
        if (SCSA2USB_IS_BULK_ONLY(scsa2usbp) || SCSA2USB_IS_CB(scsa2usbp)) {
                if ((scsa2usbp->scsa2usb_bulkout_ept.bLength == 0) ||
                    (scsa2usbp->scsa2usb_bulkin_ept.bLength == 0)) {
                        ept_check = B_FALSE;
                }
        } else if (SCSA2USB_IS_CBI(scsa2usbp)) {
                if ((scsa2usbp->scsa2usb_bulkout_ept.bLength == 0) ||
                    (scsa2usbp->scsa2usb_bulkin_ept.bLength == 0) ||
                    (scsa2usbp->scsa2usb_intr_ept.bLength == 0)) {
                        ept_check = B_FALSE;
                }
        }

        if (ept_check == B_FALSE) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, log_handle,
                    "scsa2usb%d doesn't support minimum required endpoints",
                    instance);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                goto fail;
        }

        scsa2usb_detect_quirks(scsa2usbp);

        /* Print the serial number from the registration data */
        if (scsa2usbp->scsa2usb_dev_data->dev_serial) {
                USB_DPRINTF_L4(DPRINT_MASK_SCSA,
                    scsa2usbp->scsa2usb_log_handle, "Serial Number = %s",
                    scsa2usbp->scsa2usb_dev_data->dev_serial);
        }

        /*
         * Allocate a SCSA transport structure
         */
        tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP);
        scsa2usbp->scsa2usb_tran = tran;

        /*
         * initialize transport structure
         */
        tran->tran_hba_private          = scsa2usbp;
        tran->tran_tgt_private          = NULL;
        tran->tran_tgt_init             = scsa2usb_scsi_tgt_init;
        tran->tran_tgt_probe            = scsa2usb_scsi_tgt_probe;
        tran->tran_tgt_free             = scsa2usb_scsi_tgt_free;
        tran->tran_start                = scsa2usb_scsi_start;
        tran->tran_abort                = scsa2usb_scsi_abort;
        tran->tran_reset                = scsa2usb_scsi_reset;
        tran->tran_getcap               = scsa2usb_scsi_getcap;
        tran->tran_setcap               = scsa2usb_scsi_setcap;
        tran->tran_init_pkt             = scsa2usb_scsi_init_pkt;
        tran->tran_destroy_pkt          = scsa2usb_scsi_destroy_pkt;
        tran->tran_dmafree              = NULL;
        tran->tran_sync_pkt             = NULL;
        tran->tran_reset_notify         = NULL;
        tran->tran_get_bus_addr         = NULL;
        tran->tran_get_name             = NULL;
        tran->tran_quiesce              = NULL;
        tran->tran_unquiesce            = NULL;
        tran->tran_bus_reset            = NULL;
        tran->tran_add_eventcall        = NULL;
        tran->tran_get_eventcookie      = NULL;
        tran->tran_post_event           = NULL;
        tran->tran_remove_eventcall     = NULL;
        tran->tran_bus_config           = scsa2usb_scsi_bus_config;
        tran->tran_bus_unconfig         = scsa2usb_scsi_bus_unconfig;

        /*
         * register with SCSA as an HBA
         * Note that the dma attributes are from parent nexus
         */
        if (scsi_hba_attach_setup(dip, usba_get_hc_dma_attr(dip), tran, 0)) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, log_handle,
                    "scsi_hba_attach_setup failed");
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                goto fail;
        }

        scsa2usbp->scsa2usb_flags |= SCSA2USB_FLAGS_HBA_ATTACH_SETUP;

        /* create minor node */
        if (ddi_create_minor_node(dip, "scsa2usb", S_IFCHR,
            instance << SCSA2USB_MINOR_INSTANCE_SHIFT,
            DDI_NT_SCSI_NEXUS, 0) != DDI_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "scsi_attach: ddi_create_minor_node failed");
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                goto fail;
        }

        /* open pipes and set scsa2usb_flags */
        if (scsa2usb_open_usb_pipes(scsa2usbp) == USB_FAILURE) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, log_handle,
                    "error opening pipes");
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                goto fail;
        }

        /* set default block size. updated after read cap cmd */
        for (lun = 0; lun < SCSA2USB_MAX_LUNS; lun++) {
                scsa2usbp->scsa2usb_lbasize[lun] = DEV_BSIZE;
        }

        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        /* initialize PANIC callback */
        scsa2usb_panic_callb_init(scsa2usbp);

        /* finally we are all done 'initializing' the device */
        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        scsa2usbp->scsa2usb_dev_state = USB_DEV_ONLINE;

        /* enable PM, mutex needs to be held across this */
        scsa2usb_create_pm_components(dip, scsa2usbp);
        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        /* register for connect/disconnect events */
        if (usb_register_event_cbs(scsa2usbp->scsa2usb_dip, &scsa2usb_events,
            0) != USB_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, log_handle,
                    "error cb registering");
                goto fail;
        }

        /* free the dev_data tree, we no longer need it */
        usb_free_descr_tree(dip, dev_data);

        scsa2usb_pm_idle_component(scsa2usbp);

        /* log the conf file override string if there is one */
        if (scsa2usbp->scsa2usb_override_str) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb.conf override: %s",
                    scsa2usbp->scsa2usb_override_str);
        }

        if (usb_owns_device(dip)) {
                /* get a ugen handle */
                bzero(&usb_ugen_info, sizeof (usb_ugen_info));
                usb_ugen_info.usb_ugen_flags = 0;
                usb_ugen_info.usb_ugen_minor_node_ugen_bits_mask =
                    (dev_t)SCSA2USB_MINOR_UGEN_BITS_MASK;
                usb_ugen_info.usb_ugen_minor_node_instance_mask =
                    (dev_t)~SCSA2USB_MINOR_UGEN_BITS_MASK;
                scsa2usbp->scsa2usb_ugen_hdl =
                    usb_ugen_get_hdl(dip, &usb_ugen_info);

                if (usb_ugen_attach(scsa2usbp->scsa2usb_ugen_hdl, cmd) !=
                    USB_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "usb_ugen_attach failed");

                        usb_ugen_release_hdl(scsa2usbp->scsa2usb_ugen_hdl);
                        scsa2usbp->scsa2usb_ugen_hdl = NULL;
                }
        }

        /* report device */
        ddi_report_dev(dip);

        return (DDI_SUCCESS);

fail:
        if (scsa2usbp) {
                (void) scsa2usb_cleanup(dip, scsa2usbp);
        }

        return (DDI_FAILURE);
}


/*
 * scsa2usb_detach:
 *      detach or suspend driver instance
 */
static int
scsa2usb_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        scsi_hba_tran_t *tran;
        scsa2usb_state_t *scsa2usbp;
        int rval;

        tran = ddi_get_driver_private(dip);
        ASSERT(tran != NULL);

        scsa2usbp = (scsa2usb_state_t *)tran->tran_hba_private;
        ASSERT(scsa2usbp);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_detach: dip = 0x%p, cmd = %d", (void *)dip, cmd);

        switch (cmd) {
        case DDI_DETACH:

                if (scsa2usb_cleanup(dip, scsa2usbp) != USB_SUCCESS) {

                        return (DDI_FAILURE);
                }

                return (DDI_SUCCESS);
        case DDI_SUSPEND:
                rval = scsa2usb_cpr_suspend(dip);

                return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE);
        default:

                return (DDI_FAILURE);
        }
}

/*
 * ugen support
 */
/*
 * scsa2usb_ugen_open()
 * (all ugen opens and pipe opens are by definition exclusive so it is OK
 * to count opens)
 */
static int
scsa2usb_ugen_open(dev_t *devp, int flag, int sflag, cred_t *cr)
{
        scsa2usb_state_t *scsa2usbp;
        int             rval;

        if ((scsa2usbp = ddi_get_soft_state(scsa2usb_statep,
            SCSA2USB_MINOR_TO_INSTANCE(getminor(*devp)))) == NULL) {
                /* deferred detach */

                return (ENXIO);
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_ugen_open: dev_t=0x%lx", *devp);

        mutex_enter(&scsa2usbp->scsa2usb_mutex);

        /* if this is the first ugen open, check on transport busy */
        if (scsa2usbp->scsa2usb_busy_proc != curproc) {
                while (scsa2usbp->scsa2usb_transport_busy ||
                    (scsa2usb_all_waitQs_empty(scsa2usbp) !=
                    USB_SUCCESS)) {
                        rval = cv_wait_sig(
                            &scsa2usbp->scsa2usb_transport_busy_cv,
                            &scsa2usbp->scsa2usb_mutex);
                        if (rval == 0) {
                                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                                return (EINTR);
                        }
                }
                scsa2usbp->scsa2usb_transport_busy++;
                scsa2usbp->scsa2usb_busy_proc = curproc;
        }

        scsa2usbp->scsa2usb_ugen_open_count++;

        scsa2usb_raise_power(scsa2usbp);

        scsa2usb_close_usb_pipes(scsa2usbp);

        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        rval = usb_ugen_open(scsa2usbp->scsa2usb_ugen_hdl, devp, flag,
            sflag, cr);
        if (!rval) {
                /*
                 * if usb_ugen_open() succeeded, we'll change the minor number
                 * so that we can keep track of every open()/close() issued by
                 * the userland processes. We need to pick a minor number that
                 * is not used by the ugen framework
                 */

                usb_ugen_hdl_impl_t     *usb_ugen_hdl_impl;
                ugen_state_t            *ugenp;
                int                     ugen_minor, clone;

                mutex_enter(&scsa2usbp->scsa2usb_mutex);

                usb_ugen_hdl_impl =
                    (usb_ugen_hdl_impl_t *)scsa2usbp->scsa2usb_ugen_hdl;
                ugenp =  usb_ugen_hdl_impl->hdl_ugenp;

                /* 'clone' is bigger than any ugen minor in use */
                for (clone = ugenp->ug_minor_node_table_index + 1;
                    clone < SCSA2USB_MAX_CLONE; clone++) {
                        if (!scsa2usbp->scsa2usb_clones[clone])
                                break;
                }

                if (clone >= SCSA2USB_MAX_CLONE) {
                        cmn_err(CE_WARN, "scsa2usb_ugen_open: too many clones");
                        rval = EBUSY;
                        mutex_exit(&scsa2usbp->scsa2usb_mutex);
                        goto open_done;
                }

                ugen_minor = getminor(*devp) & SCSA2USB_MINOR_UGEN_BITS_MASK;
                *devp = makedevice(getmajor(*devp),
                    (scsa2usbp->scsa2usb_instance
                    << SCSA2USB_MINOR_INSTANCE_SHIFT)
                    + clone);

                /* save the ugen minor */
                scsa2usbp->scsa2usb_clones[clone] = (uint8_t)ugen_minor;
                USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_ugen_open: new dev=%lx, old minor=%x",
                    *devp, ugen_minor);

                mutex_exit(&scsa2usbp->scsa2usb_mutex);
        }

open_done:

        if (rval) {
                mutex_enter(&scsa2usbp->scsa2usb_mutex);

                /* reopen the pipes */
                if (--scsa2usbp->scsa2usb_ugen_open_count == 0) {
                        scsa2usbp->scsa2usb_transport_busy--;
                        scsa2usbp->scsa2usb_busy_proc = NULL;
                        cv_signal(&scsa2usbp->scsa2usb_transport_busy_cv);
                }
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                scsa2usb_pm_idle_component(scsa2usbp);
        }

        return (rval);
}


/*
 * scsa2usb_ugen_close()
 */
static int
scsa2usb_ugen_close(dev_t dev, int flag, int otype, cred_t *cr)
{
        int rval;
        int     ugen_minor, clone;

        scsa2usb_state_t *scsa2usbp = ddi_get_soft_state(scsa2usb_statep,
            SCSA2USB_MINOR_TO_INSTANCE(getminor(dev)));

        if (scsa2usbp == NULL) {

                return (ENXIO);
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_ugen_close: dev_t=0x%lx", dev);

        clone = getminor(dev) & SCSA2USB_MINOR_UGEN_BITS_MASK;
        ugen_minor = scsa2usbp->scsa2usb_clones[clone];
        dev = makedevice(getmajor(dev),
            (scsa2usbp->scsa2usb_instance << SCSA2USB_MINOR_INSTANCE_SHIFT)
            + ugen_minor);
        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_ugen_close: old dev=%lx", dev);
        rval = usb_ugen_close(scsa2usbp->scsa2usb_ugen_hdl, dev, flag,
            otype, cr);

        if (rval == 0) {
                mutex_enter(&scsa2usbp->scsa2usb_mutex);

                scsa2usbp->scsa2usb_clones[clone] = 0;
                /* reopen the pipes */
                if (--scsa2usbp->scsa2usb_ugen_open_count == 0) {
                        scsa2usbp->scsa2usb_transport_busy--;
                        scsa2usbp->scsa2usb_busy_proc = NULL;
                        cv_signal(&scsa2usbp->scsa2usb_transport_busy_cv);
                }
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                scsa2usb_pm_idle_component(scsa2usbp);
        }

        return (rval);
}


/*
 * scsa2usb_ugen_read/write()
 */
/*ARGSUSED*/
static int
scsa2usb_ugen_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
        int clone, ugen_minor;
        scsa2usb_state_t *scsa2usbp = ddi_get_soft_state(scsa2usb_statep,
            SCSA2USB_MINOR_TO_INSTANCE(getminor(dev)));

        if (scsa2usbp == NULL) {

                return (ENXIO);
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_ugen_read: dev_t=0x%lx", dev);

        clone = getminor(dev) & SCSA2USB_MINOR_UGEN_BITS_MASK;
        ugen_minor = scsa2usbp->scsa2usb_clones[clone];
        dev = makedevice(getmajor(dev),
            (scsa2usbp->scsa2usb_instance << SCSA2USB_MINOR_INSTANCE_SHIFT)
            + ugen_minor);

        return (usb_ugen_read(scsa2usbp->scsa2usb_ugen_hdl, dev,
            uiop, credp));
}


/*ARGSUSED*/
static int
scsa2usb_ugen_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
        int clone, ugen_minor;
        scsa2usb_state_t *scsa2usbp = ddi_get_soft_state(scsa2usb_statep,
            SCSA2USB_MINOR_TO_INSTANCE(getminor(dev)));

        if (scsa2usbp == NULL) {

                return (ENXIO);
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_ugen_write: dev_t=0x%lx", dev);

        clone = getminor(dev) & SCSA2USB_MINOR_UGEN_BITS_MASK;
        ugen_minor = scsa2usbp->scsa2usb_clones[clone];
        dev = makedevice(getmajor(dev),
            (scsa2usbp->scsa2usb_instance << SCSA2USB_MINOR_INSTANCE_SHIFT)
            + ugen_minor);

        return (usb_ugen_write(scsa2usbp->scsa2usb_ugen_hdl,
            dev, uiop, credp));
}


/*
 * scsa2usb_ugen_poll
 */
static int
scsa2usb_ugen_poll(dev_t dev, short events,
    int anyyet,  short *reventsp, struct pollhead **phpp)
{
        int clone, ugen_minor;
        scsa2usb_state_t *scsa2usbp = ddi_get_soft_state(scsa2usb_statep,
            SCSA2USB_MINOR_TO_INSTANCE(getminor(dev)));

        if (scsa2usbp == NULL) {

                return (ENXIO);
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_ugen_poll: dev_t=0x%lx", dev);

        clone = getminor(dev) & SCSA2USB_MINOR_UGEN_BITS_MASK;
        ugen_minor = scsa2usbp->scsa2usb_clones[clone];
        dev = makedevice(getmajor(dev),
            (scsa2usbp->scsa2usb_instance << SCSA2USB_MINOR_INSTANCE_SHIFT)
            + ugen_minor);

        return (usb_ugen_poll(scsa2usbp->scsa2usb_ugen_hdl, dev, events,
            anyyet, reventsp, phpp));
}


/*
 * scsa2usb_cleanup:
 *      cleanup whatever attach has setup
 */
static int
scsa2usb_cleanup(dev_info_t *dip, scsa2usb_state_t *scsa2usbp)
{
        int             rval, i;
        scsa2usb_power_t *pm;
        uint_t          lun;

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_cleanup:");

        /* wait till the work thread is done */
        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        for (i = 0; i < SCSA2USB_DRAIN_TIMEOUT; i++) {
                if (scsa2usbp->scsa2usb_work_thread_id == NULL) {

                        break;
                }
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
                delay(drv_usectohz(1000000));
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
        }
        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        if (i >= SCSA2USB_DRAIN_TIMEOUT) {

                return (USB_FAILURE);
        }

        /*
         * Disable the event callbacks first, after this point, event
         * callbacks will never get called. Note we shouldn't hold
         * mutex while unregistering events because there may be a
         * competing event callback thread. Event callbacks are done
         * with ndi mutex held and this can cause a potential deadlock.
         */
        usb_unregister_event_cbs(scsa2usbp->scsa2usb_dip, &scsa2usb_events);

        if (scsa2usbp->scsa2usb_flags & SCSA2USB_FLAGS_LOCKS_INIT) {
                /*
                 * if a waitQ exists, get rid of it before destroying it
                 */
                for (lun = 0; lun < SCSA2USB_MAX_LUNS; lun++) {
                        scsa2usb_flush_waitQ(scsa2usbp, lun, CMD_TRAN_ERR);
                        usba_destroy_list(&scsa2usbp->scsa2usb_waitQ[lun]);
                }

                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                if (scsa2usbp->scsa2usb_flags &
                    SCSA2USB_FLAGS_HBA_ATTACH_SETUP) {
                        (void) scsi_hba_detach(dip);
                        scsi_hba_tran_free(scsa2usbp->scsa2usb_tran);
                }

                if (scsa2usbp->scsa2usb_flags &
                    SCSA2USB_FLAGS_PIPES_OPENED) {
                        scsa2usb_close_usb_pipes(scsa2usbp);
                }

                /* Lower the power */
                pm = scsa2usbp->scsa2usb_pm;

                if (pm && (scsa2usbp->scsa2usb_dev_state !=
                    USB_DEV_DISCONNECTED)) {
                        if (pm->scsa2usb_wakeup_enabled) {
                                mutex_exit(&scsa2usbp->scsa2usb_mutex);
                                (void) pm_raise_power(dip, 0,
                                    USB_DEV_OS_FULL_PWR);

                                if ((rval = usb_handle_remote_wakeup(dip,
                                    USB_REMOTE_WAKEUP_DISABLE)) !=
                                    USB_SUCCESS) {
                                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                                            scsa2usbp->scsa2usb_log_handle,
                                            "disable remote wakeup failed "
                                            "(%d)", rval);
                                }
                        } else {
                                mutex_exit(&scsa2usbp->scsa2usb_mutex);
                        }

                        (void) pm_lower_power(dip, 0, USB_DEV_OS_PWR_OFF);

                        mutex_enter(&scsa2usbp->scsa2usb_mutex);
                }

                if (pm) {
                        kmem_free(pm, sizeof (scsa2usb_power_t));
                }

                if (scsa2usbp->scsa2usb_override_str) {
                        kmem_free(scsa2usbp->scsa2usb_override_str,
                            strlen(scsa2usbp->scsa2usb_override_str) + 1);
                        scsa2usbp->scsa2usb_override_str = NULL;
                }

                /* remove the minor nodes */
                ddi_remove_minor_node(dip, NULL);

                /* Cancel the registered panic callback */
                scsa2usb_panic_callb_fini(scsa2usbp);

                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                mutex_destroy(&scsa2usbp->scsa2usb_mutex);
                cv_destroy(&scsa2usbp->scsa2usb_transport_busy_cv);
        }

        usb_client_detach(scsa2usbp->scsa2usb_dip,
            scsa2usbp->scsa2usb_dev_data);

        if (scsa2usbp->scsa2usb_ugen_hdl) {
                (void) usb_ugen_detach(scsa2usbp->scsa2usb_ugen_hdl,
                    DDI_DETACH);
                usb_ugen_release_hdl(scsa2usbp->scsa2usb_ugen_hdl);
        }

        usb_free_log_hdl(scsa2usbp->scsa2usb_log_handle);

        ddi_prop_remove_all(dip);

        ddi_soft_state_free(scsa2usb_statep, ddi_get_instance(dip));

        return (USB_SUCCESS);
}


/*
 * scsa2usb_override:
 *      some devices may be attached even though their subclass or
 *      protocol info is not according to spec.
 *      these can be determined by the 'subclass-protocol-override'
 *      property set in the conf file.
 */
static void
scsa2usb_override(scsa2usb_state_t *scsa2usbp)
{
        scsa2usb_ov_t ov;
        char    **override_str = NULL;
        char    *override_str_cpy;
        uint_t  override_str_len, override_str_cpy_len;
        uint_t  i;
        usb_dev_descr_t *descr = scsa2usbp->scsa2usb_dev_data->dev_descr;

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        scsa2usbp->scsa2usb_subclass_override =
            scsa2usbp->scsa2usb_protocol_override = 0;

        if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, scsa2usbp->scsa2usb_dip,
            DDI_PROP_DONTPASS, "attribute-override-list",
            &override_str, &override_str_len) != DDI_PROP_SUCCESS) {

                return;
        }

        /* parse each string in the subclass-protocol-override property */
        for (i = 0; i < override_str_len; i++) {

                USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "override_str[%d] = %s", i, override_str[i]);

                /*
                 * save a copy of the override string for possible
                 * inclusion in soft state later
                 */
                override_str_cpy_len = strlen(override_str[i]) + 1;
                override_str_cpy = kmem_zalloc(override_str_cpy_len, KM_SLEEP);
                (void) strcpy(override_str_cpy, override_str[i]);

                bzero(&ov, sizeof (scsa2usb_ov_t));

                if (scsa2usb_parse_input_str(override_str[i], &ov,
                    scsa2usbp) == USB_FAILURE) {
                        kmem_free(override_str_cpy, override_str_cpy_len);
                        continue;
                }

                /*
                 * see if subclass/protocol needs to be overridden for device
                 * or if device should not be power managed
                 * if there'a a match, save the override string in soft state
                 */
                if (((descr->idVendor == (uint16_t)ov.vid) || (ov.vid == 0)) &&
                    ((descr->idProduct == (uint16_t)ov.pid) || (ov.pid == 0)) &&
                    ((descr->bcdDevice == (uint16_t)ov.rev) || (ov.rev == 0))) {
                        scsa2usbp->scsa2usb_subclass_override = ov.subclass;
                        scsa2usbp->scsa2usb_protocol_override = ov.protocol;

                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "vid=0x%x pid=0x%x rev=0x%x subclass=0x%x "
                            "protocol=0x%x "
                            "pmoff=%d fake_removable=%d modesense=%d "
                            "reduced-cmd-support=%d",
                            ov.vid, ov.pid, ov.rev, ov.subclass, ov.protocol,
                            ov.pmoff, ov.fake_removable, ov.no_modesense,
                            ov.reduced_cmd_support);

                        if (ov.pmoff) {
                                scsa2usbp->scsa2usb_attrs &= ~SCSA2USB_ATTRS_PM;
                        }
                        if (ov.fake_removable) {
                                scsa2usbp->scsa2usb_attrs &=
                                    ~SCSA2USB_ATTRS_RMB;
                        }
                        if (ov.no_modesense) {
                                scsa2usbp->scsa2usb_attrs &=
                                    ~SCSA2USB_ATTRS_MODE_SENSE;
                        }
                        if (ov.reduced_cmd_support) {
                                scsa2usbp->scsa2usb_attrs &=
                                    ~SCSA2USB_ATTRS_REDUCED_CMD;
                        }
                        scsa2usbp->scsa2usb_override_str = override_str_cpy;
                        break;
                } else {
                        kmem_free(override_str_cpy, override_str_cpy_len);
                }
        }

        ddi_prop_free(override_str);
}


/*
 * scsa2usb_parse_input_str:
 *      parse one conf file subclass-protocol-override string
 *      return vendor id, product id, revision, subclass, protocol
 *      function return is success or failure
 */
static int
scsa2usb_parse_input_str(char *str, scsa2usb_ov_t *ovp,
    scsa2usb_state_t *scsa2usbp)
{
        char            *input_field, *input_value;
        char            *lasts;
        uint_t          i;
        u_longlong_t    value;

        /* parse all the input pairs in the string */
        for (input_field = strtok_r(str, "=", &lasts);
            input_field != NULL;
            input_field = strtok_r(lasts, "=", &lasts)) {

                if ((input_value = strtok_r(lasts, " ", &lasts)) ==
                    NULL) {
                        scsa2usb_override_error("format", scsa2usbp);

                        return (USB_FAILURE);
                }
                /* if input value is a 'don't care', skip to the next pair */
                if (strcmp(input_value, "*") == 0) {
                        continue;
                }
                if (strcasecmp(input_field, "vid") == 0) {
                        if (kobj_getvalue(input_value, &value) == -1) {
                                scsa2usb_override_error("vendor id", scsa2usbp);

                                return (USB_FAILURE);
                        }
                        ovp->vid = (int)value;
                } else if (strcasecmp(input_field, "pid") == 0) {
                        if (kobj_getvalue(input_value, &value) == -1) {
                                scsa2usb_override_error("product id",
                                    scsa2usbp);

                                return (USB_FAILURE);
                        }
                        ovp->pid = (int)value;
                } else if (strcasecmp(input_field, "rev") == 0) {
                        if (kobj_getvalue(input_value, &value) == -1) {
                                scsa2usb_override_error("revision id",
                                    scsa2usbp);

                                return (USB_FAILURE);
                        }
                        ovp->rev = (int)value;
                } else if (strcasecmp(input_field, "subclass") == 0) {
                        for (i = 0; i < N_SCSA2USB_SUBC_OVERRIDE; i++) {
                                if (strcasecmp(input_value,
                                    scsa2usb_subclass[i].name) == 0) {
                                        ovp->subclass =
                                            scsa2usb_subclass[i].value;
                                        break;
                                }
                        }
                        if (ovp->subclass == 0) {
                                scsa2usb_override_error("subclass", scsa2usbp);

                                return (USB_FAILURE);
                        }
                } else if (strcasecmp(input_field, "protocol") == 0) {
                        for (i = 0; i < N_SCSA2USB_PROT_OVERRIDE; i++) {
                                if (strcasecmp(input_value,
                                    scsa2usb_protocol[i].name) == 0) {
                                        ovp->protocol =
                                            scsa2usb_protocol[i].value;
                                        break;
                                }
                        }
                        if (ovp->protocol == 0) {
                                scsa2usb_override_error("protocol", scsa2usbp);

                                return (USB_FAILURE);
                        }
                } else if (strcasecmp(input_field, "pm") == 0) {
                        if (strcasecmp(input_value, "off") == 0) {
                                ovp->pmoff = 1;
                                break;
                        } else {
                                scsa2usb_override_error("pm", scsa2usbp);

                                return (USB_FAILURE);
                        }
                } else if (strcasecmp(input_field, "removable") == 0) {
                        if (strcasecmp(input_value, "true") == 0) {
                                ovp->fake_removable = 1;
                                break;
                        } else {
                                scsa2usb_override_error("removable", scsa2usbp);

                                return (USB_FAILURE);
                        }
                } else if (strcasecmp(input_field, "modesense") == 0) {
                        if (strcasecmp(input_value, "false") == 0) {
                                ovp->no_modesense = 1;
                                break;
                        } else {
                                scsa2usb_override_error("modesense",
                                    scsa2usbp);

                                return (USB_FAILURE);
                        }
                } else if (strcasecmp(input_field,
                    "reduced-cmd-support") == 0) {
                        if (strcasecmp(input_value, "true") == 0) {
                                ovp->reduced_cmd_support = 1;
                                break;
                        } else {
                                scsa2usb_override_error(
                                    "reduced-cmd-support", scsa2usbp);

                                return (USB_FAILURE);
                        }
                } else {
                        scsa2usb_override_error(input_field, scsa2usbp);

                        return (USB_FAILURE);
                }
        }

        return (USB_SUCCESS);
}


/*
 * scsa2usb_override_error:
 *      print an error message if conf file string is bad format
 */
static void
scsa2usb_override_error(char *input_field, scsa2usb_state_t *scsa2usbp)
{
        USB_DPRINTF_L1(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "invalid %s in scsa2usb.conf file entry", input_field);
}


/*
 * Some devices are not complete or compatible implementations of the USB mass
 * storage protocols, and need special handling.  This routine checks various
 * aspects of the device against internal lists of quirky hardware.
 */
static void
scsa2usb_detect_quirks(scsa2usb_state_t *scsa2usbp)
{
        int mask;
        usb_dev_descr_t *desc = scsa2usbp->scsa2usb_dev_data->dev_descr;

        if (!SCSA2USB_IS_BULK_ONLY(scsa2usbp)) {
                scsa2usbp->scsa2usb_attrs &= ~SCSA2USB_ATTRS_GET_LUN;
        }

        /*
         * Determine if this device is on the quirks list:
         */
        for (uint_t i = 0; i < ARRAY_SIZE(scsa2usb_quirks); i++) {
                struct quirk *q = &scsa2usb_quirks[i];

                if (q->q_vid == desc->idVendor &&
                    (q->q_pid == desc->idProduct || q->q_pid == X) &&
                    (q->q_rev == desc->bcdDevice || q->q_rev == X)) {
                        /*
                         * Remove any attribute bits specified in the quirks
                         * table:
                         */
                        scsa2usbp->scsa2usb_attrs &= ~(q->q_attr);
                        break;
                }
        }

        /*
         * Mitsumi's CD-RW drives subclass isn't UFI.
         * But they support UFI command-set (this code ensures that)
         * NOTE: This is a special case, and is being called out so.
         */
        if (desc->idVendor == MS_MITSUMI_VID) {
                mask = scsa2usbp->scsa2usb_cmd_protocol & SCSA2USB_CMDSET_MASK;
                if (mask) {
                        scsa2usbp->scsa2usb_cmd_protocol &= ~mask;
                }
                scsa2usbp->scsa2usb_cmd_protocol |= SCSA2USB_UFI_CMDSET;
        }

        if (scsa2usbp->scsa2usb_attrs != SCSA2USB_ALL_ATTRS) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                    scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb attributes modified: 0x%x",
                    scsa2usbp->scsa2usb_attrs);
        }
}


/*
 * scsa2usb_create_luns:
 *      check the number of luns but continue if the check fails,
 *      create child nodes for each lun
 */
static void
scsa2usb_create_luns(scsa2usb_state_t *scsa2usbp)
{
        int             lun, rval;
        char            *compatible[MAX_COMPAT_NAMES];  /* compatible names */
        dev_info_t      *cdip;
        uchar_t         dtype;
        char            *node_name;
        char            *driver_name = NULL;

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_create_luns:");

        mutex_enter(&scsa2usbp->scsa2usb_mutex);

        /* Set n_luns to 1 by default (for floppies and other devices) */
        scsa2usbp->scsa2usb_n_luns = 1;

        /*
         * Check if there are any device out there which don't
         * support the GET_MAX_LUN command. If so, don't issue
         * control request to them.
         */
        if ((scsa2usbp->scsa2usb_attrs & SCSA2USB_ATTRS_GET_LUN) == 0) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "get_max_lun cmd not supported");
        } else {
                if (SCSA2USB_IS_BULK_ONLY(scsa2usbp)) {
                        scsa2usbp->scsa2usb_n_luns =
                            scsa2usb_bulk_only_get_max_lun(scsa2usbp);
                }
        }

        USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_create_luns: %d luns found", scsa2usbp->scsa2usb_n_luns);

        /*
         * create disk child for each lun
         */
        for (lun = 0; lun < scsa2usbp->scsa2usb_n_luns; lun++) {
                ASSERT(scsa2usbp->scsa2usb_lun_dip[lun] == NULL);

                /* do an inquiry to get the dtype of this lun */
                scsa2usb_do_inquiry(scsa2usbp, 0, lun);

                dtype = scsa2usbp->scsa2usb_lun_inquiry[lun].
                    inq_dtype & DTYPE_MASK;

                USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "dtype[%d]=0x%x", lun, dtype);

                driver_name = NULL;

                switch (dtype) {
                case DTYPE_DIRECT:
                case DTYPE_RODIRECT:
                case DTYPE_OPTICAL:
                        node_name = "disk";
                        driver_name = "sd";

                        break;
                case DTYPE_SEQUENTIAL:
                        node_name = "tape";
                        driver_name = "st";

                        break;
                case DTYPE_PRINTER:
                        node_name = "printer";

                        break;
                case DTYPE_PROCESSOR:
                        node_name = "processor";

                        break;
                case DTYPE_WORM:
                        node_name = "worm";

                        break;
                case DTYPE_SCANNER:
                        node_name = "scanner";

                        break;
                case DTYPE_CHANGER:
                        node_name = "changer";

                        break;
                case DTYPE_COMM:
                        node_name = "comm";

                        break;
                case DTYPE_ARRAY_CTRL:
                        node_name = "array_ctrl";

                        break;
                case DTYPE_ESI:
                        node_name = "esi";
                        driver_name = "ses";

                        break;
                default:
                        node_name = "generic";

                        break;
                }

                if (driver_name) {
                        compatible[0] = driver_name;
                }

                ndi_devi_alloc_sleep(scsa2usbp->scsa2usb_dip, node_name,
                    (pnode_t)DEVI_SID_NODEID, &cdip);

                /* attach target & lun properties */
                rval = ndi_prop_update_int(DDI_DEV_T_NONE, cdip, "target", 0);
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "ndi_prop_update_int target failed %d", rval);
                        (void) ndi_devi_free(cdip);
                        continue;
                }

                rval = ndi_prop_create_boolean(DDI_DEV_T_NONE, cdip,
                    "hotpluggable");
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "ndi_prop_create_boolean hotpluggable failed %d",
                            rval);
                        ddi_prop_remove_all(cdip);
                        (void) ndi_devi_free(cdip);
                        continue;
                }
                /*
                 * Some devices don't support LOG SENSE, so tells
                 * sd driver not to send this command.
                 */
                rval = ndi_prop_update_int(DDI_DEV_T_NONE, cdip,
                    "pm-capable", 1);
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "ndi_prop_update_int pm-capable failed %d", rval);
                        ddi_prop_remove_all(cdip);
                        (void) ndi_devi_free(cdip);
                        continue;
                }

                rval = ndi_prop_update_int(DDI_DEV_T_NONE, cdip, "lun", lun);
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "ndi_prop_update_int lun failed %d", rval);
                        ddi_prop_remove_all(cdip);
                        (void) ndi_devi_free(cdip);
                        continue;
                }

                if (driver_name) {
                        rval = ndi_prop_update_string_array(DDI_DEV_T_NONE,
                            cdip, "compatible", (char **)compatible,
                            MAX_COMPAT_NAMES);
                        if (rval != DDI_PROP_SUCCESS) {
                                USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                                    scsa2usbp->scsa2usb_log_handle,
                                    "ndi_prop_update_string_array failed %d",
                                    rval);
                                ddi_prop_remove_all(cdip);
                                (void) ndi_devi_free(cdip);
                                continue;
                        }
                }

                /*
                 * add property "usb" so we always verify that it is our child
                 */
                rval = ndi_prop_create_boolean(DDI_DEV_T_NONE, cdip, "usb");
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "ndi_prop_create_boolean failed %d", rval);
                        ddi_prop_remove_all(cdip);
                        (void) ndi_devi_free(cdip);
                        continue;
                }

                mutex_exit(&scsa2usbp->scsa2usb_mutex);
                (void) ddi_initchild(scsa2usbp->scsa2usb_dip, cdip);
                mutex_enter(&scsa2usbp->scsa2usb_mutex);

                usba_set_usba_device(cdip,
                    usba_get_usba_device(scsa2usbp->scsa2usb_dip));
        }
        mutex_exit(&scsa2usbp->scsa2usb_mutex);
}


/*
 * scsa2usb_is_usb:
 *      scsa2usb gets called for all possible sd children.
 *      we can only accept usb children
 */
static int
scsa2usb_is_usb(dev_info_t *dip)
{
        if (dip) {
                return (ddi_prop_exists(DDI_DEV_T_ANY, dip,
                    DDI_PROP_DONTPASS, "usb"));
        }
        return (0);
}


/*
 * Panic Stuff
 * scsa2usb_panic_callb_init:
 *      initialize PANIC callb and free allocated resources
 */
static void
scsa2usb_panic_callb_init(scsa2usb_state_t *scsa2usbp)
{
        /*
         * In case the system panics, the sync command flushes
         * dirty FS pages or buffers. This would cause a hang
         * in USB.
         * The reason for the failure is that we enter
         * polled mode (interrupts disabled) and HCD gets stuck
         * trying to execute bulk requests
         * The panic_callback registered below provides a warning
         * that a panic has occurred and from that point onwards, we
         * complete each request successfully and immediately. This
         * will fake successful syncing so at least the rest of the
         * filesystems complete syncing.
         */
        scsa2usbp->scsa2usb_panic_info =
            kmem_zalloc(sizeof (scsa2usb_cpr_t), KM_SLEEP);
        mutex_init(&scsa2usbp->scsa2usb_panic_info->lockp,
            NULL, MUTEX_DRIVER,
            scsa2usbp->scsa2usb_dev_data->dev_iblock_cookie);
        scsa2usbp->scsa2usb_panic_info->statep = scsa2usbp;
        scsa2usbp->scsa2usb_panic_info->cpr.cc_lockp =
            &scsa2usbp->scsa2usb_panic_info->lockp;
        scsa2usbp->scsa2usb_panic_info->cpr.cc_id =
            callb_add(scsa2usb_panic_callb,
            (void *)scsa2usbp->scsa2usb_panic_info,
            CB_CL_PANIC, "scsa2usb");
}


/*
 * scsa2usb_panic_callb_fini:
 *      cancel out PANIC callb and free allocated resources
 */
static void
scsa2usb_panic_callb_fini(scsa2usb_state_t *scsa2usbp)
{
        if (scsa2usbp->scsa2usb_panic_info) {
                SCSA2USB_CANCEL_CB(scsa2usbp->scsa2usb_panic_info->cpr.cc_id);
                mutex_destroy(&scsa2usbp->scsa2usb_panic_info->lockp);
                scsa2usbp->scsa2usb_panic_info->statep = NULL;
                kmem_free(scsa2usbp->scsa2usb_panic_info,
                    sizeof (scsa2usb_cpr_t));
                scsa2usbp->scsa2usb_panic_info = NULL;
        }
}


/*
 * scsa2usb_panic_callb:
 *      This routine is called when there is a system panic.
 */
/* ARGSUSED */
static boolean_t
scsa2usb_panic_callb(void *arg, int code)
{
        scsa2usb_cpr_t *cpr_infop;
        scsa2usb_state_t *scsa2usbp;
        uint_t          lun;

        _NOTE(NO_COMPETING_THREADS_NOW);
        cpr_infop = (scsa2usb_cpr_t *)arg;
        scsa2usbp = (scsa2usb_state_t *)cpr_infop->statep;

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_panic_callb: code=%d", code);

        /*
         * If we return error here, "sd" prints lots of error
         * messages and could retry the same pkt over and over again.
         * The sync recovery isn't "smooth" in that case. By faking
         * a success return, instead,  we force sync to complete.
         */
        if (scsa2usbp->scsa2usb_cur_pkt) {
                /*
                 * Do not print the "no sync" warning here. it will then be
                 * displayed before we actually start syncing. Also we don't
                 * replace this code with a call to scsa2usb_pkt_completion().
                 * NOTE: mutexes are disabled during panic.
                 */
                scsa2usbp->scsa2usb_cur_pkt->pkt_reason = CMD_CMPLT;
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                scsa2usb_pkt_completion(scsa2usbp, scsa2usbp->scsa2usb_cur_pkt);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
        }

        /* get rid of waitQ */
        for (lun = 0; lun < SCSA2USB_MAX_LUNS; lun++) {
                scsa2usb_flush_waitQ(scsa2usbp, lun, CMD_CMPLT);
        }

#ifndef lint
        _NOTE(COMPETING_THREADS_NOW);
#endif

        return (B_TRUE);
}

/*
 * scsa2usb_cpr_suspend
 *      determine if the device's state can be changed to SUSPENDED
 *      close pipes if there is no activity
 */
/* ARGSUSED */
static int
scsa2usb_cpr_suspend(dev_info_t *dip)
{
        scsa2usb_state_t *scsa2usbp;
        int     prev_state;
        int     rval = USB_FAILURE;

        scsa2usbp = ddi_get_soft_state(scsa2usb_statep, ddi_get_instance(dip));

        ASSERT(scsa2usbp != NULL);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_cpr_suspend:");

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        switch (scsa2usbp->scsa2usb_dev_state) {
        case USB_DEV_ONLINE:
        case USB_DEV_PWRED_DOWN:
        case USB_DEV_DISCONNECTED:
                prev_state = scsa2usbp->scsa2usb_dev_state;
                scsa2usbp->scsa2usb_dev_state = USB_DEV_SUSPENDED;

                /*
                 * If the device is busy, we cannot suspend
                 */
                if (SCSA2USB_BUSY(scsa2usbp)) {
                        USB_DPRINTF_L3(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "scsa2usb_cpr_suspend: I/O active");

                        /* fall back to previous state */
                        scsa2usbp->scsa2usb_dev_state = prev_state;
                } else {
                        rval = USB_SUCCESS;
                }

                break;
        case USB_DEV_SUSPENDED:
        default:
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_cpr_suspend: Illegal dev state: %d",
                    scsa2usbp->scsa2usb_dev_state);

                break;
        }
        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        if ((rval == USB_SUCCESS) && scsa2usbp->scsa2usb_ugen_hdl) {
                rval = usb_ugen_detach(scsa2usbp->scsa2usb_ugen_hdl,
                    DDI_SUSPEND);
        }

        return (rval);
}


/*
 * scsa2usb_cpr_resume:
 *      restore device's state
 */
static void
scsa2usb_cpr_resume(dev_info_t *dip)
{
        scsa2usb_state_t *scsa2usbp =
            ddi_get_soft_state(scsa2usb_statep, ddi_get_instance(dip));

        ASSERT(scsa2usbp != NULL);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_cpr_resume: dip = 0x%p", (void *)dip);

        scsa2usb_restore_device_state(dip, scsa2usbp);

        if (scsa2usbp->scsa2usb_ugen_hdl) {
                (void) usb_ugen_attach(scsa2usbp->scsa2usb_ugen_hdl,
                    DDI_RESUME);
        }
}


/*
 * scsa2usb_restore_device_state:
 *      - raise the device's power
 *      - reopen all the pipes
 */
static void
scsa2usb_restore_device_state(dev_info_t *dip, scsa2usb_state_t *scsa2usbp)
{
        uint_t  prev_state;

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_restore_device_state:");

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        prev_state = scsa2usbp->scsa2usb_dev_state;

        scsa2usb_raise_power(scsa2usbp);

        ASSERT((prev_state == USB_DEV_DISCONNECTED) ||
            (prev_state == USB_DEV_SUSPENDED));

        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        /* Check for the same device */
        if (usb_check_same_device(dip, scsa2usbp->scsa2usb_log_handle,
            USB_LOG_L0, DPRINT_MASK_ALL, USB_CHK_ALL, NULL) != USB_SUCCESS) {

                /* change the flags to active */
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                scsa2usbp->scsa2usb_dev_state = USB_DEV_DISCONNECTED;
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                scsa2usb_pm_idle_component(scsa2usbp);

                return;
        }

        /*
         * if the device had remote wakeup earlier,
         * enable it again
         */
        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        if (scsa2usbp->scsa2usb_pm &&
            scsa2usbp->scsa2usb_pm->scsa2usb_wakeup_enabled) {
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
                (void) usb_handle_remote_wakeup(scsa2usbp->scsa2usb_dip,
                    USB_REMOTE_WAKEUP_ENABLE);
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
        }

        scsa2usbp->scsa2usb_dev_state = USB_DEV_ONLINE;
        scsa2usbp->scsa2usb_pkt_state = SCSA2USB_PKT_NONE;
        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        scsa2usb_pm_idle_component(scsa2usbp);
}


/*
 * SCSA entry points:
 *
 * scsa2usb_scsi_tgt_probe:
 * scsa functions are exported by means of the transport table
 * Issue a probe to get the inquiry data.
 */
/* ARGSUSED */
static int
scsa2usb_scsi_tgt_probe(struct scsi_device *sd, int (*waitfunc)(void))
{
        scsi_hba_tran_t *tran;
        scsa2usb_state_t *scsa2usbp;
        dev_info_t *dip = ddi_get_parent(sd->sd_dev);
        int     rval;

        ASSERT(dip);

        tran = ddi_get_driver_private(dip);
        ASSERT(tran != NULL);
        scsa2usbp = (scsa2usb_state_t *)tran->tran_hba_private;
        ASSERT(scsa2usbp);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_tgt_probe:");

        /* if device is disconnected (ie. pipes closed), fail immediately */
        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                return (SCSIPROBE_FAILURE);
        }
        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_tgt_probe: scsi_device = 0x%p", (void *)sd);

        if ((rval = scsi_hba_probe(sd, waitfunc)) == SCSIPROBE_EXISTS) {
                /*
                 * respect the removable bit on all USB storage devices
                 * unless overridden by a scsa2usb.conf entry
                 */
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                if (!(scsa2usbp->scsa2usb_attrs & SCSA2USB_ATTRS_RMB)) {
                        _NOTE(SCHEME_PROTECTS_DATA("unshared", scsi_inquiry))
                        sd->sd_inq->inq_rmb = 1;
                }
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
        }

        return (rval);
}


/*
 * scsa2usb_scsi_tgt_init:
 *      check whether we created this child ourselves
 */
/* ARGSUSED */
static int
scsa2usb_scsi_tgt_init(dev_info_t *dip, dev_info_t *cdip,
    scsi_hba_tran_t *tran, struct scsi_device *sd)
{
        scsa2usb_state_t *scsa2usbp = (scsa2usb_state_t *)
            tran->tran_hba_private;
        int lun;
        int t_len = sizeof (lun);

        if (ddi_prop_op(DDI_DEV_T_ANY, cdip, PROP_LEN_AND_VAL_BUF,
            DDI_PROP_DONTPASS|DDI_PROP_CANSLEEP, "lun", (caddr_t)&lun,
            &t_len) != DDI_PROP_SUCCESS) {

                return (DDI_FAILURE);
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_tgt_init: %s, lun%d", ddi_driver_name(cdip), lun);

        /* is this a child we created? */
        if (scsa2usb_is_usb(cdip) == 0) {

                USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_scsi_tgt_init: new child %s%d",
                    ddi_driver_name(cdip), ddi_get_instance(cdip));

                /*
                 * add property "usb" so we can always verify that it
                 * is our child
                 */
                if (ndi_prop_create_boolean(DDI_DEV_T_NONE, cdip, "usb") !=
                    DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "ndi_prop_create_boolean failed");

                        return (DDI_FAILURE);
                }

                usba_set_usba_device(cdip,
                    usba_get_usba_device(scsa2usbp->scsa2usb_dip));

                /*
                 * we don't store this dip in scsa2usb_lun_dip, there
                 * might be multiple dips for the same device
                 */

                return (DDI_SUCCESS);
        }

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        if ((lun >= scsa2usbp->scsa2usb_n_luns) ||
            (scsa2usbp->scsa2usb_lun_dip[lun] != NULL)) {
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                return (DDI_FAILURE);
        }

        scsa2usbp->scsa2usb_lun_dip[lun] = cdip;
        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        return (DDI_SUCCESS);
}


/*
 * scsa2usb_scsi_tgt_free:
 */
/* ARGSUSED */
static void
scsa2usb_scsi_tgt_free(dev_info_t *hba_dip, dev_info_t *cdip,
    scsi_hba_tran_t *tran, struct scsi_device *sd)
{
        scsa2usb_state_t *scsa2usbp = (scsa2usb_state_t *)
            tran->tran_hba_private;
        int lun;
        int t_len = sizeof (lun);

        /* is this our child? */
        if (scsa2usb_is_usb(cdip) == 0) {

                return;
        }

        if (ddi_prop_op(DDI_DEV_T_ANY, cdip, PROP_LEN_AND_VAL_BUF,
            DDI_PROP_DONTPASS|DDI_PROP_CANSLEEP, "lun", (caddr_t)&lun,
            &t_len) != DDI_PROP_SUCCESS) {

                return;
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_tgt_free: %s lun%d", ddi_driver_name(cdip), lun);

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        if (lun < scsa2usbp->scsa2usb_n_luns) {
                if (scsa2usbp->scsa2usb_lun_dip[lun] == cdip) {
                        scsa2usbp->scsa2usb_lun_dip[lun] = NULL;
                }
        }
        mutex_exit(&scsa2usbp->scsa2usb_mutex);
}


/*
 * bus enumeration entry points
 */
static int
scsa2usb_scsi_bus_config(dev_info_t *dip, uint_t flag, ddi_bus_config_op_t op,
    void *arg, dev_info_t **child)
{
        int     rval;

        scsa2usb_state_t *scsa2usbp =
            ddi_get_soft_state(scsa2usb_statep, ddi_get_instance(dip));

        ASSERT(scsa2usbp != NULL);

        USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_bus_config: op=%d", op);

        if (scsa2usb_scsi_bus_config_debug) {
                flag |= NDI_DEVI_DEBUG;
        }

        ndi_devi_enter(dip);
        /* create children if necessary */
        if (DEVI(dip)->devi_child == NULL) {
                scsa2usb_create_luns(scsa2usbp);
        }

        rval = ndi_busop_bus_config(dip, flag, op, arg, child, 0);

        ndi_devi_exit(dip);

        return (rval);
}


static int
scsa2usb_scsi_bus_unconfig(dev_info_t *dip, uint_t flag, ddi_bus_config_op_t op,
    void *arg)
{
        scsa2usb_state_t *scsa2usbp =
            ddi_get_soft_state(scsa2usb_statep, ddi_get_instance(dip));

        int             rval = NDI_SUCCESS;
        uint_t          save_flag = flag;

        ASSERT(scsa2usbp != NULL);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_bus_unconfig: op=%d", op);

        if (scsa2usb_scsi_bus_config_debug) {
                flag |= NDI_DEVI_DEBUG;
        }

        /*
         * first offline and if offlining successful, then
         * remove children
         */
        if (op == BUS_UNCONFIG_ALL) {
                flag &= ~(NDI_DEVI_REMOVE | NDI_UNCONFIG);
        }

        ndi_devi_enter(dip);
        rval = ndi_busop_bus_unconfig(dip, flag, op, arg);

        /*
         * If unconfig is successful and not part of modunload
         * daemon, attempt to remove children.
         */
        if (op == BUS_UNCONFIG_ALL && rval == NDI_SUCCESS &&
            (flag & NDI_AUTODETACH) == 0) {
                flag |= NDI_DEVI_REMOVE;
                rval = ndi_busop_bus_unconfig(dip, flag, op, arg);
        }
        ndi_devi_exit(dip);

        if ((rval != NDI_SUCCESS) && (op == BUS_UNCONFIG_ALL) &&
            (save_flag & NDI_DEVI_REMOVE)) {
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                if (scsa2usbp->scsa2usb_warning_given != B_TRUE) {
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "Disconnected device was busy, "
                            "please reconnect.");
                        scsa2usbp->scsa2usb_warning_given = B_TRUE;
                }
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_bus_unconfig: rval=%d", rval);

        return (rval);
}


/*
 * scsa2usb_scsi_init_pkt:
 *      Set up the scsi_pkt for transport. Also initialize
 *      scsa2usb_cmd struct for the transport.
 *      NOTE: We do not do any DMA setup here as USBA framework
 *      does that for us.
 */
static struct scsi_pkt *
scsa2usb_scsi_init_pkt(struct scsi_address *ap,
    struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen,
    int tgtlen, int flags, int (*callback)(), caddr_t arg)
{
        scsa2usb_cmd_t   *cmd;
        scsa2usb_state_t *scsa2usbp;
        struct scsi_pkt  *in_pkt = pkt;

        ASSERT(callback == NULL_FUNC || callback == SLEEP_FUNC);

        scsa2usbp = (scsa2usb_state_t *)ADDR2SCSA2USB(ap);

        /* Print sync message */
        if (ddi_in_panic()) {
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                SCSA2USB_PRINT_SYNC_MSG(scsa2usb_sync_message, scsa2usbp);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
                /* continue so caller will not hang or complain */
        }

        /* allocate a pkt, if none already allocated */
        if (pkt == NULL) {
                if (statuslen < sizeof (struct scsi_arq_status)) {
                        statuslen = sizeof (struct scsi_arq_status);
                }

                pkt = scsi_hba_pkt_alloc(scsa2usbp->scsa2usb_dip, ap, cmdlen,
                    statuslen, tgtlen, sizeof (scsa2usb_cmd_t),
                    callback, arg);
                if (pkt == NULL) {

                        return (NULL);
                }

                cmd = PKT2CMD(pkt);
                cmd->cmd_pkt    = pkt; /* back link to pkt */
                cmd->cmd_scblen = statuslen;
                cmd->cmd_cdblen = (uchar_t)cmdlen;

                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                cmd->cmd_tag    = scsa2usbp->scsa2usb_tag++;
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                cmd->cmd_bp     = bp;
                /*
                 * The buffer size of cmd->cmd_scb is constrained
                 * to sizeof (struct scsi_arq_status), if the scblen
                 * is bigger than that, we use pkt->pkt_scbp directly.
                 */
                if (cmd->cmd_scblen == sizeof (struct scsi_arq_status)) {
                        pkt->pkt_scbp = (opaque_t)&cmd->cmd_scb;
                }

                usba_init_list(&cmd->cmd_waitQ, (usb_opaque_t)cmd,
                    scsa2usbp->scsa2usb_dev_data->dev_iblock_cookie);
        } else {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb: pkt != NULL");

                /* nothing to do */
        }

        if (bp && (bp->b_bcount != 0)) {
                if ((bp_mapin_common(bp, (callback == SLEEP_FUNC) ?
                    VM_SLEEP : VM_NOSLEEP)) == NULL) {
                        if (pkt != in_pkt) {
                                scsi_hba_pkt_free(ap, pkt);
                        }

                        return (NULL);
                }

                USB_DPRINTF_L3(DPRINT_MASK_SCSA,
                    scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_scsi_init_pkt: mapped in 0x%p, addr=0x%p",
                    (void *)bp, (void *)bp->b_un.b_addr);
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_init_pkt: ap = 0x%p pkt: 0x%p\n\t"
            "bp = 0x%p cmdlen = %x stlen = 0x%x tlen = 0x%x flags = 0x%x",
            (void *)ap, (void *)pkt, (void *)bp, cmdlen, statuslen,
            tgtlen, flags);

        return (pkt);
}


/*
 * scsa2usb_scsi_destroy_pkt:
 *      We are done with the packet. Get rid of it.
 */
static void
scsa2usb_scsi_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt)
{
        scsa2usb_cmd_t *cmd = PKT2CMD(pkt);
        scsa2usb_state_t *scsa2usbp = ADDR2SCSA2USB(ap);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_destroy_pkt: pkt=0x%p", (void *)pkt);

        usba_destroy_list(&cmd->cmd_waitQ);
        scsi_hba_pkt_free(ap, pkt);
}


/*
 * scsa2usb_scsi_start:
 *      For each command being issued, build up the CDB
 *      and call scsi_transport to issue the command. This
 *      function is based on the assumption that USB allows
 *      a subset of SCSI commands. Other SCSI commands we fail.
 */
static int
scsa2usb_scsi_start(struct scsi_address *ap, struct scsi_pkt *pkt)
{
        scsa2usb_cmd_t          *cmd;
        scsa2usb_state_t        *scsa2usbp = ADDR2SCSA2USB(ap);
        uint_t                  lun = ap->a_lun;

        mutex_enter(&scsa2usbp->scsa2usb_mutex);

        cmd = PKT2CMD(pkt);
        USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_start:\n\t"
            "bp: 0x%p ap: 0x%p pkt: 0x%p flag: 0x%x time: 0x%x\n\tcdb0: 0x%x "
            "dev_state: 0x%x pkt_state: 0x%x flags: 0x%x pipe_state: 0x%x",
            (void *)cmd->cmd_bp, (void *)ap, (void *)pkt, pkt->pkt_flags,
            pkt->pkt_time, pkt->pkt_cdbp[0], scsa2usbp->scsa2usb_dev_state,
            scsa2usbp->scsa2usb_pkt_state, scsa2usbp->scsa2usb_flags,
            scsa2usbp->scsa2usb_pipe_state);

        if (pkt->pkt_time == 0) {
                USB_DPRINTF_L1(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "pkt submitted with 0 timeout which may cause indefinite "
                    "hangs");
        }

        /*
         * if we are in panic, we are in polled mode, so we can just
         * accept the request, drop it and return
         * if we fail this request, the rest of the file systems do not
         * get synced
         */
        if (ddi_in_panic()) {
                extern int do_polled_io;

                ASSERT(do_polled_io);
                scsa2usb_prepare_pkt(scsa2usbp, pkt);
                SCSA2USB_PRINT_SYNC_MSG(scsa2usb_sync_message, scsa2usbp);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                return (TRAN_ACCEPT);
        }

        /* we cannot do polling, this should not happen */
        if (pkt->pkt_flags & FLAG_NOINTR) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "NOINTR packet: opcode = 0%x", pkt->pkt_cdbp[0]);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                return (TRAN_BADPKT);
        }

        /* prepare packet */
        scsa2usb_prepare_pkt(scsa2usbp, pkt);

        /* just queue up the requests in the waitQ if below max */
        if (usba_list_entry_count(&scsa2usbp->scsa2usb_waitQ[lun]) >
            SCSA2USB_MAX_REQ_PER_LUN) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                    scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_scsi_start: limit (%d) exceeded",
                    SCSA2USB_MAX_REQ_PER_LUN);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                return (TRAN_BUSY);
        }

        usba_add_to_list(&scsa2usbp->scsa2usb_waitQ[lun], &cmd->cmd_waitQ);

        USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_work_thread_id=0x%p, count=%d, lun=%d",
            (void *)scsa2usbp->scsa2usb_work_thread_id,
            usba_list_entry_count(&scsa2usbp->scsa2usb_waitQ[lun]), lun);

        /* fire up a thread to start executing the protocol */
        if (scsa2usbp->scsa2usb_work_thread_id == 0) {
                if ((usb_async_req(scsa2usbp->scsa2usb_dip,
                    scsa2usb_work_thread,
                    (void *)scsa2usbp, USB_FLAGS_SLEEP)) != USB_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "no work thread started");

                        if (usba_rm_from_list(
                            &scsa2usbp->scsa2usb_waitQ[lun],
                            &cmd->cmd_waitQ) == USB_SUCCESS) {
                                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                                return (TRAN_BUSY);
                        } else {

                                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                                return (TRAN_ACCEPT);
                        }
                }
                scsa2usbp->scsa2usb_work_thread_id = (kthread_t *)1;
        }

        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        return (TRAN_ACCEPT);
}


/*
 * scsa2usb_scsi_abort:
 *      Issue SCSI abort command. This function is a NOP.
 */
/* ARGSUSED */
static int
scsa2usb_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt)
{
        scsa2usb_state_t *scsa2usbp = (scsa2usb_state_t *)ADDR2SCSA2USB(ap);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_abort: pkt = %p", (void *)pkt);

        /* if device is disconnected (ie. pipes closed), fail immediately */
        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                return (0);
        }

        /* flush waitQ if target and lun match */
        if ((ap->a_target == pkt->pkt_address.a_target) &&
            (ap->a_lun == pkt->pkt_address.a_lun)) {
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
                scsa2usb_flush_waitQ(scsa2usbp, ap->a_lun, CMD_ABORTED);
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
        }
        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        return (0);
}


/*
 * scsa2usb_scsi_reset:
 *      device reset may turn the device into a brick and bus reset
 *      is not applicable.
 *      just flush the waitQ
 *      We return success, always.
 */
/* ARGSUSED */
static int
scsa2usb_scsi_reset(struct scsi_address *ap, int level)
{
        scsa2usb_state_t *scsa2usbp = (scsa2usb_state_t *)ADDR2SCSA2USB(ap);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_reset: ap = 0x%p, level = %d", (void *)ap, level);

        /* flush waitQ */
        scsa2usb_flush_waitQ(scsa2usbp, ap->a_lun, CMD_RESET);

        return (1);
}


/*
 * scsa2usb_scsi_getcap:
 *      Get SCSI capabilities.
 */
/* ARGSUSED */
static int
scsa2usb_scsi_getcap(struct scsi_address *ap, char *cap, int whom)
{
        int rval = -1;
        uint_t cidx;
        size_t dev_bsize_cap;
        scsa2usb_state_t *scsa2usbp = (scsa2usb_state_t *)ADDR2SCSA2USB(ap);
        ASSERT(scsa2usbp);

        if (cap == NULL) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_scsi_getcap: invalid arg, "
                    "cap = 0x%p whom = %d", (void *)cap, whom);

                return (rval);
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_getcap: cap = %s", cap);

        mutex_enter(&scsa2usbp->scsa2usb_mutex);

        /* if device is disconnected (ie. pipes closed), fail immediately */
        if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {

                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                return (rval);
        }

        cidx =  scsi_hba_lookup_capstr(cap);
        switch (cidx) {
        case SCSI_CAP_GEOMETRY:
                /* Just check and fail immediately if zero, rarely happens */
                if (scsa2usbp->scsa2usb_secsz[ap->a_lun] == 0) {
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "scsa2usb_scsi_getcap failed:"
                            "scsa2usbp->scsa2usb_secsz[ap->a_lun] == 0");
                        mutex_exit(&scsa2usbp->scsa2usb_mutex);

                        return (rval);
                }

                dev_bsize_cap = scsa2usbp->scsa2usb_totalsec[ap->a_lun];

                if (scsa2usbp->scsa2usb_secsz[ap->a_lun] > DEV_BSIZE) {
                        dev_bsize_cap *=
                            scsa2usbp->scsa2usb_secsz[ap->a_lun] / DEV_BSIZE;
                } else if (scsa2usbp->scsa2usb_secsz[ap->a_lun] <
                    DEV_BSIZE) {
                        dev_bsize_cap /=
                            DEV_BSIZE / scsa2usbp->scsa2usb_secsz[ap->a_lun];
                }

                if (dev_bsize_cap < 65536 * 2 * 18) {           /* < ~1GB */
                        /* unlabeled floppy, 18k per cylinder */
                        rval = ((2 << 16) | 18);
                } else if (dev_bsize_cap < 65536 * 64 * 32) {   /* < 64GB */
                        /* 1024k per cylinder */
                        rval = ((64 << 16) | 32);
                } else if (dev_bsize_cap < 65536 * 255 * 63) {  /* < ~500GB */
                        /* ~8m per cylinder */
                        rval = ((255 << 16) | 63);
                } else {                                        /* .. 8TB */
                        /* 64m per cylinder */
                        rval = ((512 << 16) | 256);
                }
                break;

        case SCSI_CAP_DMA_MAX:
                rval = scsa2usbp->scsa2usb_max_bulk_xfer_size;
                break;
        case SCSI_CAP_SCSI_VERSION:
                rval = SCSI_VERSION_2;
                break;
        case SCSI_CAP_INTERCONNECT_TYPE:
                rval = INTERCONNECT_USB;
                break;
        case SCSI_CAP_ARQ:
                /* FALLTHRU */
        case SCSI_CAP_UNTAGGED_QING:
                rval = 1;
                break;
        default:
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_scsi_getcap: unsupported cap = %s", cap);
                break;
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_getcap: cap = %s, returned = %d", cap, rval);

        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        return (rval);
}


/*
 * scsa2usb_scsi_setcap:
 *      Set SCSI capabilities.
 */
/* ARGSUSED */
static int
scsa2usb_scsi_setcap(struct scsi_address *ap, char *cap, int value, int whom)
{
        int rval = -1; /* default is cap undefined */
        uint_t cidx;
        scsa2usb_state_t *scsa2usbp = (scsa2usb_state_t *)ADDR2SCSA2USB(ap);
        ASSERT(scsa2usbp);

        if (cap == NULL || whom == 0) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_scsi_setcap: invalid arg");

                return (rval);
        }

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        /* if device is disconnected (ie. pipes closed), fail immediately */
        if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                return (rval);
        }

        cidx =  scsi_hba_lookup_capstr(cap);
        USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_scsi_setcap: ap = 0x%p value = 0x%x whom = 0x%x "
            "cidx = 0x%x", (void *)ap, value, whom, cidx);

        switch (cidx) {
        case SCSI_CAP_SECTOR_SIZE:
                if (value) {
                        scsa2usbp->scsa2usb_secsz[ap->a_lun] = value;
                }
                break;
        case SCSI_CAP_TOTAL_SECTORS:
                if (value) {
                        scsa2usbp->scsa2usb_totalsec[ap->a_lun] = value;
                }
                break;
        case SCSI_CAP_ARQ:
                rval = 1;
                break;
        case SCSI_CAP_DMA_MAX:
        case SCSI_CAP_SCSI_VERSION:
        case SCSI_CAP_INTERCONNECT_TYPE:
        case SCSI_CAP_UNTAGGED_QING:
                /* supported but not settable */
                rval = 0;
                break;
        default:
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_scsi_setcap: unsupported cap = %s", cap);
                break;
        }

        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        return (rval);
}


/*
 * scsa2usb - cmd and transport stuff
 */
/*
 * scsa2usb_prepare_pkt:
 *      initialize some fields of the pkt and cmd
 *      (the pkt may have been resubmitted/retried)
 */
static void
scsa2usb_prepare_pkt(scsa2usb_state_t *scsa2usbp, struct scsi_pkt *pkt)
{
        scsa2usb_cmd_t  *cmd = PKT2CMD(pkt);

        USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_prepare_pkt: pkt=0x%p cdb: 0x%x (%s)",
            (void *)pkt, pkt->pkt_cdbp[0],
            scsi_cname(pkt->pkt_cdbp[0], scsa2usb_cmds));

        pkt->pkt_reason = CMD_CMPLT;    /* Set reason to pkt_complete */
        pkt->pkt_state = 0;             /* Reset next three fields */
        pkt->pkt_statistics = 0;
        pkt->pkt_resid = 0;
        bzero(pkt->pkt_scbp, cmd->cmd_scblen); /* Set status to good */

        if (cmd) {
                cmd->cmd_timeout = pkt->pkt_time;
                cmd->cmd_xfercount = 0;         /* Reset the fields */
                cmd->cmd_total_xfercount = 0;
                cmd->cmd_lba = 0;
                cmd->cmd_done = 0;
                cmd->cmd_dir = 0;
                cmd->cmd_offset = 0;
                cmd->cmd_actual_len = cmd->cmd_cdblen;
        }
}


/*
 * scsa2usb_force_invalid_request
 */
static void
scsa2usb_force_invalid_request(scsa2usb_state_t *scsa2usbp,
    scsa2usb_cmd_t *cmd)
{
        struct scsi_arq_status  *arqp;

        USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_force_invalid_request: pkt = 0x%p", (void *)cmd->cmd_pkt);

        if (cmd->cmd_scblen >= sizeof (struct scsi_arq_status)) {
                arqp = (struct scsi_arq_status *)cmd->cmd_pkt->pkt_scbp;
                bzero(arqp, cmd->cmd_scblen);

                arqp->sts_status.sts_chk = 1;
                arqp->sts_rqpkt_reason = CMD_CMPLT;
                arqp->sts_rqpkt_state = STATE_XFERRED_DATA |
                    STATE_GOT_BUS | STATE_GOT_STATUS;
                arqp->sts_sensedata.es_valid = 1;
                arqp->sts_sensedata.es_class = 7;
                arqp->sts_sensedata.es_key = KEY_ILLEGAL_REQUEST;

                cmd->cmd_pkt->pkt_state = STATE_ARQ_DONE |
                    STATE_GOT_BUS | STATE_GOT_BUS | STATE_GOT_BUS |
                    STATE_GOT_STATUS;
#ifdef DEBUG
                {
                        uchar_t *p = (uchar_t *)(&arqp->sts_sensedata);
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "cdb: %x rqsense: "
                            "%x %x %x %x %x %x %x %x %x %x "
                            "%x %x %x %x %x %x %x %x %x %x",
                            cmd->cmd_pkt->pkt_cdbp[0],
                            p[0], p[1], p[2], p[3], p[4],
                            p[5], p[6], p[7], p[8], p[9],
                            p[10], p[11], p[12], p[13], p[14],
                            p[15], p[16], p[17], p[18], p[19]);
                }
#endif

        }
}


static int
scsa2usb_cmd_transport(scsa2usb_state_t *scsa2usbp, scsa2usb_cmd_t *cmd)
{
        int rval, transport;
        struct scsi_pkt *pkt;

        USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_cmd_transport: pkt: 0x%p, cur_pkt = 0x%p",
            (void *)cmd->cmd_pkt, (void *)scsa2usbp->scsa2usb_cur_pkt);

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));
        ASSERT(scsa2usbp->scsa2usb_cur_pkt == NULL);

        pkt = scsa2usbp->scsa2usb_cur_pkt = cmd->cmd_pkt;

        /*
         * Check per-device quirks first:
         */
        if (SCSA2USB_IS_BULK_ONLY(scsa2usbp)) {
                transport = scsa2usb_check_bulkonly_quirks(scsa2usbp, cmd);
        } else if (SCSA2USB_IS_CB(scsa2usbp) || SCSA2USB_IS_CBI(scsa2usbp)) {
                transport = scsa2usb_check_ufi_quirks(scsa2usbp, cmd);
        } else {
                return (TRAN_FATAL_ERROR);
        }

        /* just accept the command or return error */
        if (transport == SCSA2USB_JUST_ACCEPT) {
                SCSA2USB_SET_PKT_DO_COMP_STATE(scsa2usbp);

                return (TRAN_ACCEPT);
        } else if (transport == SCSA2USB_REJECT) {
                return (TRAN_FATAL_ERROR);
        }

        /* check command set next */
        if (SCSA2USB_IS_SCSI_CMDSET(scsa2usbp) ||
            SCSA2USB_IS_ATAPI_CMDSET(scsa2usbp)) {
                transport =
                    scsa2usb_handle_scsi_cmd_sub_class(scsa2usbp, cmd, pkt);
        } else if (SCSA2USB_IS_UFI_CMDSET(scsa2usbp)) {
                transport =
                    scsa2usb_handle_ufi_subclass_cmd(scsa2usbp, cmd, pkt);
        } else {
                transport = SCSA2USB_REJECT;
        }

        switch (transport) {
        case SCSA2USB_TRANSPORT:
                if (SCSA2USB_IS_BULK_ONLY(scsa2usbp)) {
                        rval = scsa2usb_bulk_only_transport(scsa2usbp, cmd);
                } else if (SCSA2USB_IS_CB(scsa2usbp) ||
                    SCSA2USB_IS_CBI(scsa2usbp)) {
                        rval = scsa2usb_cbi_transport(scsa2usbp, cmd);
                } else {
                        rval = TRAN_FATAL_ERROR;
                }
                break;
        case SCSA2USB_JUST_ACCEPT:
                SCSA2USB_SET_PKT_DO_COMP_STATE(scsa2usbp);
                rval = TRAN_ACCEPT;
                break;
        default:
                rval = TRAN_FATAL_ERROR;
        }

        return (rval);
}


/*
 * Check this Bulk Only command against the quirks for this particular device.
 * Returns a transport disposition.
 */
int
scsa2usb_check_bulkonly_quirks(scsa2usb_state_t *scsa2usbp, scsa2usb_cmd_t *cmd)
{
        struct scsi_inquiry *inq =
            &scsa2usbp->scsa2usb_lun_inquiry[cmd->cmd_pkt->pkt_address.a_lun];

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        /*
         * decode and convert the packet
         * for most cmds, we can bcopy the cdb
         */
        switch (cmd->cmd_pkt->pkt_cdbp[0]) {
        case SCMD_DOORLOCK:
                if (!(scsa2usbp->scsa2usb_attrs & SCSA2USB_ATTRS_DOORLOCK)) {

                        return (SCSA2USB_JUST_ACCEPT);

                /*
                 * only lock the door for CD and DVD drives
                 */
                } else if ((inq->inq_dtype == DTYPE_RODIRECT) ||
                    (inq->inq_dtype == DTYPE_OPTICAL)) {

                        if (inq->inq_rmb) {

                                break;
                        }
                }

                return (SCSA2USB_JUST_ACCEPT);

        case SCMD_START_STOP:   /* SCMD_LOAD for sequential devices */
                /*
                 * these devices don't have mechanics that spin the
                 * media up and down. So, it doesn't make much sense
                 * to issue this cmd.
                 *
                 * Furthermore, Hagiwara devices do not handle these
                 * cmds well. just accept this command as success.
                 */
                if (!(scsa2usbp->scsa2usb_attrs & SCSA2USB_ATTRS_START_STOP)) {

                        return (SCSA2USB_JUST_ACCEPT);

                } else if (inq->inq_dtype == DTYPE_SEQUENTIAL) {
                        /*
                         * In case of USB tape device, we need to send the
                         * command to the device to unload the media.
                         */
                        break;

                } else if (cmd->cmd_pkt->pkt_cdbp[4] & LOEJECT) {
                        /*
                         * if the device is really a removable then
                         * pass it on to the device, else just accept
                         */
                        if (inq->inq_rmb) {

                                break;
                        }

                        return (SCSA2USB_JUST_ACCEPT);

                } else if (!scsa2usbp->scsa2usb_rcvd_not_ready) {
                        /*
                         * if we have not received a NOT READY condition,
                         * just accept since some device choke on this too.
                         * we do have to let EJECT get through though
                         */
                        return (SCSA2USB_JUST_ACCEPT);
                }

                break;
        case SCMD_INQUIRY:
                /*
                 * Some devices do not handle the inquiry cmd well
                 * so build an inquiry and accept this command as
                 * success.
                 */
                if (!(scsa2usbp->scsa2usb_attrs & SCSA2USB_ATTRS_INQUIRY)) {
                        uchar_t evpd = 0x01;
                        unsigned int bufsize;
                        int count;

                        if (cmd->cmd_pkt->pkt_cdbp[1] & evpd)
                                return (SCSA2USB_REJECT);

                        scsa2usb_fake_inquiry(scsa2usbp, inq);

                        /* Copy no more than requested */
                        count = MIN(cmd->cmd_bp->b_bcount,
                            sizeof (struct scsi_inquiry));
                        bufsize = cmd->cmd_pkt->pkt_cdbp[4];
                        count = MIN(count, bufsize);
                        bcopy(inq, cmd->cmd_bp->b_un.b_addr, count);

                        cmd->cmd_pkt->pkt_resid = bufsize - count;
                        cmd->cmd_pkt->pkt_state |= STATE_XFERRED_DATA;

                        return (SCSA2USB_JUST_ACCEPT);
                } else if (!(scsa2usbp->scsa2usb_attrs &
                    SCSA2USB_ATTRS_INQUIRY_EVPD)) {
                        /*
                         * Some devices do not handle the inquiry cmd with
                         * evpd bit set well, e.g. some devices return the
                         * same page 0x83 data which will cause the generated
                         * devid by sd is not unique, thus return CHECK
                         * CONDITION directly to sd.
                         */
                        uchar_t evpd = 0x01;

                        if (!(cmd->cmd_pkt->pkt_cdbp[1] & evpd))
                                break;

                        if (cmd->cmd_bp) {
                                cmd->cmd_pkt->pkt_resid = cmd->cmd_bp->
                                    b_bcount;
                        }
                        scsa2usb_force_invalid_request(scsa2usbp, cmd);

                        return (SCSA2USB_JUST_ACCEPT);
                }
                break;
        /*
         * Fake accepting the following  Opcodes
         * (as most drives don't support these)
         * These are needed by format command.
         */
        case SCMD_RESERVE:
        case SCMD_RELEASE:
        case SCMD_PERSISTENT_RESERVE_IN:
        case SCMD_PERSISTENT_RESERVE_OUT:

                return (SCSA2USB_JUST_ACCEPT);

        case SCMD_MODE_SENSE:
        case SCMD_MODE_SELECT:
        case SCMD_MODE_SENSE_G1:
        case SCMD_MODE_SELECT_G1:
                if (!(scsa2usbp->scsa2usb_attrs & SCSA2USB_ATTRS_MODE_SENSE)) {
                        if (cmd->cmd_bp) {
                                cmd->cmd_pkt->pkt_resid = cmd->cmd_bp->
                                    b_bcount;
                        }
                        scsa2usb_force_invalid_request(scsa2usbp, cmd);

                        return (SCSA2USB_JUST_ACCEPT);
                }

                break;
        default:

                break;
        }

        return (SCSA2USB_TRANSPORT);
}


/*
 * scsa2usb_handle_scsi_cmd_sub_class:
 *      prepare a scsi cmd
 *      returns SCSA2USB_TRANSPORT, SCSA2USB_REJECT, SCSA2USB_JUST_ACCEPT
 */
int
scsa2usb_handle_scsi_cmd_sub_class(scsa2usb_state_t *scsa2usbp,
    scsa2usb_cmd_t *cmd, struct scsi_pkt *pkt)
{
        uchar_t evpd = 0x01;
        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_handle_scsi_cmd_sub_class: cmd = 0x%p pkt = 0x%p",
            (void *)cmd, (void *)pkt);

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        bzero(&cmd->cmd_cdb, SCSI_CDB_SIZE);
        cmd->cmd_cdb[SCSA2USB_OPCODE] = pkt->pkt_cdbp[0];   /* Set the opcode */
        cmd->cmd_cdb[SCSA2USB_LUN] = pkt->pkt_cdbp[1];

        /*
         * decode and convert the packet
         * for most cmds, we can bcopy the cdb
         */
        switch (pkt->pkt_cdbp[0]) {
        case SCMD_FORMAT:
                /*
                 * SCMD_FORMAT used to limit cmd->cmd_xfercount
                 * to 4 bytes, but this hangs
                 * formatting dvd media using cdrecord (that is,
                 * a SCSI FORMAT UNIT command with a parameter list > 4 bytes)
                 * (bit 4 in cdb1 is the Fmtdata bit)
                 */
                if ((pkt->pkt_cdbp[1] & 0x10) && cmd->cmd_bp) {
                        cmd->cmd_xfercount = cmd->cmd_bp->b_bcount;
                } else {
                        cmd->cmd_xfercount = 4;
                }
                cmd->cmd_dir = CBW_DIR_OUT;
                cmd->cmd_actual_len = CDB_GROUP0;
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                break;

        case SCMD_INQUIRY:
                cmd->cmd_dir = CBW_DIR_IN;
                cmd->cmd_actual_len = CDB_GROUP0;
                cmd->cmd_cdb[SCSA2USB_LBA_0] = pkt->pkt_cdbp[2];

                /*
                 * If vpd pages data is limited to maximum SCSA2USB_MAX_INQ_LEN,
                 * the page data may be truncated, which may cause some issues
                 * such as making the unique page 0x83 or 0x80 data from
                 * different devices become the same. So don't limit return
                 * length for vpd page inquiry cmd.
                 * Another, in order to maintain compatibility, the original
                 * length limitation for standard inquiry retains here. It
                 * can be removed in future if it is verified that enough
                 * devices can work well.
                 */
                if (pkt->pkt_cdbp[1] & evpd) {
                        cmd->cmd_cdb[SCSA2USB_LBA_2] = cmd->cmd_xfercount =
                            (cmd->cmd_bp ? cmd->cmd_bp->b_bcount : 0);
                } else {
                        cmd->cmd_cdb[SCSA2USB_LBA_2] = cmd->cmd_xfercount =
                            min(SCSA2USB_MAX_INQ_LEN,
                            cmd->cmd_bp ? cmd->cmd_bp->b_bcount : 0);
                }
                break;

        case SCMD_READ_CAPACITY:
                cmd->cmd_dir = CBW_DIR_IN;
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                cmd->cmd_xfercount = sizeof (scsa2usb_read_cap_t);
                break;

        /*
         * SCMD_READ/SCMD_WRITE are converted to G1 cmds
         * (as ATAPI devices don't recognize G0 commands)
         *
         * SCMD_READ_LONG/SCMD_WRITE_LONG are handled in
         * scsa2usb_rw_transport() along with other commands.
         *
         * USB Host Controllers cannot handle large (read/write)
         * xfers. We split the large request to chunks of
         * smaller ones to meet the HCD limitations.
         */
        case SCMD_READ:
        case SCMD_WRITE:
        case SCMD_READ_G1:
        case SCMD_WRITE_G1:
        case SCMD_READ_G4:
        case SCMD_WRITE_G4:
        case SCMD_READ_G5:
        case SCMD_WRITE_G5:
        case SCMD_READ_LONG:
        case SCMD_WRITE_LONG:
        case SCMD_READ_CD:
                switch (scsa2usbp->
                    scsa2usb_lun_inquiry[pkt->pkt_address.a_lun].
                    inq_dtype & DTYPE_MASK) {
                case DTYPE_DIRECT:
                case DTYPE_RODIRECT:
                case DTYPE_OPTICAL:
                        return (scsa2usb_rw_transport(
                            scsa2usbp, pkt));
                default:
                        bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                        if (cmd->cmd_bp) {
                                cmd->cmd_dir =
                                    (cmd->cmd_bp->b_flags & B_READ) ?
                                    CBW_DIR_IN : CBW_DIR_OUT;
                                cmd->cmd_xfercount =
                                    cmd->cmd_bp->b_bcount;
                        }
                        break;
                }
                break;

        case SCMD_REQUEST_SENSE:
                cmd->cmd_dir = CBW_DIR_IN;
                cmd->cmd_xfercount = pkt->pkt_cdbp[4];
                cmd->cmd_cdb[SCSA2USB_LBA_2] = pkt->pkt_cdbp[4];
                cmd->cmd_actual_len = CDB_GROUP0;
                break;

        case SCMD_DOORLOCK:
        case SCMD_START_STOP:
        case SCMD_TEST_UNIT_READY:
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                break;

        /*
         * Needed by zip protocol to reset the device
         */
        case SCMD_SDIAG:
        case SCMD_REZERO_UNIT:
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                cmd->cmd_actual_len = CDB_GROUP1;
                break;

        case SCMD_WRITE_VERIFY:
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                cmd->cmd_dir = CBW_DIR_OUT;
                cmd->cmd_xfercount = (pkt->pkt_cdbp[7] << 8) | pkt->pkt_cdbp[8];
                cmd->cmd_actual_len = CDB_GROUP1;
                break;

        /*
         * Next command does not have a SCSI equivalent as
         * it is vendor specific.
         * It was listed in the vendor's ATAPI Zip specs.
         */
        case SCMD_READ_FORMAT_CAP:
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                cmd->cmd_dir = CBW_DIR_IN;
                cmd->cmd_xfercount = (pkt->pkt_cdbp[7] << 8) | pkt->pkt_cdbp[8];
                cmd->cmd_actual_len = CDB_GROUP1;
                break;
        case IOMEGA_CMD_CARTRIDGE_PROTECT:
                cmd->cmd_dir = CBW_DIR_OUT;
                cmd->cmd_cdb[SCSA2USB_LBA_2] = pkt->pkt_cdbp[4];
                cmd->cmd_cdb[SCSA2USB_LBA_2] &= ~1;     /* Make it even */
                cmd->cmd_cdb[SCSA2USB_LUN] = pkt->pkt_cdbp[1];
                cmd->cmd_actual_len = CDB_GROUP0;
                cmd->cmd_xfercount = pkt->pkt_cdbp[4]; /* Length of password */
                break;

        /*
         * Do not convert SCMD_MODE_SENSE/SELECT to G1 cmds because
         * the mode header is different as well. USB devices don't
         * support 0x03 & 0x04 mode pages, which are already obsoleted
         * by SPC-2 specification.
         */
        case SCMD_MODE_SENSE:
        case SCMD_MODE_SELECT:
                if (((pkt->pkt_cdbp[2] & SD_MODE_SENSE_PAGE_MASK)
                    == SD_MODE_SENSE_PAGE3_CODE) ||
                    ((pkt->pkt_cdbp[2] & SD_MODE_SENSE_PAGE_MASK)
                    == SD_MODE_SENSE_PAGE4_CODE)) {
                        if (cmd->cmd_bp) {
                                cmd->cmd_pkt->pkt_resid = cmd->cmd_bp->b_bcount;
                        }
                        scsa2usb_force_invalid_request(scsa2usbp, cmd);
                        return (SCSA2USB_JUST_ACCEPT);
                }
                /* FALLTHROUGH */

        default:
                /*
                 * an unknown command may be a uscsi cmd which we
                 * should let go thru without mapping
                 */
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                if (cmd->cmd_bp) {
                        cmd->cmd_dir = (cmd->cmd_bp->b_flags & B_READ) ?
                            CBW_DIR_IN : CBW_DIR_OUT;
                        cmd->cmd_xfercount = cmd->cmd_bp->b_bcount;
                }

                break;
        } /* end of switch */

        USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_handle_scsi_cmd_sub_class: opcode = 0x%x count = 0x%lx",
            pkt->pkt_cdbp[SCSA2USB_OPCODE], cmd->cmd_xfercount);

        cmd->cmd_total_xfercount = cmd->cmd_xfercount;

        return (SCSA2USB_TRANSPORT);
}


/*
 * scsa2usb_do_tur is performed before READ CAPACITY command is issued.
 * It returns media status, 0 for media ready, -1 for media not ready
 * or other errors.
 */
static int
scsa2usb_do_tur(scsa2usb_state_t *scsa2usbp, struct scsi_address *ap)
{
        struct scsi_pkt         *pkt;
        scsa2usb_cmd_t          *turcmd;
        int                     rval = -1;

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_do_tur:");

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        mutex_exit(&scsa2usbp->scsa2usb_mutex);
        if ((pkt = scsi_init_pkt(ap, NULL, NULL, CDB_GROUP0, 1,
            PKT_PRIV_LEN, PKT_CONSISTENT, SLEEP_FUNC, NULL)) == NULL) {
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                    scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_do_tur: init pkt failed");

                return (rval);
        }

        RQ_MAKECOM_G0(pkt, FLAG_HEAD | FLAG_NODISCON,
            (char)SCMD_TEST_UNIT_READY, 0, 0);

        pkt->pkt_comp = NULL;
        pkt->pkt_time = PKT_DEFAULT_TIMEOUT;
        turcmd = PKT2CMD(pkt);

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        scsa2usb_prepare_pkt(scsa2usbp, turcmd->cmd_pkt);

        if (scsa2usb_cmd_transport(scsa2usbp, turcmd) != TRAN_ACCEPT) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                    scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_do_tur: cmd transport failed, "
                    "pkt_reason=0x%x", turcmd->cmd_pkt->pkt_reason);
        } else if (*(turcmd->cmd_pkt->pkt_scbp) != STATUS_GOOD) {
                /*
                 * Theoretically, the sense data should be retrieved and
                 * sense key be checked when check condition happens. If
                 * the sense key is UNIT ATTENTION, TEST UNIT READY cmd
                 * needs to be sent again to clear the UNIT ATTENTION and
                 * another TUR to be sent to get the real media status.
                 * But the AMI virtual floppy device simply cannot recover
                 * from UNIT ATTENTION by re-sending a TUR cmd, so it
                 * doesn't make any difference whether to check sense key
                 * or not. Just ignore sense key checking here and assume
                 * the device is NOT READY.
                 */
                USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                    scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_do_tur: media not ready");
        } else {
                rval = 0;
        }

        mutex_exit(&scsa2usbp->scsa2usb_mutex);
        scsi_destroy_pkt(pkt);
        mutex_enter(&scsa2usbp->scsa2usb_mutex);

        return (rval);
}


/*
 * Check this UFI command against the quirks for this particular device.
 * Returns a transport disposition.
 */
static int
scsa2usb_check_ufi_quirks(scsa2usb_state_t *scsa2usbp, scsa2usb_cmd_t *cmd)
{
        int rval = SCSA2USB_TRANSPORT;

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        switch (cmd->cmd_pkt->pkt_cdbp[0]) {
        case SCMD_PRIN:
        case SCMD_PROUT:
                rval = SCSA2USB_JUST_ACCEPT;
                break;
        case SCMD_MODE_SENSE:
        case SCMD_MODE_SELECT:
                if (cmd->cmd_bp) {
                        cmd->cmd_pkt->pkt_resid = cmd->cmd_bp->b_bcount;
                }
                scsa2usb_force_invalid_request(scsa2usbp, cmd);
                rval = SCSA2USB_JUST_ACCEPT;
                break;
        case SCMD_GET_CONFIGURATION:
                if (!(scsa2usbp->scsa2usb_attrs & SCSA2USB_ATTRS_GET_CONF)) {
                        rval = SCSA2USB_JUST_ACCEPT;
                }
                break;
        case SCMD_GET_PERFORMANCE:
                if (!(scsa2usbp->scsa2usb_attrs & SCSA2USB_ATTRS_GET_PERF)) {
                        rval = SCSA2USB_JUST_ACCEPT;
                }
                break;
        case SCMD_START_STOP:
                /*
                 * some CB/CBI devices don't have mechanics that spin the
                 * media up and down. So, it doesn't make much sense
                 * to issue this cmd to those devices.
                 */
                if (!(scsa2usbp->scsa2usb_attrs & SCSA2USB_ATTRS_START_STOP)) {
                        rval = SCSA2USB_JUST_ACCEPT;
                }
                break;
        case SCMD_READ_CAPACITY:
                /*
                 * Some devices don't support READ CAPACITY command
                 * when media is not ready. Need to check media status
                 * before issuing the cmd to such device.
                 */
                if (!(scsa2usbp->scsa2usb_attrs &
                    SCSA2USB_ATTRS_NO_MEDIA_CHECK)) {
                        struct scsi_pkt *pkt = cmd->cmd_pkt;

                        ASSERT(scsa2usbp->scsa2usb_cur_pkt == pkt);
                        scsa2usbp->scsa2usb_cur_pkt = NULL;

                        if (scsa2usb_do_tur(scsa2usbp,
                            &pkt->pkt_address) != 0) {
                                /* media not ready, force cmd invalid */
                                if (cmd->cmd_bp) {
                                        cmd->cmd_pkt->pkt_resid =
                                            cmd->cmd_bp->b_bcount;
                                }
                                scsa2usb_force_invalid_request(scsa2usbp, cmd);
                                rval = SCSA2USB_JUST_ACCEPT;
                        }

                        scsa2usbp->scsa2usb_cur_pkt = pkt;
                }
                break;
        default:
                break;
        }

        return (rval);
}


/*
 * scsa2usb_handle_ufi_subclass_cmd:
 *      prepare a UFI cmd
 *      returns SCSA2USB_TRANSPORT, SCSA2USB_REJECT
 */
int
scsa2usb_handle_ufi_subclass_cmd(scsa2usb_state_t *scsa2usbp,
    scsa2usb_cmd_t *cmd, struct scsi_pkt *pkt)
{
        uchar_t opcode = pkt->pkt_cdbp[0];

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_handle_ufi_subclass_cmd: cmd = 0x%p pkt = 0x%p",
            (void *)cmd, (void *)pkt);

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        bzero(&cmd->cmd_cdb, SCSI_CDB_SIZE);
        cmd->cmd_cdb[SCSA2USB_OPCODE] = opcode;   /* Set the opcode */
        cmd->cmd_cdb[SCSA2USB_LUN] = pkt->pkt_cdbp[1];

        /*
         * decode and convert the packet if necessary
         * for most cmds, we can bcopy the cdb
         */
        switch (opcode) {
        case SCMD_FORMAT:
                /* if parameter list is specified */
                if (pkt->pkt_cdbp[1] & 0x10) {
                        cmd->cmd_xfercount =
                            (pkt->pkt_cdbp[7] << 8) | pkt->pkt_cdbp[8];
                        cmd->cmd_dir = USB_EP_DIR_OUT;
                        cmd->cmd_actual_len = CDB_GROUP5;
                }
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                break;
        case SCMD_INQUIRY:
                cmd->cmd_dir = USB_EP_DIR_IN;
                cmd->cmd_actual_len = CDB_GROUP0;
                cmd->cmd_cdb[SCSA2USB_LBA_0] = pkt->pkt_cdbp[2];
                cmd->cmd_cdb[SCSA2USB_LBA_2] = cmd->cmd_xfercount =
                    min(SCSA2USB_MAX_INQ_LEN,
                    cmd->cmd_bp ? cmd->cmd_bp->b_bcount : 0);
                break;
        case SCMD_READ_CAPACITY:
                cmd->cmd_dir = USB_EP_DIR_IN;
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                cmd->cmd_xfercount = sizeof (scsa2usb_read_cap_t);
                break;
        case SCMD_REQUEST_SENSE:
                cmd->cmd_dir = USB_EP_DIR_IN;
                cmd->cmd_xfercount = pkt->pkt_cdbp[4];
                cmd->cmd_cdb[SCSA2USB_LBA_2] = pkt->pkt_cdbp[4];
                cmd->cmd_actual_len = CDB_GROUP0;
                break;

        /*
         * do not convert SCMD_MODE_SENSE/SELECT because the
         * mode header is different as well
         */

        /*
         * see usb_bulkonly.c for comments on the next set of commands
         */
        case SCMD_READ:
        case SCMD_WRITE:
        case SCMD_READ_G1:
        case SCMD_WRITE_G1:
        case SCMD_READ_G4:
        case SCMD_WRITE_G4:
        case SCMD_READ_G5:
        case SCMD_WRITE_G5:
        case SCMD_READ_LONG:
        case SCMD_WRITE_LONG:
        case SCMD_READ_CD:
                return (scsa2usb_rw_transport(scsa2usbp, pkt));

        case SCMD_TEST_UNIT_READY:
                /*
                 * Some CB/CBI devices may not support TUR.
                 */
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                break;
        case SCMD_READ_FORMAT_CAP:
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                cmd->cmd_dir = USB_EP_DIR_IN;
                cmd->cmd_actual_len = CDB_GROUP1;
                cmd->cmd_xfercount = (pkt->pkt_cdbp[7] << 8) | pkt->pkt_cdbp[8];
                break;
        case SCMD_WRITE_VERIFY:
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                cmd->cmd_dir = USB_EP_DIR_OUT;
                cmd->cmd_actual_len = CDB_GROUP1;
                cmd->cmd_xfercount = (pkt->pkt_cdbp[7] << 8) | pkt->pkt_cdbp[8];
                break;
        case SCMD_START_STOP:
                /* A larger timeout is needed for 'flaky' CD-RW devices */
                if (!(scsa2usbp->scsa2usb_attrs & SCSA2USB_ATTRS_BIG_TIMEOUT)) {
                        cmd->cmd_timeout = max(cmd->cmd_timeout,
                            20 * SCSA2USB_BULK_PIPE_TIMEOUT);
                }
                /* FALLTHRU */
        default:
                /*
                 * all other commands don't need special mapping
                 */
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                if (cmd->cmd_bp) {
                        cmd->cmd_dir = (cmd->cmd_bp->b_flags & B_READ) ?
                            CBW_DIR_IN : CBW_DIR_OUT;
                        cmd->cmd_xfercount = cmd->cmd_bp->b_bcount;
                }
                break;

        } /* end of switch */

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_handle_ufi_subclass_cmd: opcode = 0x%x count = 0x%lx",
            opcode, cmd->cmd_xfercount);

        cmd->cmd_total_xfercount = cmd->cmd_xfercount;

        return (SCSA2USB_TRANSPORT);
}


/*
 * scsa2usb_rw_transport:
 *      Handle splitting READ and WRITE requests to the
 *      device to a size that the host controller allows.
 *
 *      returns TRAN_* values and not USB_SUCCESS/FAILURE
 *
 * To support CD-R/CD-RW/DVD media, we need to support a
 * variety of block sizes for the different types of CD
 * data (audio, data, video, CD-XA, yellowbook, redbook etc.)
 *
 * Some of the block sizes used are:- 512, 1k, 2k, 2056, 2336
 * 2340, 2352, 2368, 2448, 2646, 2647 etc.
 *
 * NOTE: the driver could be entertaining a SCSI CDB that uses
 * any of the above listed block sizes at a given time, and a
 * totally different block size at any other given time for a
 * different CDB.
 *
 * We need to compute block size every time and figure out
 * matching LBA and LEN accordingly.
 *
 * Also UHCI has a limitation that it can only xfer 32k at a
 * given time. So, with "odd" sized blocks and a limitation of
 * how much we can xfer per shot, we need to compute xfer_count
 * as well each time.
 *
 * The same computation is also done in the function
 * scsa2usb_setup_next_xfer().  To save computing block_size in
 * this function, I am saving block_size in "cmd" now.
 */
int
scsa2usb_rw_transport(scsa2usb_state_t *scsa2usbp, struct scsi_pkt *pkt)
{
        scsa2usb_cmd_t *cmd = PKT2CMD(pkt);
        int dir, opcode;
        uint64_t lba;
        struct buf *bp = cmd->cmd_bp;
        size_t len, xfer_count;
        size_t blk_size;        /* calculate the block size to be used */
        int sz;

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_rw_transport:");

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        opcode = pkt->pkt_cdbp[0];
        blk_size = scsa2usbp->scsa2usb_lbasize[pkt->pkt_address.a_lun];
                                                /* set to default */

        switch (opcode) {
        case SCMD_READ:
                /*
                 * Note that READ/WRITE(6) are not supported by the drive.
                 * convert it into a 10 byte read/write.
                 */
                lba = SCSA2USB_LBA_6BYTE(pkt);
                len = SCSA2USB_LEN_6BYTE(pkt);
                opcode = SCMD_READ_G1;  /* Overwrite it w/ byte 10 cmd val */
                dir = USB_EP_DIR_IN;
                break;
        case SCMD_WRITE:
                lba = SCSA2USB_LBA_6BYTE(pkt);
                len = SCSA2USB_LEN_6BYTE(pkt);
                opcode = SCMD_WRITE_G1; /* Overwrite it w/ byte 10 cmd val */
                dir = USB_EP_DIR_OUT;
                break;
        case SCMD_READ_G1:
        case SCMD_READ_LONG:
                lba = SCSA2USB_LBA_10BYTE(pkt);
                len = SCSA2USB_LEN_10BYTE(pkt);
                dir = USB_EP_DIR_IN;
                break;
        case SCMD_WRITE_G1:
        case SCMD_WRITE_LONG:
                lba = SCSA2USB_LBA_10BYTE(pkt);
                len = SCSA2USB_LEN_10BYTE(pkt);
                dir = USB_EP_DIR_OUT;
                if (len > 0) {
                        sz = SCSA2USB_CDRW_BLKSZ(bp != NULL ?
                            bp->b_bcount : 0, len);
                        if (SCSA2USB_VALID_CDRW_BLKSZ(sz)) {
                                blk_size = sz;  /* change it accordingly */
                        }
                }
                break;
        case SCMD_READ_CD:
                lba = SCSA2USB_LBA_10BYTE(pkt);
                len = SCSA2USB_LEN_READ_CD(pkt);
                dir = USB_EP_DIR_IN;

                /* Figure out the block size */
                blk_size = scsa2usb_read_cd_blk_size(pkt->pkt_cdbp[1] >> 2);
                break;
        case SCMD_READ_G4:
                lba = SCSA2USB_LBA_16BYTE(pkt);
                len = SCSA2USB_LEN_16BYTE(pkt);
                dir = USB_EP_DIR_IN;
                break;
        case SCMD_WRITE_G4:
                lba = SCSA2USB_LBA_16BYTE(pkt);
                len = SCSA2USB_LEN_16BYTE(pkt);
                dir = USB_EP_DIR_OUT;
                break;
        case SCMD_READ_G5:
                lba = SCSA2USB_LBA_12BYTE(pkt);
                len = SCSA2USB_LEN_12BYTE(pkt);
                dir = USB_EP_DIR_IN;
                break;
        case SCMD_WRITE_G5:
                lba = SCSA2USB_LBA_12BYTE(pkt);
                len = SCSA2USB_LEN_12BYTE(pkt);
                dir = USB_EP_DIR_OUT;
                break;
        }

        cmd->cmd_total_xfercount = xfer_count = len * blk_size;

        /* reduce xfer count if necessary */
        if (blk_size != 0 &&
            xfer_count > scsa2usbp->scsa2usb_max_bulk_xfer_size) {
                /*
                 * For CD-RW devices reduce the xfer count based
                 * on the block size used by these devices. The
                 * block size could change for READ_CD and WRITE
                 * opcodes.
                 *
                 * Also as UHCI allows a max xfer of 32k at a time;
                 * compute the xfer_count based on the new block_size.
                 *
                 * The len part of the cdb changes as a result of that.
                 */
                if (SCSA2USB_VALID_CDRW_BLKSZ(blk_size)) {
                        xfer_count = (scsa2usbp->scsa2usb_max_bulk_xfer_size /
                            blk_size) * blk_size;
                        len = xfer_count / blk_size;
                        xfer_count = blk_size * len;
                } else {
                        xfer_count = scsa2usbp->scsa2usb_max_bulk_xfer_size;
                        len = xfer_count / blk_size;
                }
        }

        cmd->cmd_xfercount = xfer_count;
        cmd->cmd_dir = (uchar_t)dir;
        cmd->cmd_blksize = (int)blk_size;

        /*
         * Having figured out the 'partial' xfer len based on the
         * block size; fill it in to the cmd->cmd_cdb
         */
        cmd->cmd_cdb[SCSA2USB_OPCODE] = (uchar_t)opcode;
        switch (opcode) {
        case SCMD_READ_CD:
                bcopy(pkt->pkt_cdbp, &cmd->cmd_cdb, cmd->cmd_cdblen);
                scsa2usb_fill_up_ReadCD_cdb_len(cmd, len, CDB_GROUP5);
                scsa2usb_fill_up_cdb_lba(cmd, lba);
                break;
        case SCMD_WRITE_G4:
        case SCMD_READ_G4:
                scsa2usb_fill_up_16byte_cdb_len(cmd, len, CDB_GROUP4);
                scsa2usb_fill_up_g4_cdb_lba(cmd, lba);
                break;
        case SCMD_WRITE_G5:
        case SCMD_READ_G5:
                scsa2usb_fill_up_12byte_cdb_len(cmd, len, CDB_GROUP5);
                scsa2usb_fill_up_cdb_lba(cmd, lba);
                break;
        default:
                scsa2usb_fill_up_cdb_len(cmd, len);
                cmd->cmd_actual_len = CDB_GROUP1;
                scsa2usb_fill_up_cdb_lba(cmd, lba);
                break;
        }

        USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "bcount=0x%lx lba=0x%x len=0x%lx xfercount=0x%lx total=0x%lx",
            bp ? bp->b_bcount : 0, lba, len, cmd->cmd_xfercount,
            cmd->cmd_total_xfercount);

        /* Set the timeout value as per command request */
        if ((opcode == SCMD_WRITE_G1) && SCSA2USB_VALID_CDRW_BLKSZ(blk_size)) {
                /*
                 * We increase the time as CD-RW writes have two things
                 * to do. After writing out the data to the media, a
                 * TOC needs to be filled up at the beginning of the media
                 * This is when the write gets "finalized".
                 * Hence the actual write could take longer than the
                 * value specified in cmd->cmd_timeout.
                 */
                cmd->cmd_timeout *= 4;

                USB_DPRINTF_L4(DPRINT_MASK_SCSA,
                    scsa2usbp->scsa2usb_log_handle,
                    "new timeout value = 0x%x", cmd->cmd_timeout);
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "lba 0x%x len 0x%lx xfercount 0x%lx total 0x%lx",
            lba, len, cmd->cmd_xfercount, cmd->cmd_total_xfercount);

        return (SCSA2USB_TRANSPORT);
}


/*
 * scsa2usb_setup_next_xfer:
 *      For READs and WRITEs we split up the transfer in terms of
 *      HCD understood units. This function handles the split transfers.
 *
 * See comments in the previous function scsa2usb_rw_transport
 *
 * The lba computation was being done based on scsa2usb_max_bulk_xfer_size
 * earlier. With CD-RW devices, the xfer_count and the block_size may
 * no longer be a multiple of scsa2usb_max_bulk_xfer_size. So compute
 * xfer_count all over again. Adjust lba, based on the previous requests'
 * len. Find out the len and add it to cmd->cmd_lba to get the new lba
 */
void
scsa2usb_setup_next_xfer(scsa2usb_state_t *scsa2usbp, scsa2usb_cmd_t *cmd)
{
        int xfer_len = min(scsa2usbp->scsa2usb_max_bulk_xfer_size,
            cmd->cmd_total_xfercount);
        int cdb_len;
        size_t blk_size;

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_setup_next_xfer: opcode = 0x%x lba = 0x%x "
            "total count = 0x%lx", cmd->cmd_cdb[SCSA2USB_OPCODE],
            cmd->cmd_lba, cmd->cmd_total_xfercount);

        ASSERT(cmd->cmd_total_xfercount > 0);
        cmd->cmd_xfercount = xfer_len;
        blk_size = scsa2usbp->scsa2usb_lbasize[
            cmd->cmd_pkt->pkt_address.a_lun];

        /*
         * For CD-RW devices reduce the xfer count based on the
         * block_size used by these devices. See changes below
         * where xfer_count is being adjusted.
         *
         * Also adjust len/lba based on the block_size and xfer_count.
         * NOTE: Always calculate lba first, as it based on previous
         * commands' values.
         */
        switch (cmd->cmd_cdb[SCSA2USB_OPCODE]) {
        case SCMD_READ_CD:
                /* calculate lba = current_lba + len_of_prev_cmd */
                cmd->cmd_lba += (cmd->cmd_cdb[6] << 16) +
                    (cmd->cmd_cdb[7] << 8) + cmd->cmd_cdb[8];
                cdb_len = xfer_len / cmd->cmd_blksize;
                cmd->cmd_cdb[SCSA2USB_READ_CD_LEN_2] = (uchar_t)cdb_len;
                /* re-adjust xfer count */
                cmd->cmd_xfercount = cdb_len * cmd->cmd_blksize;
                scsa2usb_fill_up_cdb_lba(cmd, cmd->cmd_lba);
                break;
        case SCMD_WRITE_G4:
        case SCMD_READ_G4:
                /* calculate lba = current_lba + len_of_prev_cmd */
                cmd->cmd_lba += (cmd->cmd_cdb[10] << 24) +
                    (cmd->cmd_cdb[11] << 16) + (cmd->cmd_cdb[12] << 8) +
                    cmd->cmd_cdb[13];
                if (blk_size != 0) {
                        xfer_len /= blk_size;
                }
                scsa2usb_fill_up_16byte_cdb_len(cmd, xfer_len, CDB_GROUP5);
                scsa2usb_fill_up_g4_cdb_lba(cmd, cmd->cmd_lba);
                break;
        case SCMD_WRITE_G5:
        case SCMD_READ_G5:
                /* calculate lba = current_lba + len_of_prev_cmd */
                cmd->cmd_lba += (cmd->cmd_cdb[6] << 24) +
                    (cmd->cmd_cdb[7] << 16) + (cmd->cmd_cdb[8] << 8) +
                    cmd->cmd_cdb[9];
                if (blk_size != 0) {
                        xfer_len /= blk_size;
                }
                scsa2usb_fill_up_12byte_cdb_len(cmd, xfer_len, CDB_GROUP5);
                scsa2usb_fill_up_cdb_lba(cmd, cmd->cmd_lba);
                break;
        case SCMD_WRITE_G1:
        case SCMD_WRITE_LONG:
                /* calculate lba = current_lba + len_of_prev_cmd */
                cmd->cmd_lba += (cmd->cmd_cdb[7] << 8) + cmd->cmd_cdb[8];
                if (SCSA2USB_VALID_CDRW_BLKSZ(cmd->cmd_blksize)) {
                        blk_size = cmd->cmd_blksize;
                }
                cdb_len = xfer_len / blk_size;
                scsa2usb_fill_up_cdb_len(cmd, cdb_len);
                /* re-adjust xfer count */
                cmd->cmd_xfercount = cdb_len * blk_size;
                scsa2usb_fill_up_cdb_lba(cmd, cmd->cmd_lba);
                break;
        default:
                if (blk_size != 0) {
                        xfer_len /= blk_size;
                }
                scsa2usb_fill_up_cdb_len(cmd, xfer_len);
                cmd->cmd_lba += scsa2usbp->scsa2usb_max_bulk_xfer_size /
                    blk_size;
                scsa2usb_fill_up_cdb_lba(cmd, cmd->cmd_lba);
                break;
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_setup_next_xfer:\n\tlba = 0x%x xfer_len = 0x%x "
            "xfercount = 0x%lx total = 0x%lx", cmd->cmd_lba, xfer_len,
            cmd->cmd_xfercount, cmd->cmd_total_xfercount);
}


/*
 * take one request from the lun's waitQ and transport it
 */
static void
scsa2usb_transport_request(scsa2usb_state_t *scsa2usbp, uint_t lun)
{
        int                     rval;
        struct scsi_pkt         *pkt;
        struct scsa2usb_cmd     *cmd, *arqcmd;

        if ((cmd = (scsa2usb_cmd_t *)
            usba_rm_first_pvt_from_list(
            &scsa2usbp->scsa2usb_waitQ[lun])) == NULL) {

                return;
        }
        pkt = cmd->cmd_pkt;

        /*
         * if device has been disconnected, just complete it
         */
        if (scsa2usbp->scsa2usb_dev_state == USB_DEV_DISCONNECTED) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "device not accessible");
                pkt->pkt_reason = CMD_DEV_GONE;
                SCSA2USB_SET_PKT_DO_COMP_STATE(scsa2usbp);
                scsa2usb_pkt_completion(scsa2usbp, pkt);

                return;
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA,
            scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_transport_request: cmd=0x%p bp=0x%p addr=0x%p",
            (void *)cmd, (void *)cmd->cmd_bp,
            (void *)(cmd->cmd_bp ? cmd->cmd_bp->b_un.b_addr : NULL));

        rval = scsa2usb_cmd_transport(scsa2usbp, cmd);

        USB_DPRINTF_L3(DPRINT_MASK_SCSA,
            scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_transport_request: transport rval = %d",
            rval);

        if (scsa2usbp->scsa2usb_cur_pkt == NULL) {

                return;
        }

        ASSERT(pkt == scsa2usbp->scsa2usb_cur_pkt);

        if (ddi_in_panic()) {
                pkt->pkt_reason = CMD_CMPLT;
                scsa2usb_pkt_completion(scsa2usbp, pkt);

                return;
        }

        /*
         * start an auto-request sense iff
         * there was a check condition, we have enough
         * space in the status block, and we have not
         * faked an auto request sense
         */
        if ((*(pkt->pkt_scbp) == STATUS_CHECK) &&
            (cmd->cmd_scblen >= sizeof (struct scsi_arq_status)) &&
            ((pkt->pkt_state & STATE_ARQ_DONE) == 0) &&
            (scsa2usb_create_arq_pkt(scsa2usbp,
            &pkt->pkt_address) == USB_SUCCESS)) {
                arqcmd = scsa2usbp->scsa2usb_arq_cmd;

                /*
                 * copy the timeout from the
                 * original packet
                 * for lack of a better value
                 */
                arqcmd->cmd_pkt->pkt_time = pkt->pkt_time;
                scsa2usb_prepare_pkt(scsa2usbp,
                    arqcmd->cmd_pkt);

                scsa2usbp->scsa2usb_cur_pkt = NULL;
                if (scsa2usb_cmd_transport(
                    scsa2usbp, arqcmd) == TRAN_ACCEPT) {

                        /* finish w/ this packet */
                        scsa2usb_complete_arq_pkt(
                            scsa2usbp, arqcmd->cmd_pkt, cmd,
                            scsa2usbp->scsa2usb_arq_bp);

                        /*
                         * we have valid request sense
                         * data so clear the pkt_reason
                         */
                        pkt->pkt_reason = CMD_CMPLT;
                }
                scsa2usbp->scsa2usb_cur_pkt = pkt;
                scsa2usb_delete_arq_pkt(scsa2usbp);
        }

        if ((rval != TRAN_ACCEPT) &&
            (pkt->pkt_reason == CMD_CMPLT)) {
                pkt->pkt_reason = CMD_TRAN_ERR;
        }

        SCSA2USB_SET_PKT_DO_COMP_STATE(scsa2usbp);
        scsa2usb_pkt_completion(scsa2usbp, pkt);

        ASSERT(scsa2usbp->scsa2usb_cur_pkt == NULL);
}


/*
 * scsa2usb_work_thread:
 *      The taskq thread that kicks off the transport (BO and CB/CBI)
 */
static void
scsa2usb_work_thread(void *arg)
{
        scsa2usb_state_t        *scsa2usbp = (scsa2usb_state_t *)arg;
        uint_t                  lun;
        uint_t                  count;

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_work_thread start: thread_id=0x%p",
            (void *)scsa2usbp->scsa2usb_work_thread_id);

        ASSERT(scsa2usbp->scsa2usb_work_thread_id == (kthread_t *)1);
        scsa2usbp->scsa2usb_work_thread_id = curthread;

        /* exclude ugen accesses */
        while (scsa2usbp->scsa2usb_transport_busy) {
                cv_wait(&scsa2usbp->scsa2usb_transport_busy_cv,
                    &scsa2usbp->scsa2usb_mutex);
        }
        ASSERT(scsa2usbp->scsa2usb_ugen_open_count == 0);
        scsa2usbp->scsa2usb_transport_busy++;
        scsa2usbp->scsa2usb_busy_proc = curproc;

        scsa2usb_raise_power(scsa2usbp);

        /* reopen the pipes if necessary */
        (void) scsa2usb_open_usb_pipes(scsa2usbp);

        for (;;) {
                ASSERT(scsa2usbp->scsa2usb_ugen_open_count == 0);
                for (lun = 0; lun < scsa2usbp->scsa2usb_n_luns; lun++) {
                        scsa2usb_transport_request(scsa2usbp, lun);
                }
                count = 0;
                for (lun = 0; lun < SCSA2USB_MAX_LUNS; lun++) {
                        count += usba_list_entry_count(
                            &scsa2usbp->scsa2usb_waitQ[lun]);
                }

                if (count == 0) {

                        break;
                }
        }

        scsa2usbp->scsa2usb_work_thread_id = 0;

        ASSERT(scsa2usbp->scsa2usb_ugen_open_count == 0);

        scsa2usbp->scsa2usb_transport_busy--;
        scsa2usbp->scsa2usb_busy_proc = NULL;
        cv_signal(&scsa2usbp->scsa2usb_transport_busy_cv);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_work_thread: exit");

        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        scsa2usb_pm_idle_component(scsa2usbp);
}


/*
 * scsa2usb_flush_waitQ:
 *      empties the entire waitQ with errors asap.
 *
 * It is called from scsa2usb_scsi_reset and scsa2usb_panic_callb.
 * If the device is reset; we should empty the waitQ right away.
 * If the system has paniced; we should empty the waitQ right away.
 *
 * CPR suspend will only succeed if device is idle. No need to call
 * this function for CPR suspend case.
 */
static void
scsa2usb_flush_waitQ(scsa2usb_state_t *scsa2usbp, uint_t lun,
    uchar_t error)
{
        struct scsi_pkt         *pkt;
        struct scsa2usb_cmd     *cmd;
        usba_list_entry_t       head;

        mutex_enter(&scsa2usbp->scsa2usb_mutex);

        usba_move_list(&scsa2usbp->scsa2usb_waitQ[lun], &head,
            scsa2usbp->scsa2usb_dev_data->dev_iblock_cookie);
        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        while ((cmd = (scsa2usb_cmd_t *)usba_rm_first_pvt_from_list(&head)) !=
            NULL) {
                pkt = cmd->cmd_pkt;
                pkt->pkt_reason = error;        /* set error */

                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                scsa2usbp->scsa2usb_pkt_state = SCSA2USB_PKT_DO_COMP;
                scsa2usb_pkt_completion(scsa2usbp, pkt);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
        } /* end of while */
}


/*
 * scsa2usb_do_inquiry is performed before INIT CHILD and we have
 * to fake a few things normally done by SCSA
 */
static void
scsa2usb_do_inquiry(scsa2usb_state_t *scsa2usbp, uint_t target, uint_t lun)
{
        struct buf      *bp;
        struct scsi_pkt *pkt;
        struct scsi_address ap;
        int             len = SCSA2USB_MAX_INQ_LEN;

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_do_inquiry: %d bytes", len);

        /* is it inquiry-challenged? */
        if (!(scsa2usbp->scsa2usb_attrs & SCSA2USB_ATTRS_INQUIRY)) {
                scsa2usb_fake_inquiry(scsa2usbp,
                    &scsa2usbp->scsa2usb_lun_inquiry[lun]);
                return;
        }

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        bzero(&ap, sizeof (struct scsi_address));
        ap.a_hba_tran = scsa2usbp->scsa2usb_tran;
        ap.a_target = (ushort_t)target;
        ap.a_lun = (uchar_t)lun;

        /* limit inquiry to 36 bytes */
        mutex_exit(&scsa2usbp->scsa2usb_mutex);
        if ((bp = scsi_alloc_consistent_buf(&ap, (struct buf *)NULL,
            len, B_READ, SLEEP_FUNC, NULL)) == NULL) {
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                    scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_do_inquiry: failed");

                return;
        }

        pkt = scsi_init_pkt(&ap, NULL, bp, CDB_GROUP0, 1,
            PKT_PRIV_LEN, PKT_CONSISTENT, SLEEP_FUNC, NULL);

        RQ_MAKECOM_G0(pkt, FLAG_NOINTR, (char)SCMD_INQUIRY, 0, (char)len);

        pkt->pkt_comp = NULL;
        pkt->pkt_time = 5;
        bzero(bp->b_un.b_addr, len);

        USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_do_inquiry:INQUIRY");

        (void) scsi_transport(pkt);

        if (pkt->pkt_reason) {
                USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                    scsa2usbp->scsa2usb_log_handle,
                    "INQUIRY failed, cannot determine device type, "
                    "pkt_reason=0x%x", pkt->pkt_reason);

                /* not much hope for other cmds, reduce */
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                scsa2usbp->scsa2usb_attrs &=
                    ~SCSA2USB_ATTRS_REDUCED_CMD;
                scsa2usb_fake_inquiry(scsa2usbp,
                    &scsa2usbp->scsa2usb_lun_inquiry[lun]);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
        }

        scsi_destroy_pkt(pkt);
        scsi_free_consistent_buf(bp);

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
}


/*
 * scsa2usb_fake_inquiry:
 *    build an inquiry for a given device that doesnt like inquiry
 *    commands.
 */
static void
scsa2usb_fake_inquiry(scsa2usb_state_t *scsa2usbp, struct scsi_inquiry *inqp)
{
        usb_client_dev_data_t *dev_data = scsa2usbp->scsa2usb_dev_data;
        int len;

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_fake_inquiry:");

        bzero(inqp, sizeof (struct scsi_inquiry));
        for (len = 0; len < sizeof (inqp->inq_vid); len++) {
                *(inqp->inq_vid + len) = ' ';
        }

        for (len = 0; len < sizeof (inqp->inq_pid); len++) {
                *(inqp->inq_pid + len) = ' ';
        }

        inqp->inq_dtype = DTYPE_DIRECT;
        inqp->inq_rmb = 1;
        inqp->inq_ansi = 2;
        inqp->inq_rdf = RDF_SCSI2;
        inqp->inq_len = sizeof (struct scsi_inquiry)-4;

        /* Fill in the Vendor id/Product id strings */
        if (dev_data->dev_mfg) {
                if ((len = strlen(dev_data->dev_mfg)) >
                    sizeof (inqp->inq_vid)) {
                        len = sizeof (inqp->inq_vid);
                }
                bcopy(dev_data->dev_mfg, inqp->inq_vid, len);
        }

        if (dev_data->dev_product) {
                if ((len = strlen(dev_data->dev_product)) >
                    sizeof (inqp->inq_pid)) {
                        len = sizeof (inqp->inq_pid);
                }
                bcopy(dev_data->dev_product, inqp->inq_pid, len);
        }

        /* Set the Revision to the Device */
        inqp->inq_revision[0] = 0x30 +
            ((dev_data->dev_descr->bcdDevice>>12) & 0xF);
        inqp->inq_revision[1] = 0x30 +
            ((dev_data->dev_descr->bcdDevice>>8) & 0xF);
        inqp->inq_revision[2] = 0x30 +
            ((dev_data->dev_descr->bcdDevice>>4) & 0xF);
        inqp->inq_revision[3] = 0x30 +
            ((dev_data->dev_descr->bcdDevice) & 0xF);
}


/*
 * scsa2usb_create_arq_pkt:
 *      Create and ARQ packet to get request sense data
 */
static int
scsa2usb_create_arq_pkt(scsa2usb_state_t *scsa2usbp, struct scsi_address *ap)
{
        struct buf *bp;
        scsa2usb_cmd_t *arq_cmd;

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_create_arq_pkt: scsa2usbp: %p, ap: %p",
            (void *)scsa2usbp, (void *)ap);

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        mutex_exit(&scsa2usbp->scsa2usb_mutex);
        if ((bp = scsi_alloc_consistent_buf(ap, (struct buf *)NULL,
            SENSE_LENGTH, B_READ, SLEEP_FUNC, NULL)) == NULL) {
                mutex_enter(&scsa2usbp->scsa2usb_mutex);

                return (USB_FAILURE);
        }

        arq_cmd = PKT2CMD(scsi_init_pkt(ap, NULL, bp, CDB_GROUP0, 1,
            PKT_PRIV_LEN, PKT_CONSISTENT, SLEEP_FUNC, NULL));
        mutex_enter(&scsa2usbp->scsa2usb_mutex);

        RQ_MAKECOM_G0(arq_cmd->cmd_pkt,
            FLAG_SENSING | FLAG_HEAD | FLAG_NODISCON,
            (char)SCMD_REQUEST_SENSE, 0, (char)SENSE_LENGTH);

        arq_cmd->cmd_pkt->pkt_ha_private = arq_cmd;
        scsa2usbp->scsa2usb_arq_cmd = arq_cmd;
        scsa2usbp->scsa2usb_arq_bp = bp;
        arq_cmd->cmd_pkt->pkt_comp = NULL;
        bzero(bp->b_un.b_addr, SENSE_LENGTH);

        return (USB_SUCCESS);
}


/*
 * scsa2usb_delete_arq_pkt:
 *      Destroy the ARQ packet
 */
static void
scsa2usb_delete_arq_pkt(scsa2usb_state_t *scsa2usbp)
{
        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_delete_arq_pkt: cmd: 0x%p",
            (void *)scsa2usbp->scsa2usb_arq_cmd);

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        if (scsa2usbp->scsa2usb_arq_cmd != NULL) {
                scsi_destroy_pkt(scsa2usbp->scsa2usb_arq_cmd->cmd_pkt);
                scsi_free_consistent_buf(scsa2usbp->scsa2usb_arq_bp);
        }
        scsa2usbp->scsa2usb_arq_cmd = NULL;
        scsa2usbp->scsa2usb_arq_bp = NULL;
}


/*
 * scsa2usb_complete_arq_pkt:
 *      finish processing the arq packet
 */
static void
scsa2usb_complete_arq_pkt(scsa2usb_state_t *scsa2usbp,
    struct scsi_pkt *pkt, scsa2usb_cmd_t *ssp, struct buf *bp)
{
        scsa2usb_cmd_t          *sp = pkt->pkt_ha_private;
        struct scsi_arq_status  *arqp;

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        arqp = (struct scsi_arq_status *)(ssp->cmd_pkt->pkt_scbp);
        arqp->sts_rqpkt_status = *((struct scsi_status *)
            (sp->cmd_pkt->pkt_scbp));
        arqp->sts_rqpkt_reason = CMD_CMPLT;
        arqp->sts_rqpkt_state |= STATE_XFERRED_DATA;
        arqp->sts_rqpkt_statistics = arqp->sts_rqpkt_resid = 0;

        /* is this meaningful sense data */
        if (*(bp->b_un.b_addr) != 0) {
                bcopy(bp->b_un.b_addr, &arqp->sts_sensedata, SENSE_LENGTH);
                ssp->cmd_pkt->pkt_state |= STATE_ARQ_DONE;
        }

        /* we will not sense start cmd until we receive a NOT READY */
        if (arqp->sts_sensedata.es_key == KEY_NOT_READY) {
                scsa2usbp->scsa2usb_rcvd_not_ready = B_TRUE;
        }
}


/*
 * Miscellaneous functions for any command/transport
 */
/*
 * scsa2usb_open_usb_pipes:
 *      set up a pipe policy
 *      open usb bulk pipes (BO and CB/CBI)
 *      open usb interrupt pipe (CBI)
 */
static int
scsa2usb_open_usb_pipes(scsa2usb_state_t *scsa2usbp)
{
        int                     rval;
        usb_pipe_policy_t       policy; /* bulk pipe policy */
        size_t                  sz;

        ASSERT(scsa2usbp);
        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_open_usb_pipes: dip = 0x%p flag = 0x%x",
            (void *)scsa2usbp->scsa2usb_dip, scsa2usbp->scsa2usb_flags);

        if (!(scsa2usbp->scsa2usb_flags & SCSA2USB_FLAGS_PIPES_OPENED)) {

                /*
                 * one pipe policy for all bulk pipes
                 */
                bzero(&policy, sizeof (usb_pipe_policy_t));
                /* at least 2, for the normal and exceptional callbacks */
                policy.pp_max_async_reqs = 1;

                USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_open_usb_pipes: opening bulk pipes");

                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                /* Open the USB bulk-in pipe */
                if ((rval = usb_pipe_xopen(scsa2usbp->scsa2usb_dip,
                    &scsa2usbp->scsa2usb_bulkin_xept, &policy, USB_FLAGS_SLEEP,
                    &scsa2usbp->scsa2usb_bulkin_pipe)) != USB_SUCCESS) {
                        mutex_enter(&scsa2usbp->scsa2usb_mutex);
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "scsa2usb_open_usb_pipes: bulk/in pipe open "
                            " failed rval = %d", rval);

                        return (USB_FAILURE);
                }

                /* Open the bulk-out pipe  using the same policy */
                if ((rval = usb_pipe_xopen(scsa2usbp->scsa2usb_dip,
                    &scsa2usbp->scsa2usb_bulkout_xept, &policy, USB_FLAGS_SLEEP,
                    &scsa2usbp->scsa2usb_bulkout_pipe)) != USB_SUCCESS) {
                        usb_pipe_close(scsa2usbp->scsa2usb_dip,
                            scsa2usbp->scsa2usb_bulkin_pipe,
                            USB_FLAGS_SLEEP, NULL, NULL);

                        mutex_enter(&scsa2usbp->scsa2usb_mutex);
                        scsa2usbp->scsa2usb_bulkin_pipe = NULL;

                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "scsa2usb_open_usb_pipes: bulk/out pipe open"
                            " failed rval = %d", rval);

                        return (USB_FAILURE);
                }

                mutex_enter(&scsa2usbp->scsa2usb_mutex);

                /* open interrupt pipe for CBI protocol */
                if (SCSA2USB_IS_CBI(scsa2usbp)) {
                        mutex_exit(&scsa2usbp->scsa2usb_mutex);

                        if ((rval = usb_pipe_xopen(scsa2usbp->scsa2usb_dip,
                            &scsa2usbp->scsa2usb_intr_xept, &policy,
                            USB_FLAGS_SLEEP, &scsa2usbp->scsa2usb_intr_pipe)) !=
                            USB_SUCCESS) {
                                usb_pipe_close(scsa2usbp->scsa2usb_dip,
                                    scsa2usbp->scsa2usb_bulkin_pipe,
                                    USB_FLAGS_SLEEP, NULL, NULL);

                                usb_pipe_close(scsa2usbp->scsa2usb_dip,
                                    scsa2usbp->scsa2usb_bulkout_pipe,
                                    USB_FLAGS_SLEEP, NULL, NULL);

                                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                                scsa2usbp->scsa2usb_bulkin_pipe = NULL;
                                scsa2usbp->scsa2usb_bulkout_pipe = NULL;

                                USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                                    scsa2usbp->scsa2usb_log_handle,
                                    "scsa2usb_open_usb_pipes: intr pipe open"
                                    " failed rval = %d", rval);

                                return (USB_FAILURE);
                        }

                        mutex_enter(&scsa2usbp->scsa2usb_mutex);
                }

                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                /* get the max transfer size of the bulk pipe */
                if (usb_pipe_get_max_bulk_transfer_size(scsa2usbp->scsa2usb_dip,
                    &sz) == USB_SUCCESS) {
                        mutex_enter(&scsa2usbp->scsa2usb_mutex);
                        scsa2usbp->scsa2usb_max_bulk_xfer_size = sz;
                } else {
                        mutex_enter(&scsa2usbp->scsa2usb_mutex);
                        scsa2usbp->scsa2usb_max_bulk_xfer_size = DEV_BSIZE;
                }

                /* limit the xfer size */
                scsa2usbp->scsa2usb_max_bulk_xfer_size = min(
                    scsa2usbp->scsa2usb_max_bulk_xfer_size,
                    scsa2usb_max_bulk_xfer_size);

                USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_open_usb_pipes: max bulk transfer size = %lx",
                    scsa2usbp->scsa2usb_max_bulk_xfer_size);

                /* Set the pipes opened flag */
                scsa2usbp->scsa2usb_flags |= SCSA2USB_FLAGS_PIPES_OPENED;

                scsa2usbp->scsa2usb_pipe_state = SCSA2USB_PIPE_NORMAL;

                /* Set the state to NONE */
                scsa2usbp->scsa2usb_pkt_state = SCSA2USB_PKT_NONE;
        }

        return (USB_SUCCESS);
}


/*
 * scsa2usb_close_usb_pipes:
 *      close all pipes synchronously
 */
void
scsa2usb_close_usb_pipes(scsa2usb_state_t *scsa2usbp)
{
        usb_flags_t flags = USB_FLAGS_SLEEP;

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_close_usb_pipes: scsa2usb_state = 0x%p",
            (void *)scsa2usbp);

        ASSERT(scsa2usbp);
        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        if ((scsa2usbp->scsa2usb_flags & SCSA2USB_FLAGS_PIPES_OPENED) == 0) {

                return;
        }

        scsa2usbp->scsa2usb_pipe_state = SCSA2USB_PIPE_CLOSING;
        /* to avoid races, reset the flag first */
        scsa2usbp->scsa2usb_flags &= ~SCSA2USB_FLAGS_PIPES_OPENED;

        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        usb_pipe_close(scsa2usbp->scsa2usb_dip,
            scsa2usbp->scsa2usb_bulkout_pipe, flags, NULL, NULL);

        usb_pipe_close(scsa2usbp->scsa2usb_dip,
            scsa2usbp->scsa2usb_bulkin_pipe, flags, NULL, NULL);

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        if (SCSA2USB_IS_CBI(scsa2usbp)) {
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
                usb_pipe_close(scsa2usbp->scsa2usb_dip,
                    scsa2usbp->scsa2usb_intr_pipe, flags, NULL, NULL);
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
        }
        scsa2usbp->scsa2usb_bulkout_pipe = NULL;
        scsa2usbp->scsa2usb_bulkin_pipe = NULL;
        scsa2usbp->scsa2usb_intr_pipe = NULL;

        scsa2usbp->scsa2usb_pipe_state = SCSA2USB_PIPE_NORMAL;
}


/*
 * scsa2usb_fill_up_cdb_lba:
 *      fill up command CDBs' LBA part
 */
static void
scsa2usb_fill_up_cdb_lba(scsa2usb_cmd_t *cmd, uint64_t lba)
{
        /* zero cdb1, lba bits so they won't get copied in the new cdb */
        cmd->cmd_cdb[SCSA2USB_LUN] &= 0xE0;
        cmd->cmd_cdb[SCSA2USB_LBA_0] = lba >> 24;
        cmd->cmd_cdb[SCSA2USB_LBA_1] = lba >> 16;
        cmd->cmd_cdb[SCSA2USB_LBA_2] = lba >> 8;
        cmd->cmd_cdb[SCSA2USB_LBA_3] = (uchar_t)lba;
        cmd->cmd_lba = lba;
}


/*
 * scsa2usb_fill_up_g4_cdb_lba:
 *      fill in the CDB for a Group 4 command (16-byte CDB)
 */
static void
scsa2usb_fill_up_g4_cdb_lba(scsa2usb_cmd_t *cmd, uint64_t lba)
{
        /* zero cdb1, lba bits so they won't get copied in the new cdb */
        cmd->cmd_cdb[SCSA2USB_LUN] &= 0xE0;
        cmd->cmd_cdb[2] = lba >> 56;
        cmd->cmd_cdb[3] = lba >> 48;
        cmd->cmd_cdb[4] = lba >> 40;
        cmd->cmd_cdb[5] = lba >> 32;
        cmd->cmd_cdb[6] = lba >> 24;
        cmd->cmd_cdb[7] = lba >> 16;
        cmd->cmd_cdb[8] = lba >> 8;
        cmd->cmd_cdb[9] = lba;
        cmd->cmd_lba = lba;
}


/*
 * scsa2usb_fill_up_ReadCD_cdb_len:
 *      fill up READ_CD command CDBs' len part
 */
static void
scsa2usb_fill_up_ReadCD_cdb_len(scsa2usb_cmd_t *cmd, int len, int actual_len)
{
        cmd->cmd_cdb[SCSA2USB_READ_CD_LEN_0] = len >> 16;
        cmd->cmd_cdb[SCSA2USB_READ_CD_LEN_1] = len >> 8;
        cmd->cmd_cdb[SCSA2USB_READ_CD_LEN_2] = (uchar_t)len;
        cmd->cmd_actual_len = (uchar_t)actual_len;
}


/*
 * scsa2usb_fill_up_16byte_cdb_len:
 *      populate CDB length field for SCMD_WRITE_G4 and SCMD_READ_G4
 */
static void
scsa2usb_fill_up_16byte_cdb_len(scsa2usb_cmd_t *cmd, int len, int actual_len)
{
        cmd->cmd_cdb[10] = len >> 24;
        cmd->cmd_cdb[11] = len >> 16;
        cmd->cmd_cdb[12] = len >> 8;
        cmd->cmd_cdb[13] = len;
        cmd->cmd_actual_len = actual_len;
}


/*
 * scsa2usb_fill_up_12byte_cdb_len:
 *      fill up generic 12-byte command CDBs' len part
 */
static void
scsa2usb_fill_up_12byte_cdb_len(scsa2usb_cmd_t *cmd, int len, int actual_len)
{
        cmd->cmd_cdb[6] = len >> 24;
        cmd->cmd_cdb[7] = len >> 16;
        cmd->cmd_cdb[8] = len >> 8;
        cmd->cmd_cdb[9] = (uchar_t)len;
        cmd->cmd_actual_len = (uchar_t)actual_len;
}


/*
 * scsa2usb_fill_up_cdb_len:
 *      fill up generic 10-byte command CDBs' len part
 */
static void
scsa2usb_fill_up_cdb_len(scsa2usb_cmd_t *cmd, int len)
{
        cmd->cmd_cdb[SCSA2USB_LEN_0] = len >> 8;
        cmd->cmd_cdb[SCSA2USB_LEN_1] = (uchar_t)len;
}


/*
 * scsa2usb_read_cd_blk_size:
 *      For SCMD_READ_CD opcode (0xbe). Figure out the
 *      block size based on expected sector type field
 *      definition. See MMC SCSI Specs section 6.1.15
 *
 *      Based on the value of the "expected_sector_type"
 *      field, the block size could be different.
 */
static int
scsa2usb_read_cd_blk_size(uchar_t expected_sector_type)
{
        int blk_size;

        switch (expected_sector_type) {
        case READ_CD_EST_CDDA:
                blk_size = CDROM_BLK_2352;
                break;
        case READ_CD_EST_MODE2:
                blk_size = CDROM_BLK_2336;
                break;
        case READ_CD_EST_MODE2FORM2:
                blk_size = CDROM_BLK_2324;
                break;
        case READ_CD_EST_MODE2FORM1:
        case READ_CD_EST_ALLTYPE:
        case READ_CD_EST_MODE1:
        default:
                blk_size = CDROM_BLK_2048;
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, NULL, "scsa2usb_read_cd_blk_size: "
            "est = 0x%x blk_size = %d", expected_sector_type, blk_size);

        return (blk_size);
}


/*
 * scsa2usb_bp_to_mblk:
 *      Convert a bp to mblk_t. USBA framework understands mblk_t.
 */
static mblk_t *
scsa2usb_bp_to_mblk(scsa2usb_state_t *scsa2usbp)
{
        size_t          size;
        mblk_t          *mp;
        struct buf      *bp;
        scsa2usb_cmd_t  *cmd = PKT2CMD(scsa2usbp->scsa2usb_cur_pkt);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_bp_to_mblk: ");

        ASSERT(scsa2usbp->scsa2usb_cur_pkt);
        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        bp = cmd->cmd_bp;

        if (bp && (bp->b_bcount > 0)) {
                size = ((bp->b_bcount > cmd->cmd_xfercount) ?
                    cmd->cmd_xfercount : bp->b_bcount);
        } else {

                return (NULL);
        }

        mp = esballoc_wait((uchar_t *)bp->b_un.b_addr + cmd->cmd_offset,
            size, BPRI_LO, &frnop);

        USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_bp_to_mblk: "
            "mp=0x%p bp=0x%p pkt=0x%p off=0x%lx sz=%lu add=0x%p",
            (void *)mp, (void *)bp, (void *)scsa2usbp->scsa2usb_cur_pkt,
            cmd->cmd_offset, bp->b_bcount - cmd->cmd_offset,
            (void *)bp->b_un.b_addr);

        mp->b_wptr += size;
        cmd->cmd_offset += size;

        return (mp);
}


/*
 * scsa2usb_handle_data_start:
 *      Initiate the data xfer. It could be IN/OUT direction.
 *
 *      Data IN:
 *              Send out the bulk-xfer request
 *              if rval implies STALL
 *                      clear endpoint stall and reset bulk-in pipe
 *                      handle data read in so far; set cmd->cmd_done
 *                      also adjust data xfer length accordingly
 *              else other error
 *                      report back to transport
 *                      typically transport will call reset recovery
 *              else (no error)
 *                      return success
 *
 *      Data OUT:
 *              Send out the bulk-xfer request
 *              if rval implies STALL
 *                      clear endpoint stall and reset bulk-in pipe
 *                      adjust data xfer length
 *              else other error
 *                      report back to transport
 *                      typically transport will call reset recovery
 *              else (no error)
 *                      return success
 *
 *      NOTE: We call this function only if there is xfercount.
 */
int
scsa2usb_handle_data_start(scsa2usb_state_t *scsa2usbp,
    scsa2usb_cmd_t *cmd, usb_bulk_req_t *req)
{
        int             rval = USB_SUCCESS;
        uint_t          ept_addr;
        usb_flags_t     flags = USB_FLAGS_SLEEP;
#ifdef  SCSA2USB_BULK_ONLY_TEST
        usb_req_attrs_t attrs = 0;
#else
        usb_req_attrs_t attrs = USB_ATTRS_SHORT_XFER_OK;
#endif

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_handle_data_start: BEGIN cmd = %p, req = %p",
            (void *)cmd, (void *)req);

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        switch (cmd->cmd_dir) {
        case USB_EP_DIR_IN:
#ifdef  SCSA2USB_BULK_ONLY_TEST
                /*
                 * This case occurs when the host expects to receive
                 * more data than the device actually transfers. Hi > Di
                 */
                if (scsa2usb_test_case_5) {
                        usb_bulk_req_t *req2;

                        req->bulk_len = cmd->cmd_xfercount - 1;
                        req->bulk_attributes = 0;
                        mutex_exit(&scsa2usbp->scsa2usb_mutex);
                        SCSA2USB_FREE_MSG(req->bulk_data);
                        req->bulk_data = allocb_wait(req->bulk_len, BPRI_LO,
                            STR_NOSIG, NULL);

                        ASSERT(req->bulk_timeout);
                        rval = usb_pipe_bulk_xfer(
                            scsa2usbp->scsa2usb_bulkin_pipe, req, flags);
                        mutex_enter(&scsa2usbp->scsa2usb_mutex);
                        USB_DPRINTF_L1(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle, "rval = %x", rval);

                        req2 = scsa2usb_init_bulk_req(scsa2usbp,
                            cmd->cmd_xfercount + 2,
                            cmd->cmd_timeout, 0, flags);
                        req2->bulk_len = cmd->cmd_xfercount + 2;
                        mutex_exit(&scsa2usbp->scsa2usb_mutex);

                        ASSERT(req2->bulk_timeout);
                        rval = usb_pipe_bulk_xfer(
                            scsa2usbp->scsa2usb_bulkin_pipe, req2, flags);
                        mutex_enter(&scsa2usbp->scsa2usb_mutex);

                        USB_DPRINTF_L1(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "TEST 5: Hi > Di: rval = 0x%x", rval);
                        scsa2usb_test_case_5 = 0;
                        usb_free_bulk_req(req2);

                        return (rval);
                }

                /*
                 * This happens when the host expects to send data to the
                 * device while the device intends to send data to the host.
                 */
                if (scsa2usb_test_case_8 && (cmd->cmd_cdb[0] == SCMD_READ_G1)) {
                        USB_DPRINTF_L1(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "TEST 8: Hi <> Do: Step 2");
                        scsa2usb_test_mblk(scsa2usbp, B_TRUE);
                        scsa2usb_test_case_8 = 0;

                        return (rval);
                }
#endif  /* SCSA2USB_BULK_ONLY_TEST */

                ept_addr = scsa2usbp->scsa2usb_bulkin_ept.bEndpointAddress;
                req->bulk_len = cmd->cmd_xfercount;
                req->bulk_attributes = attrs;
                SCSA2USB_FREE_MSG(req->bulk_data);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                req->bulk_data = esballoc_wait(
                    (uchar_t *)cmd->cmd_bp->b_un.b_addr +
                    cmd->cmd_offset,
                    req->bulk_len, BPRI_LO, &frnop);

                ASSERT(req->bulk_timeout);
                rval = usb_pipe_bulk_xfer(scsa2usbp->scsa2usb_bulkin_pipe,
                    req, flags);
                mutex_enter(&scsa2usbp->scsa2usb_mutex);

                break;

        case USB_EP_DIR_OUT:
#ifdef  SCSA2USB_BULK_ONLY_TEST
                /*
                 * This happens when the host expects to receive data
                 * from the device while the device intends to receive
                 * data from the host.
                 */
                if (scsa2usb_test_case_10 &&
                    (cmd->cmd_cdb[0] == SCMD_WRITE_G1)) {
                        req->bulk_len = CSW_LEN;
                        mutex_exit(&scsa2usbp->scsa2usb_mutex);

                        ASSERT(req->bulk_timeout);
                        rval = usb_pipe_bulk_xfer(
                            scsa2usbp->scsa2usb_bulkin_pipe, req, flags);
                        mutex_enter(&scsa2usbp->scsa2usb_mutex);

                        USB_DPRINTF_L1(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "TEST 10: Ho <> Di: done rval = 0x%x",  rval);
                        scsa2usb_test_case_10 = 0;

                        return (rval);
                }
#endif  /* SCSA2USB_BULK_ONLY_TEST */

                req->bulk_data = scsa2usb_bp_to_mblk(scsa2usbp);
                if (req->bulk_data == NULL) {

                        return (USB_FAILURE);
                }

#ifdef  SCSA2USB_BULK_ONLY_TEST
                if (scsa2usb_test_case_11) {
                        /*
                         * Host expects to send data to the device and
                         * device doesn't expect to receive any data
                         */
                        USB_DPRINTF_L1(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle, "TEST 11: Ho > Do");

                        scsa2usb_test_mblk(scsa2usbp, B_FALSE);
                        scsa2usb_test_case_11 = 0;
                }
#endif  /* SCSA2USB_BULK_ONLY_TEST */

                ept_addr = scsa2usbp->scsa2usb_bulkout_ept.bEndpointAddress;
                req->bulk_len = MBLKL(req->bulk_data);
                req->bulk_timeout = scsa2usb_bulk_timeout(cmd->cmd_timeout);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                ASSERT(req->bulk_timeout);
                rval = usb_pipe_bulk_xfer(scsa2usbp->scsa2usb_bulkout_pipe,
                    req, flags);
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                break;
        }

        USB_DPRINTF_L3(DPRINT_MASK_SCSA,
            scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_handle_data_start: rval=%d cr=%d", rval,
            req->bulk_completion_reason);

        if (rval != USB_SUCCESS) {
                /* Handle Errors now */
                if (req->bulk_completion_reason == USB_CR_STALL) {
                        if (cmd->cmd_dir == USB_EP_DIR_IN) {
                                (void) scsa2usb_clear_ept_stall(
                                    scsa2usbp, ept_addr,
                                    scsa2usbp-> scsa2usb_bulkin_pipe,
                                    "bulk-in");
                        } else {
                                (void) scsa2usb_clear_ept_stall(
                                    scsa2usbp, ept_addr,
                                    scsa2usbp-> scsa2usb_bulkout_pipe,
                                    "bulk-out");
                        }
                }

                /* no more data to transfer after this */
                cmd->cmd_done = 1;
        }

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_handle_data_start: END %s data rval = %d",
            (cmd->cmd_dir == USB_EP_DIR_IN) ? "bulk-in" : "bulk-out", rval);

        return (rval);
}


/*
 * scsa2usb_handle_data_done:
 *      This function handles the completion of the data xfer.
 *      It also massages the inquiry data. This function may
 *      also be called after a stall.
 */
void
scsa2usb_handle_data_done(scsa2usb_state_t *scsa2usbp,
    scsa2usb_cmd_t *cmd, usb_bulk_req_t *req)
{
        struct buf      *bp = cmd->cmd_bp;
        struct scsi_pkt *pkt = scsa2usbp->scsa2usb_cur_pkt;
        mblk_t          *data = req->bulk_data;
        int             len = data ? MBLKL(data) : 0;
        uint32_t        max_lba;

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_handle_data_done:\n\tcmd = 0x%p data = 0x%p len = 0x%x",
            (void *)cmd, (void *)data, len);

        cmd->cmd_resid_xfercount = cmd->cmd_xfercount - len;

        if (len)  {
                uchar_t *p;
                uchar_t dtype;
                scsa2usb_read_cap_t *cap;
                struct scsi_inquiry *inq;

                switch (cmd->cmd_cdb[SCSA2USB_OPCODE]) {
                case SCMD_INQUIRY:
                        /*
                         * cache a copy of the inquiry data for our own use
                         * but ensure that we have at least up to
                         * inq_revision, inq_serial is not required.
                         * ignore inquiry data returned for inquiry commands
                         * with SCSI-3 EVPD, CmdDt bits set.
                         */
                        if (((cmd->cmd_cdb[SCSA2USB_LUN] & 0x1f) == 0) &&
                            (len >= SCSA2USB_MAX_INQ_LEN)) {
                                inq = (struct scsi_inquiry *)data->b_rptr;
                                dtype = inq->inq_dtype & DTYPE_MASK;
                                /*
                                 * scsi framework sends zero byte write(10) cmd
                                 * to (Simplified) direct-access devices with
                                 * inquiry version > 2 for reservation changes.
                                 * But some USB devices don't support zero byte
                                 * write(10) even though they have inquiry
                                 * version > 2. Considering scsa2usb driver
                                 * doesn't support reservation and all the
                                 * reservation cmds are being faked, we fake
                                 * the inquiry version to 0 to make scsi
                                 * framework send test unit ready cmd which is
                                 * supported by all the usb devices.
                                 */
                                if (((dtype == DTYPE_DIRECT) ||
                                    (dtype == DTYPE_RBC)) &&
                                    (inq->inq_ansi > 2)) {
                                        inq->inq_ansi = 0;
                                }

                                bzero(&scsa2usbp->scsa2usb_lun_inquiry
                                    [pkt->pkt_address.a_lun],
                                    sizeof (struct scsi_inquiry));
                                bcopy(data->b_rptr,
                                    &scsa2usbp->scsa2usb_lun_inquiry
                                    [pkt->pkt_address.a_lun], len);
                        }

                        USB_DPRINTF_L3(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "scsi inquiry type = 0x%x",
                            scsa2usbp->scsa2usb_lun_inquiry
                            [pkt->pkt_address.a_lun].inq_dtype);

                        cmd->cmd_done = 1;
                        goto handle_data;

                case SCMD_READ_CAPACITY:
                        cap = (scsa2usb_read_cap_t *)data->b_rptr;

                        /* Figure out the logical block size */
                        if ((len >= sizeof (struct scsa2usb_read_cap)) &&
                            (req->bulk_completion_reason == USB_CR_OK)) {
                                scsa2usbp->
                                    scsa2usb_lbasize[pkt->pkt_address.a_lun] =
                                    SCSA2USB_MK_32BIT(
                                    cap->scsa2usb_read_cap_blen3,
                                    cap->scsa2usb_read_cap_blen2,
                                    cap->scsa2usb_read_cap_blen1,
                                    cap->scsa2usb_read_cap_blen0);

                                max_lba = SCSA2USB_MK_32BIT(
                                    cap->scsa2usb_read_cap_lba3,
                                    cap->scsa2usb_read_cap_lba2,
                                    cap->scsa2usb_read_cap_lba1,
                                    cap->scsa2usb_read_cap_lba0);

                                /*
                                 * Some devices return total logical block
                                 * number instead of highest logical block
                                 * address. Adjust the value by minus 1.
                                 */
                                if (max_lba > 0 && (scsa2usbp->scsa2usb_attrs &
                                    SCSA2USB_ATTRS_NO_CAP_ADJUST) == 0) {
                                        max_lba -= 1;
                                        cap->scsa2usb_read_cap_lba0 =
                                            (uchar_t)(max_lba & 0xFF);
                                        cap->scsa2usb_read_cap_lba1 =
                                            (uchar_t)(max_lba >> 8 & 0xFF);
                                        cap->scsa2usb_read_cap_lba2 =
                                            (uchar_t)(max_lba >> 16 & 0xFF);
                                        cap->scsa2usb_read_cap_lba3 =
                                            (uchar_t)(max_lba >> 24 & 0xFF);
                                }

                                USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                                    scsa2usbp->scsa2usb_log_handle,
                                    "bytes in each logical block=0x%lx,"
                                    "number of total logical blocks=0x%x",
                                    scsa2usbp->
                                    scsa2usb_lbasize[pkt->pkt_address.a_lun],
                                    max_lba + 1);
                        }
                        cmd->cmd_done = 1;
                        goto handle_data;

                case SCMD_REQUEST_SENSE:
                        p = data->b_rptr;
                        USB_DPRINTF_L2(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "cdb: %x rqsense: "
                            "%x %x %x %x %x %x %x %x %x %x\n\t"
                            "%x %x %x %x %x %x %x %x %x %x",
                            cmd->cmd_cdb[0],
                            p[0], p[1], p[2], p[3], p[4],
                            p[5], p[6], p[7], p[8], p[9],
                            p[10], p[11], p[12], p[13], p[14],
                            p[15], p[16], p[17], p[18], p[19]);

                        scsa2usbp->scsa2usb_last_cmd.status = p[2];
                        cmd->cmd_done = 1;
                        /* FALLTHROUGH */

                default:
handle_data:
                        if (bp && len && (cmd->cmd_dir == USB_EP_DIR_IN)) {
                                /*
                                 * we don't have to copy the data, the
                                 * data pointers for the mblk_t for
                                 * the bulk-in xfer points to the
                                 * struct buf * data.
                                 */
                                cmd->cmd_offset += len;
                        }

                        USB_DPRINTF_L3(DPRINT_MASK_SCSA,
                            scsa2usbp->scsa2usb_log_handle,
                            "len = 0x%x total = 0x%lx offset = 0x%lx",
                            len, cmd->cmd_total_xfercount, cmd->cmd_offset);

                        /*
                         * update total_xfercount now but it may be
                         * adjusted after receiving the residue
                         */
                        cmd->cmd_total_xfercount -= len;

                        if ((req->bulk_completion_reason != USB_CR_OK) ||
                            (cmd->cmd_resid_xfercount != 0) ||
                            (cmd->cmd_total_xfercount == 0)) {
                                /* set pkt_resid to total to be sure */
                                pkt->pkt_resid = cmd->cmd_total_xfercount;
                                cmd->cmd_done = 1;
                        }

                        break;
                }
        } else {
                if (cmd->cmd_dir == USB_EP_DIR_OUT) {
                        if (cmd->cmd_total_xfercount == 0) {
                                cmd->cmd_done = 1;
                        }
                }
        }
}


/*
 * scsa2usb_init_bulk_req:
 *      Allocate (synchronously) and fill in a bulk-request
 */
usb_bulk_req_t *
scsa2usb_init_bulk_req(scsa2usb_state_t *scsa2usbp, size_t length,
    uint_t timeout, usb_req_attrs_t attrs, usb_flags_t flags)
{
        usb_bulk_req_t  *req;

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        req = usb_alloc_bulk_req(scsa2usbp->scsa2usb_dip, length,
            flags | USB_FLAGS_SLEEP);

        req->bulk_len = (uint_t)length;                 /* xfer length */
        req->bulk_timeout = scsa2usb_bulk_timeout(timeout); /* xfer timeout */
        req->bulk_attributes = attrs;           /* xfer attrs */
        req->bulk_client_private = (usb_opaque_t)scsa2usbp; /* statep */

        return (req);
}


/*
 * scsa2usb_bulk_timeout:
 *      ensure that bulk requests do not have infinite timeout values
 */
int
scsa2usb_bulk_timeout(int timeout)
{
        return ((timeout == 0) ? scsa2usb_long_timeout : timeout);
}


/*
 * scsa2usb_clear_ept_stall:
 *      clear endpoint stall and reset pipes
 */
int
scsa2usb_clear_ept_stall(scsa2usb_state_t *scsa2usbp, uint_t ept_addr,
    usb_pipe_handle_t ph, char *what)
{
        int rval;
        dev_info_t *dip = scsa2usbp->scsa2usb_dip;

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));
        if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {

                return (USB_FAILURE);
        }

        mutex_exit(&scsa2usbp->scsa2usb_mutex);
        rval = usb_clr_feature(dip, USB_DEV_REQ_RCPT_EP, 0, ept_addr,
            USB_FLAGS_SLEEP, NULL, NULL);

        usb_pipe_reset(dip, ph, USB_FLAGS_SLEEP, NULL, NULL);

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_clear_ept_stall: on %s: ept = 0x%x rval = %d",
            what, ept_addr, rval);

        return (rval);
}


/*
 * scsa2usb_pkt_completion:
 *      Handle pkt completion.
 */
static void
scsa2usb_pkt_completion(scsa2usb_state_t *scsa2usbp, struct scsi_pkt *pkt)
{
        scsa2usb_cmd_t *cmd = PKT2CMD(pkt);
        size_t len;

        ASSERT(pkt);
        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_pkt_completion:\n\tscsa2usbp = 0x%p "
            "reason=%d, status=%d state=0x%x stats=0x%x resid=0x%lx",
            (void *)scsa2usbp, pkt->pkt_reason, *(pkt->pkt_scbp),
            pkt->pkt_state, pkt->pkt_statistics, pkt->pkt_resid);

        if (pkt->pkt_reason == CMD_CMPLT) {
                pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET |
                    STATE_SENT_CMD | STATE_GOT_STATUS;
                if (cmd->cmd_xfercount) {
                        pkt->pkt_state |= STATE_XFERRED_DATA;
                }
        } else {
                pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET |
                    STATE_SENT_CMD;
        }

        /*
         * don't zap the current state when in panic as this will
         * make debugging harder
         */
        if ((scsa2usbp->scsa2usb_cur_pkt == pkt) && !ddi_in_panic()) {
                SCSA2USB_RESET_CUR_PKT(scsa2usbp);

                len = sizeof (scsa2usbp->scsa2usb_last_cmd.cdb);
                bzero(scsa2usbp->scsa2usb_last_cmd.cdb, len);

                len = (len < cmd->cmd_cdblen) ? len : cmd->cmd_cdblen;
                USB_DPRINTF_L3(DPRINT_MASK_SCSA,
                    scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_pkt_completion: save last cmd, len=%ld", len);

                /* save the last command */
                bcopy(pkt->pkt_cdbp, scsa2usbp->scsa2usb_last_cmd.cdb, len);

                /* reset the scsa2usb_last_cmd.status value */
                if ((pkt->pkt_cdbp[0] != SCMD_REQUEST_SENSE) &&
                    (pkt->pkt_cdbp[0] != SCMD_INQUIRY)) {
                        scsa2usbp->scsa2usb_last_cmd.status = 0;
                }

                /*
                 * set pkt state to NONE *before* calling back as the target
                 * driver will immediately submit the next packet
                 */
                scsa2usbp->scsa2usb_pkt_state = SCSA2USB_PKT_NONE;
        }

        if (pkt->pkt_comp) {
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
                scsi_hba_pkt_comp(pkt);
                mutex_enter(&scsa2usbp->scsa2usb_mutex);

        }
}


/*
 * Even handling functions:
 *
 * scsa2usb_reconnect_event_cb:
 *      event handling
 */
static int
scsa2usb_reconnect_event_cb(dev_info_t *dip)
{
        scsa2usb_state_t *scsa2usbp =
            ddi_get_soft_state(scsa2usb_statep, ddi_get_instance(dip));
        dev_info_t      *cdip;
        int             rval = USB_SUCCESS;

        ASSERT(scsa2usbp != NULL);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_reconnect_event_cb: dip = 0x%p", (void *)dip);

        scsa2usb_restore_device_state(dip, scsa2usbp);

        USB_DPRINTF_L0(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "Reinserted device is accessible again.");

        ndi_devi_enter(dip);
        for (cdip = ddi_get_child(dip); cdip; ) {
                dev_info_t *next = ddi_get_next_sibling(cdip);

                mutex_enter(&DEVI(cdip)->devi_lock);
                DEVI_SET_DEVICE_REINSERTED(cdip);
                mutex_exit(&DEVI(cdip)->devi_lock);

                cdip = next;
        }
        ndi_devi_exit(dip);

        /* stop suppressing warnings */
        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        scsa2usbp->scsa2usb_warning_given = B_FALSE;
        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        if (scsa2usbp->scsa2usb_ugen_hdl) {
                rval = usb_ugen_reconnect_ev_cb(
                    scsa2usbp->scsa2usb_ugen_hdl);
        }

        return (rval);
}


/*
 * scsa2usb_all_waitQs_empty:
 *      check if all waitQs empty
 */
static int
scsa2usb_all_waitQs_empty(scsa2usb_state_t *scsa2usbp)
{
        uint_t  lun;

        for (lun = 0; lun < SCSA2USB_MAX_LUNS; lun++) {
                if (usba_list_entry_count(
                    &scsa2usbp->scsa2usb_waitQ[lun])) {

                        return (USB_FAILURE);
                }
        }

        return (USB_SUCCESS);
}


/*
 * scsa2usb_disconnect_event_cb:
 *      callback for disconnect events
 */
static int
scsa2usb_disconnect_event_cb(dev_info_t *dip)
{
        scsa2usb_state_t *scsa2usbp =
            ddi_get_soft_state(scsa2usb_statep, ddi_get_instance(dip));
        dev_info_t      *cdip;
        int             i;
        int             rval = USB_SUCCESS;

        ASSERT(scsa2usbp != NULL);

        USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_disconnect_event_cb: dip = 0x%p", (void *)dip);

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        scsa2usbp->scsa2usb_dev_state = USB_DEV_DISCONNECTED;

        /*
         * wait till the work thread is done, carry on regardless
         * if not.
         */
        for (i = 0; i < SCSA2USB_DRAIN_TIMEOUT; i++) {
                if ((scsa2usbp->scsa2usb_work_thread_id == NULL) &&
                    (scsa2usbp->scsa2usb_cur_pkt == NULL) &&
                    (scsa2usb_all_waitQs_empty(scsa2usbp) ==
                    USB_SUCCESS)) {

                        break;
                }
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
                delay(drv_usectohz(1000000));
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
        }
        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        ndi_devi_enter(dip);
        for (cdip = ddi_get_child(dip); cdip; ) {
                dev_info_t *next = ddi_get_next_sibling(cdip);

                mutex_enter(&DEVI(cdip)->devi_lock);
                DEVI_SET_DEVICE_REMOVED(cdip);
                mutex_exit(&DEVI(cdip)->devi_lock);

                cdip = next;
        }
        ndi_devi_exit(dip);

        if (scsa2usbp->scsa2usb_ugen_hdl) {
                rval = usb_ugen_disconnect_ev_cb(
                    scsa2usbp->scsa2usb_ugen_hdl);
        }

        return (rval);
}


/*
 * PM support
 *
 * scsa2usb_create_pm_components:
 *      create the pm components required for power management
 *      no mutex is need when calling USBA interfaces
 */
static void
scsa2usb_create_pm_components(dev_info_t *dip, scsa2usb_state_t *scsa2usbp)
{
        scsa2usb_power_t *pm;
        uint_t          pwr_states;

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        USB_DPRINTF_L4(DPRINT_MASK_PM, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_create_pm_components: dip = 0x%p, scsa2usbp = 0x%p",
            (void *)dip, (void *)scsa2usbp);

        /*
         * Check if power management is disabled by a per-device quirk:
         */
        if ((scsa2usbp->scsa2usb_attrs & SCSA2USB_ATTRS_PM) == 0) {
                USB_DPRINTF_L2(DPRINT_MASK_PM, scsa2usbp->scsa2usb_log_handle,
                    "device cannot be power managed");
                return;
        }

        /* Allocate the PM state structure */
        pm = kmem_zalloc(sizeof (scsa2usb_power_t), KM_SLEEP);

        scsa2usbp->scsa2usb_pm = pm;
        pm->scsa2usb_current_power = USB_DEV_OS_FULL_PWR;
        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        if (usb_create_pm_components(dip, &pwr_states) ==
            USB_SUCCESS) {
                if (usb_handle_remote_wakeup(dip,
                    USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS) {
                        pm->scsa2usb_wakeup_enabled = 1;
                }

                mutex_enter(&scsa2usbp->scsa2usb_mutex);
                pm->scsa2usb_pwr_states = (uint8_t)pwr_states;
                scsa2usb_raise_power(scsa2usbp);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);
        }

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
}


/*
 * scsa2usb_raise_power:
 *      check if the device is using full power or not
 */
static void
scsa2usb_raise_power(scsa2usb_state_t *scsa2usbp)
{
        USB_DPRINTF_L4(DPRINT_MASK_PM, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_raise_power:");

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        if (scsa2usbp->scsa2usb_pm) {
                scsa2usb_pm_busy_component(scsa2usbp);
                if (scsa2usbp->scsa2usb_pm->scsa2usb_current_power !=
                    USB_DEV_OS_FULL_PWR) {
                        mutex_exit(&scsa2usbp->scsa2usb_mutex);
                        (void) pm_raise_power(scsa2usbp->scsa2usb_dip,
                            0, USB_DEV_OS_FULL_PWR);
                        mutex_enter(&scsa2usbp->scsa2usb_mutex);
                }
        }
}


/*
 * functions to handle power transition for OS levels 0 -> 3
 */
static int
scsa2usb_pwrlvl0(scsa2usb_state_t *scsa2usbp)
{
        int     rval;

        switch (scsa2usbp->scsa2usb_dev_state) {
        case USB_DEV_ONLINE:
                /* Deny the powerdown request if the device is busy */
                if (scsa2usbp->scsa2usb_pm->scsa2usb_pm_busy != 0) {

                        return (USB_FAILURE);
                }

                /*
                 * stop polling on interrupt pipe
                 */
                scsa2usb_cbi_stop_intr_polling(scsa2usbp);

                /* Issue USB D3 command to the device here */
                rval = usb_set_device_pwrlvl3(scsa2usbp->scsa2usb_dip);
                ASSERT(rval == USB_SUCCESS);

                scsa2usbp->scsa2usb_dev_state = USB_DEV_PWRED_DOWN;

                /* FALLTHRU */
        case USB_DEV_DISCONNECTED:
        case USB_DEV_SUSPENDED:
        case USB_DEV_PWRED_DOWN:
        default:
                scsa2usbp->scsa2usb_pm->scsa2usb_current_power =
                    USB_DEV_OS_PWR_OFF;

                return (USB_SUCCESS);
        }
}


static int
scsa2usb_pwrlvl1(scsa2usb_state_t *scsa2usbp)
{
        int     rval;

        /* Issue USB D2 command to the device here */
        rval = usb_set_device_pwrlvl2(scsa2usbp->scsa2usb_dip);
        ASSERT(rval == USB_SUCCESS);

        return (DDI_FAILURE);
}


static int
scsa2usb_pwrlvl2(scsa2usb_state_t *scsa2usbp)
{
        int     rval;

        /* Issue USB D1 command to the device here */
        rval = usb_set_device_pwrlvl1(scsa2usbp->scsa2usb_dip);
        ASSERT(rval == USB_SUCCESS);

        return (DDI_FAILURE);
}


static int
scsa2usb_pwrlvl3(scsa2usb_state_t *scsa2usbp)
{
        int     rval;

        /*
         * PM framework tries to put us in full power
         * during system shutdown. If we are disconnected
         * return success anyways
         */
        if (scsa2usbp->scsa2usb_dev_state != USB_DEV_DISCONNECTED) {
                /* Issue USB D0 command to the device here */
                rval = usb_set_device_pwrlvl0(scsa2usbp->scsa2usb_dip);
                ASSERT(rval == USB_SUCCESS);

                scsa2usbp->scsa2usb_dev_state = USB_DEV_ONLINE;
        }
        scsa2usbp->scsa2usb_pm->scsa2usb_current_power = USB_DEV_OS_FULL_PWR;

        return (DDI_SUCCESS);
}


/*
 * scsa2usb_power:
 *      power entry point
 */
/* ARGSUSED */
static int
scsa2usb_power(dev_info_t *dip, int comp, int level)
{
        scsa2usb_state_t        *scsa2usbp;
        scsa2usb_power_t        *pm;
        int                     rval = DDI_FAILURE;

        scsa2usbp = ddi_get_soft_state(scsa2usb_statep, ddi_get_instance(dip));

        USB_DPRINTF_L3(DPRINT_MASK_PM, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_power: Begin scsa2usbp (%p): level = %d",
            (void *)scsa2usbp, level);

        mutex_enter(&scsa2usbp->scsa2usb_mutex);
        if (SCSA2USB_BUSY(scsa2usbp)) {
                USB_DPRINTF_L2(DPRINT_MASK_PM, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_power: busy");
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                return (rval);
        }

        pm = scsa2usbp->scsa2usb_pm;
        if (pm == NULL) {
                USB_DPRINTF_L2(DPRINT_MASK_PM, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_power: pm NULL");
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                return (rval);
        }

        /* check if we are transitioning to a legal power level */
        if (USB_DEV_PWRSTATE_OK(pm->scsa2usb_pwr_states, level)) {
                USB_DPRINTF_L2(DPRINT_MASK_PM, scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_power: illegal power level = %d "
                    "pwr_states: %x", level, pm->scsa2usb_pwr_states);
                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                return (rval);
        }

        switch (level) {
        case USB_DEV_OS_PWR_OFF :
                rval = scsa2usb_pwrlvl0(scsa2usbp);
                break;
        case USB_DEV_OS_PWR_1 :
                rval = scsa2usb_pwrlvl1(scsa2usbp);
                break;
        case USB_DEV_OS_PWR_2 :
                rval = scsa2usb_pwrlvl2(scsa2usbp);
                break;
        case USB_DEV_OS_FULL_PWR :
                rval = scsa2usb_pwrlvl3(scsa2usbp);
                break;
        }

        mutex_exit(&scsa2usbp->scsa2usb_mutex);

        return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE);
}


static void
scsa2usb_pm_busy_component(scsa2usb_state_t *scsa2usbp)
{
        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        if (scsa2usbp->scsa2usb_pm) {
                scsa2usbp->scsa2usb_pm->scsa2usb_pm_busy++;

                USB_DPRINTF_L4(DPRINT_MASK_PM,
                    scsa2usbp->scsa2usb_log_handle,
                    "scsa2usb_pm_busy_component: %d",
                    scsa2usbp->scsa2usb_pm->scsa2usb_pm_busy);

                mutex_exit(&scsa2usbp->scsa2usb_mutex);

                if (pm_busy_component(scsa2usbp->scsa2usb_dip, 0) !=
                    DDI_SUCCESS) {
                        mutex_enter(&scsa2usbp->scsa2usb_mutex);
                        ASSERT(scsa2usbp->scsa2usb_pm->scsa2usb_pm_busy > 0);
                        scsa2usbp->scsa2usb_pm->scsa2usb_pm_busy--;

                        USB_DPRINTF_L2(DPRINT_MASK_PM,
                            scsa2usbp->scsa2usb_log_handle,
                            "scsa2usb_pm_busy_component failed: %d",
                            scsa2usbp->scsa2usb_pm->scsa2usb_pm_busy);

                        return;
                }
                mutex_enter(&scsa2usbp->scsa2usb_mutex);
        }
}


/*
 * scsa2usb_pm_idle_component:
 *      idles the device
 */
static void
scsa2usb_pm_idle_component(scsa2usb_state_t *scsa2usbp)
{
        ASSERT(!mutex_owned(&scsa2usbp->scsa2usb_mutex));

        if (scsa2usbp->scsa2usb_pm) {
                if (pm_idle_component(scsa2usbp->scsa2usb_dip, 0) ==
                    DDI_SUCCESS) {
                        mutex_enter(&scsa2usbp->scsa2usb_mutex);
                        ASSERT(scsa2usbp->scsa2usb_pm->scsa2usb_pm_busy > 0);
                        scsa2usbp->scsa2usb_pm->scsa2usb_pm_busy--;

                        USB_DPRINTF_L4(DPRINT_MASK_PM,
                            scsa2usbp->scsa2usb_log_handle,
                            "scsa2usb_pm_idle_component: %d",
                            scsa2usbp->scsa2usb_pm->scsa2usb_pm_busy);

                        mutex_exit(&scsa2usbp->scsa2usb_mutex);
                }
        }
}


#ifdef  DEBUG
/*
 * scsa2usb_print_cdb:
 *      prints CDB
 */
void
scsa2usb_print_cdb(scsa2usb_state_t *scsa2usbp, scsa2usb_cmd_t *cmd)
{
        uchar_t *c = (uchar_t *)&cmd->cmd_cdb;

        USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "cmd = 0x%p opcode=%s "
            "cdb: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
            (void *)cmd,
            scsi_cname(cmd->cmd_cdb[SCSA2USB_OPCODE], scsa2usb_cmds),
            c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8],
            c[9], c[10], c[11], c[12], c[13], c[14], c[15]);
}
#endif  /* DEBUG */


#ifdef  SCSA2USB_BULK_ONLY_TEST
/*
 * scsa2usb_test_mblk:
 *      This function sends a dummy data mblk_t to simulate
 *      the following test cases: 5 and 11.
 */
static void
scsa2usb_test_mblk(scsa2usb_state_t *scsa2usbp, boolean_t large)
{
        int                     i, rval;
        size_t                  len;
        usb_flags_t             flags = USB_FLAGS_SLEEP;
        usb_bulk_req_t          *req;

        ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));

        /* should we create a larger mblk? */
        len = (large == B_TRUE) ? DEV_BSIZE : USB_BULK_CBWCMD_LEN;

        req = scsa2usb_init_bulk_req(scsa2usbp, len,
            SCSA2USB_BULK_PIPE_TIMEOUT, 0, flags);

        /* fill up the data mblk */
        for (i = 0; i < len; i++) {
                *req->bulk_data->b_wptr++ = (uchar_t)i;
        }

        mutex_exit(&scsa2usbp->scsa2usb_mutex);
        ASSERT(req->bulk_timeout);
        rval = usb_pipe_bulk_xfer(scsa2usbp->scsa2usb_bulkout_pipe, req, flags);
        mutex_enter(&scsa2usbp->scsa2usb_mutex);

        USB_DPRINTF_L1(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
            "scsa2usb_test_mblk: Sent Data Out rval = 0x%x", rval);

        usb_free_bulk_req(req);
}
#endif  /* SCSA2USB_BULK_ONLY_TEST */