scsa2usb.c revision 5547f1d8c68fa119522b859bbf38315dc773e696
/*
* 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
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* 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 (
*
* For details on USB Mass Storage Class overview:
*/
#define DEBUG 1
#endif
#include <sys/kobj_lex.h>
#include <sys/sysmacros.h>
/*
* Function Prototypes
*/
void **);
static void scsa2usb_validate_attrs(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 *,
/* 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 *);
static char *scsa2usb_strtok_r(char *, char *, char **);
/* 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));
scsi_hba_tran_t *, struct scsi_device *);
scsi_hba_tran_t *, struct scsi_device *);
int, int, int (*)(), caddr_t);
static void scsa2usb_scsi_destroy_pkt(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);
ddi_bus_config_op_t, void *, dev_info_t **);
ddi_bus_config_op_t, void *);
/* functions for command and transport support */
static int scsa2usb_check_bulkonly_blacklist_attrs(scsa2usb_state_t *,
scsa2usb_cmd_t *, uchar_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 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 *);
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 *, int);
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 int scsa2usb_read_cd_blk_size(uchar_t);
scsa2usb_cmd_t *, usb_bulk_req_t *);
int scsa2usb_bulk_timeout(int);
usb_pipe_handle_t, char *);
/* 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 *);
/* PM handling */
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 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 *);
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",
"\121readdiscinfo",
"\122readtrkinfo",
"\123reservedtrk",
"\124sendopcinfo",
"\125modeselect",
"\132modesense",
"\133closetrksession",
"\135sendcuesheet",
"\136prin",
"\137prout",
"\241blankcd",
"\245playaudio12",
"\250read12",
"\251playtrk12",
"\252write12",
"\254getperf",
"\271readcdmsf",
"\273setcdspeed",
"\275mechanism_sts",
"\276readcd",
};
/*
* Mass-Storage devices masquerade as "sd" disks.
*
* These devices may not support all SCSI CDBs in their
* entirety due to their hardware implementation limitations.
*
* As such, following is a list of some of the black-listed
* devices w/ the attributes that they do not support.
* (See scsa2usb.h for description on each attribute)
*/
#define X ((uint16_t)(-1))
static struct blacklist {
} scsa2usb_blacklist[] = {
/* Iomega Zip100 drive (prototype) with flaky bridge */
/* Iomega Zip100 drive (newer model) with flaky bridge */
/* Iomega Zip100 drive (newer model) with flaky bridge */
/* Iomega Zip250 drive */
/* Iomega Clik! drive */
/* Kingston DataTraveler Stick / PNY Attache Stick */
/* PNY Floppy drive */
{MS_PNY_VID, MS_PNY_PID0, 0,
/* SMSC floppy Device - and its clones */
{MS_SMSC_VID, X, 0, SCSA2USB_ATTRS_START_STOP},
/* Hagiwara SmartMedia Device */
/* Hagiwara CompactFlash Device */
/* Hagiwara SmartMedia/CompactFlash Combo Device */
/* Hagiwara new SM Device */
/* Hagiwara new CF Device */
/* Mitsumi CD-RW Device(s) */
{MS_MITSUMI_VID, X, X, SCSA2USB_ATTRS_BIG_TIMEOUT |
/* dumb flash devices */
/* SimpleTech UCF-100 CF Device */
/* Acomdata 80GB USB/1394 Hard Disk */
/* OTi6828 Flash Disk */
{MS_OTI_VID, MS_OTI_DEVICE_6828, 0,
/* AMI Virtual Floppy */
/* ScanLogic USB Storage Device */
/* Super Top USB 2.0 IDE Device */
/* Aigo Miniking Device NEHFSP14 */
/* Alcor Micro Corp 6387 flash disk */
{MS_ALCOR_VID, MS_ALCOR_PID0, 0,
};
#define N_SCSA2USB_BLACKLIST (sizeof (scsa2usb_blacklist))/ \
sizeof (struct blacklist)
/*
* 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 */
/* for debug messages */
/*
* Some devices have problems with big bulk transfers,
* transfers >= 128kbytes hang the device. This tunable allows to
* limit the maximum bulk transfers rate.
*/
#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;
#endif /* SCSA2USB_BULK_ONLY_TEST */
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 */
};
&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 = {
};
/* event support */
static usb_event_t scsa2usb_events = {
};
int
_init(void)
{
int rval;
sizeof (scsa2usb_state_t), SCSA2USB_INITIAL_ALLOC)) != 0)) {
return (rval);
}
return (rval);
}
return (rval);
}
return (rval);
}
int
_fini(void)
{
int rval;
}
return (rval);
}
int
{
}
/*
* scsa2usb_info :
* Get minor number, soft state structure etc.
*/
/*ARGSUSED*/
static int
{
int error = DDI_FAILURE;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
error = DDI_SUCCESS;
} else {
}
break;
case DDI_INFO_DEVT2INSTANCE:
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
{
int interface;
"scsa2usb_attach: dip = 0x%p", (void *)dip);
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
"scsa2usb_attach: failed");
return (DDI_FAILURE);
}
/* Allocate softc information */
return (DDI_FAILURE);
}
/* get soft state space and initialize */
"scsa2usb%d: bad soft state", instance);
return (DDI_FAILURE);
}
0);
/* attach to USBA */
"usb_client_attach failed");
goto fail;
}
USB_SUCCESS) {
"usb_get_dev_data failed");
goto fail;
}
/* initialize the mutex with the right cookie */
}
/* save the default pipe handle */
/* basic inits are done */
"curr_cfg=%ld, curr_if=%d",
/* now find out relevant descriptors for alternate 0 */
if (altif_data->altif_n_ep == 0) {
"invalid alt 0 for interface %d", interface);
goto fail;
}
"invalid interface class (0x%x)",
}
/* figure out the endpoints and copy the descr */
}
}
}
/*
* 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
*/
"protocol=0x%x override=0x%x subclass=0x%x override=0x%x",
case USB_PROTO_MS_CBI:
break;
case USB_PROTO_MS_CBI_WC:
break;
case USB_PROTO_MS_BULK_ONLY:
break;
default:
if (scsa2usbp->scsa2usb_protocol_override) {
"overriding protocol %x",
break;
}
"unsupported protocol = %x",
goto fail;
}
case USB_SUBCLS_MS_SCSI: /* transparent SCSI */
break;
case USB_SUBCLS_MS_SFF8020I:
case USB_SUBCLS_MS_SFF8070I:
break;
case USB_SUBCLS_MS_UFI: /* UFI */
break;
default:
if (scsa2usbp->scsa2usb_subclass_override) {
"overriding subclass %x",
break;
}
"unsupported subclass = %x",
goto fail;
}
/* check that we have the right set of endpoint descriptors */
}
} else if (SCSA2USB_IS_CBI(scsa2usbp)) {
}
}
"scsa2usb%d doesn't support minimum required endpoints",
instance);
goto fail;
}
/*
* Validate the black-listed attributes
*/
/* Print the serial number from the registration data */
}
/*
* Allocate a SCSA transport structure
*/
/*
* initialize transport structure
*/
/*
* register with SCSA as an HBA
* Note that the dma attributes are from parent nexus
*/
"scsi_hba_attach_setup failed");
goto fail;
}
/* create minor node */
DDI_NT_SCSI_NEXUS, 0) != DDI_SUCCESS) {
"scsi_attach: ddi_create_minor_node failed");
goto fail;
}
/* open pipes and set scsa2usb_flags */
"error opening pipes");
goto fail;
}
/* set default block size. updated after read cap cmd */
}
/* initialize PANIC callback */
/* finally we are all done 'initializing' the device */
/* enable PM, mutex needs to be held across this */
/* register for connect/disconnect events */
0) != USB_SUCCESS) {
"error cb registering");
goto fail;
}
/* free the dev_data tree, we no longer need it */
/* log the conf file override string if there is one */
if (scsa2usbp->scsa2usb_override_str) {
"scsa2usb.conf override: %s",
}
if (usb_owns_device(dip)) {
/* get a ugen handle */
USB_SUCCESS) {
"usb_ugen_attach failed");
}
}
/* report device */
return (DDI_SUCCESS);
fail:
if (scsa2usbp) {
}
return (DDI_FAILURE);
}
/*
* scsa2usb_detach:
* detach or suspend driver instance
*/
static int
{
int rval;
switch (cmd) {
case DDI_DETACH:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
case DDI_SUSPEND:
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
{
int rval;
/* deferred detach */
return (ENXIO);
}
"scsa2usb_ugen_open: dev_t=0x%lx", *devp);
/* if this is the first ugen open, check on transport busy */
if (scsa2usbp->scsa2usb_ugen_open_count == 0) {
while (scsa2usbp->scsa2usb_transport_busy ||
USB_SUCCESS)) {
rval = cv_wait_sig(
if (rval == 0) {
return (EINTR);
}
}
}
if (rval) {
/* reopen the pipes */
if (--scsa2usbp->scsa2usb_ugen_open_count == 0) {
}
}
return (rval);
}
/*
* scsa2usb_ugen_close()
*/
static int
{
int rval;
return (ENXIO);
}
"scsa2usb_ugen_close: dev_t=0x%lx", dev);
if (rval == 0) {
/* reopen the pipes */
if (--scsa2usbp->scsa2usb_ugen_open_count == 0) {
}
}
return (rval);
}
/*
*/
/*ARGSUSED*/
static int
{
return (ENXIO);
}
"scsa2usb_ugen_read: dev_t=0x%lx", dev);
}
/*ARGSUSED*/
static int
{
return (ENXIO);
}
"scsa2usb_ugen_write: dev_t=0x%lx", dev);
}
/*
* scsa2usb_ugen_poll
*/
static int
{
return (ENXIO);
}
"scsa2usb_ugen_poll: dev_t=0x%lx", dev);
}
/*
* scsa2usb_cleanup:
* cleanup whatever attach has setup
*/
static int
{
int rval, i;
"scsa2usb_cleanup:");
/* wait till the work thread is done */
for (i = 0; i < SCSA2USB_DRAIN_TIMEOUT; i++) {
break;
}
}
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.
*/
/*
* if a waitQ exists, get rid of it before destroying it
*/
}
if (scsa2usbp->scsa2usb_flags &
(void) scsi_hba_detach(dip);
}
if (scsa2usbp->scsa2usb_flags &
}
/* Lower the power */
if (pm->scsa2usb_wakeup_enabled) {
(void) pm_raise_power(dip, 0,
USB_SUCCESS) {
"disable remote wakeup failed "
"(%d)", rval);
}
} else {
}
}
if (pm) {
}
if (scsa2usbp->scsa2usb_override_str) {
}
/* remove the minor nodes */
/* Cancel the registered panic callback */
}
if (scsa2usbp->scsa2usb_ugen_hdl) {
}
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
{
char **override_str = NULL;
char *override_str_cpy;
uint_t i;
DDI_PROP_DONTPASS, "attribute-override-list",
return;
}
/* parse each string in the subclass-protocol-override property */
for (i = 0; i < override_str_len; i++) {
"override_str[%d] = %s", i, override_str[i]);
/*
* save a copy of the override string for possible
* inclusion in soft state later
*/
scsa2usbp) == USB_FAILURE) {
continue;
}
/*
* or if device should not be power managed
* if there'a a match, save the override string in soft state
*/
"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",
}
if (ov.fake_removable) {
}
if (ov.no_modesense) {
}
if (ov.reduced_cmd_support) {
}
break;
} else {
}
}
}
/*
* 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
{
char *input_field, *input_value;
char *lasts;
uint_t i;
/* parse all the input pairs in the string */
input_field != NULL;
NULL) {
return (USB_FAILURE);
}
/* if input value is a 'don't care', skip to the next pair */
continue;
}
return (USB_FAILURE);
}
scsa2usb_override_error("product id",
return (USB_FAILURE);
}
scsa2usb_override_error("revision id",
return (USB_FAILURE);
}
for (i = 0; i < N_SCSA2USB_SUBC_OVERRIDE; i++) {
if (strcasecmp(input_value,
scsa2usb_subclass[i].name) == 0) {
break;
}
}
return (USB_FAILURE);
}
for (i = 0; i < N_SCSA2USB_PROT_OVERRIDE; i++) {
if (strcasecmp(input_value,
scsa2usb_protocol[i].name) == 0) {
break;
}
}
return (USB_FAILURE);
}
break;
} else {
return (USB_FAILURE);
}
break;
} else {
return (USB_FAILURE);
}
break;
} else {
scsa2usb_override_error("modesense",
return (USB_FAILURE);
}
} else if (strcasecmp(input_field,
"reduced-cmd-support") == 0) {
break;
} else {
"reduced-cmd-support", scsa2usbp);
return (USB_FAILURE);
}
} else {
return (USB_FAILURE);
}
}
return (USB_SUCCESS);
}
/*
* scsa2usb_override_error:
* print an error message if conf file string is bad format
*/
static void
{
"invalid %s in scsa2usb.conf file entry", input_field);
}
/*
* scsa2usb_strtok_r:
* parse a list of tokens
*/
static char *
{
char *e;
if (p == 0 || *p == 0) {
return (NULL);
}
e = p+strlen(p);
do {
*p = 0;
*lasts = p+1;
return (tok);
}
tok = p;
}
} while (++p < e);
return (tok);
}
/*
* scsa2usb_validate_attrs:
* individual erroneous attributes
*
* NOTE: we look at only device at a time (at attach time)
*/
static void
{
int i, mask;
if (!SCSA2USB_IS_BULK_ONLY(scsa2usbp)) {
}
/* determine if this device is on the blacklist */
for (i = 0; i < N_SCSA2USB_BLACKLIST; i++) {
(scsa2usb_blacklist[i].idProduct == X))) {
~(scsa2usb_blacklist[i].attributes);
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 (mask) {
}
}
"scsa2usb attributes modified: 0x%x",
}
}
/*
* scsa2usb_create_luns:
* check the number of luns but continue if the check fails,
* create child nodes for each lun
*/
static void
{
char *node_name;
char *driver_name = NULL;
"scsa2usb_create_luns:");
/* Set n_luns to 1 by default (for floppies and other devices) */
/*
* 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.
*/
"get_max_lun cmd not supported");
} else {
if (SCSA2USB_IS_BULK_ONLY(scsa2usbp)) {
}
}
/*
* create disk child for each lun
*/
/* do an inquiry to get the dtype of this lun */
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;
}
/* attach target & lun properties */
if (rval != DDI_PROP_SUCCESS) {
"ndi_prop_update_int target failed %d", rval);
(void) ndi_devi_free(cdip);
continue;
}
"hotpluggable");
if (rval != DDI_PROP_SUCCESS) {
"ndi_prop_create_boolean hotpluggable failed %d",
rval);
(void) ndi_devi_free(cdip);
continue;
}
/*
* Some devices don't support LOG SENSE, so tells
* sd driver not to send this command.
*/
"pm-capable", 1);
if (rval != DDI_PROP_SUCCESS) {
"ndi_prop_update_int pm-capable failed %d", rval);
(void) ndi_devi_free(cdip);
continue;
}
if (rval != DDI_PROP_SUCCESS) {
"ndi_prop_update_int lun failed %d", rval);
(void) ndi_devi_free(cdip);
continue;
}
if (driver_name) {
if (rval != DDI_PROP_SUCCESS) {
"ndi_prop_update_string_array failed %d",
rval);
(void) ndi_devi_free(cdip);
continue;
}
}
/*
* add property "usb" so we always verify that it is our child
*/
if (rval != DDI_PROP_SUCCESS) {
"ndi_prop_create_boolean failed %d", rval);
(void) ndi_devi_free(cdip);
continue;
}
}
}
/*
* scsa2usb_is_usb:
* scsa2usb gets called for all possible sd children.
* we can only accept usb children
*/
static int
{
if (dip) {
DDI_PROP_DONTPASS, "usb"));
}
return (0);
}
/*
* Panic Stuff
* scsa2usb_panic_callb_init:
* initialize PANIC callb and free allocated resources
*/
static void
{
/*
* 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.
*/
(void *)scsa2usbp->scsa2usb_panic_info,
CB_CL_PANIC, "scsa2usb");
}
/*
* scsa2usb_panic_callb_fini:
* cancel out PANIC callb and free allocated resources
*/
static void
{
if (scsa2usbp->scsa2usb_panic_info) {
sizeof (scsa2usb_cpr_t));
}
}
/*
* scsa2usb_panic_callb:
* This routine is called when there is a system panic.
*/
/* ARGSUSED */
static boolean_t
{
"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.
*/
}
/* get rid of waitQ */
}
#ifndef lint
#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
{
int prev_state;
int rval = USB_FAILURE;
"scsa2usb_cpr_suspend:");
switch (scsa2usbp->scsa2usb_dev_state) {
case USB_DEV_ONLINE:
case USB_DEV_PWRED_DOWN:
case USB_DEV_DISCONNECTED:
/*
* If the device is busy, we cannot suspend
*/
if (SCSA2USB_BUSY(scsa2usbp)) {
"scsa2usb_cpr_suspend: I/O active");
/* fall back to previous state */
} else {
rval = USB_SUCCESS;
}
break;
case USB_DEV_SUSPENDED:
default:
"scsa2usb_cpr_suspend: Illegal dev state: %d",
break;
}
}
return (rval);
}
/*
* scsa2usb_cpr_resume:
* restore device's state
*/
static void
{
"scsa2usb_cpr_resume: dip = 0x%p", (void *)dip);
if (scsa2usbp->scsa2usb_ugen_hdl) {
}
}
/*
* scsa2usb_restore_device_state:
* - raise the device's power
* - reopen all the pipes
*/
static void
{
"scsa2usb_restore_device_state:");
(prev_state == USB_DEV_SUSPENDED));
/* Check for the same device */
/* change the flags to active */
return;
}
/*
* if the device had remote wakeup earlier,
* enable it again
*/
if (scsa2usbp->scsa2usb_pm &&
}
}
/*
* 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
{
int rval;
"scsa2usb_scsi_tgt_probe:");
/* if device is disconnected (ie. pipes closed), fail immediately */
if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {
return (SCSIPROBE_FAILURE);
}
"scsa2usb_scsi_tgt_probe: scsi_device = 0x%p", (void *)sd);
/*
* respect the removable bit on all USB storage devices
* unless overridden by a scsa2usb.conf entry
*/
}
}
return (rval);
}
/*
* scsa2usb_scsi_tgt_init:
* check whether we created this child ourselves
*/
/* ARGSUSED */
static int
{
int lun;
&t_len) != DDI_PROP_SUCCESS) {
return (DDI_FAILURE);
}
/* is this a child we created? */
if (scsa2usb_is_usb(cdip) == 0) {
"scsa2usb_scsi_tgt_init: new child %s%d",
/*
* add property "usb" so we can always verify that it
* is our child
*/
"ndi_prop_create_boolean failed");
return (DDI_FAILURE);
}
/*
* we don't store this dip in scsa2usb_lun_dip, there
* might be multiple dips for the same device
*/
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* scsa2usb_scsi_tgt_free:
*/
/* ARGSUSED */
static void
{
int lun;
/* is this our child? */
if (scsa2usb_is_usb(cdip) == 0) {
return;
}
&t_len) != DDI_PROP_SUCCESS) {
return;
}
}
}
}
/*
* bus enumeration entry points
*/
static int
{
int circ;
int rval;
"scsa2usb_scsi_bus_config: op=%d", op);
flag |= NDI_DEVI_DEBUG;
}
/* create children if necessary */
}
return (rval);
}
static int
void *arg)
{
int circular_count;
int rval = NDI_SUCCESS;
"scsa2usb_scsi_bus_unconfig: op=%d", op);
flag |= NDI_DEVI_DEBUG;
}
/*
* first offline and if offlining successful, then
* remove children
*/
if (op == BUS_UNCONFIG_ALL) {
}
/*
* If unconfig is successful and not part of modunload
* daemon, attempt to remove children.
*/
(flag & NDI_AUTODETACH) == 0) {
flag |= NDI_DEVI_REMOVE;
}
(save_flag & NDI_DEVI_REMOVE)) {
"Disconnected device was busy, "
"please reconnect.");
}
}
"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 *
{
/* Print sync message */
if (ddi_in_panic()) {
/* continue so caller will not hang or complain */
}
/* allocate a pkt, if none already allocated */
if (statuslen < sizeof (struct scsi_arq_status)) {
statuslen = sizeof (struct scsi_arq_status);
}
return (NULL);
}
/*
* 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.
*/
}
} else {
"scsa2usb: pkt != NULL");
/* nothing to do */
}
}
return (NULL);
}
"scsa2usb_scsi_init_pkt: mapped in 0x%p, addr=0x%p",
}
"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",
return (pkt);
}
/*
* scsa2usb_scsi_destroy_pkt:
* We are done with the packet. Get rid of it.
*/
static void
{
"scsa2usb_scsi_destroy_pkt: pkt=0x%p", (void *)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:\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",
"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;
return (TRAN_ACCEPT);
}
/* we cannot do polling, this should not happen */
return (TRAN_BADPKT);
}
/* is there a ugen open? */
if (scsa2usbp->scsa2usb_ugen_open_count) {
"ugen access in progress (count=%d)",
return (TRAN_BUSY);
}
/* prepare packet */
/* just queue up the requests in the waitQ if below max */
"scsa2usb_scsi_start: limit (%d) exceeded",
return (TRAN_BUSY);
}
"scsa2usb_work_thread_id=0x%p, count=%d, lun=%d",
(void *)scsa2usbp->scsa2usb_work_thread_id,
/* fire up a thread to start executing the protocol */
if (scsa2usbp->scsa2usb_work_thread_id == 0) {
"no work thread started");
if (usba_rm_from_list(
return (TRAN_BUSY);
} else {
return (TRAN_ACCEPT);
}
}
}
return (TRAN_ACCEPT);
}
/*
* scsa2usb_scsi_abort:
* Issue SCSI abort command. This function is a NOP.
*/
/* ARGSUSED */
static int
{
"scsa2usb_scsi_abort: pkt = %p", (void *)pkt);
/* if device is disconnected (ie. pipes closed), fail immediately */
if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {
return (0);
}
/* flush waitQ if target and lun match */
}
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
{
/* flush waitQ */
return (1);
}
/*
* scsa2usb_scsi_getcap:
* Get SCSI capabilities.
*/
/* ARGSUSED */
static int
{
int rval = -1;
"scsa2usb_scsi_getcap: invalid arg, "
return (rval);
}
"scsa2usb_scsi_getcap: cap = %s", cap);
/* if device is disconnected (ie. pipes closed), fail immediately */
if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {
return (rval);
}
switch (cidx) {
case SCSI_CAP_GEOMETRY:
/* Just check and fail immediately if zero, rarely happens */
"scsa2usb_scsi_getcap failed:"
"scsa2usbp->scsa2usb_secsz[ap->a_lun] == 0");
return (rval);
}
DEV_BSIZE) {
}
/* unlabeled floppy, 18k per cylinder */
/* 1024k per cylinder */
/* ~8m per cylinder */
} else { /* .. 8TB */
/* 64m per cylinder */
}
break;
case SCSI_CAP_DMA_MAX:
break;
case SCSI_CAP_SCSI_VERSION:
break;
break;
case SCSI_CAP_ARQ:
/* FALLTHRU */
case SCSI_CAP_UNTAGGED_QING:
rval = 1;
break;
default:
"scsa2usb_scsi_getcap: unsupported cap = %s", cap);
break;
}
return (rval);
}
/*
* scsa2usb_scsi_setcap:
* Set SCSI capabilities.
*/
/* ARGSUSED */
static int
{
"scsa2usb_scsi_setcap: invalid arg");
return (rval);
}
/* if device is disconnected (ie. pipes closed), fail immediately */
if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {
return (rval);
}
"scsa2usb_scsi_setcap: ap = 0x%p value = 0x%x whom = 0x%x "
switch (cidx) {
case SCSI_CAP_SECTOR_SIZE:
if (value) {
}
break;
case SCSI_CAP_TOTAL_SECTORS:
if (value) {
}
break;
case SCSI_CAP_ARQ:
rval = 1;
break;
case SCSI_CAP_DMA_MAX:
case SCSI_CAP_SCSI_VERSION:
case SCSI_CAP_UNTAGGED_QING:
/* supported but not settable */
rval = 0;
break;
default:
"scsa2usb_scsi_setcap: unsupported cap = %s", cap);
break;
}
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: pkt=0x%p cdb: 0x%x (%s)",
pkt->pkt_statistics = 0;
if (cmd) {
cmd->cmd_total_xfercount = 0;
cmd->cmd_offset = 0;
}
}
/*
* scsa2usb_force_invalid_request
*/
static void
{
struct scsi_arq_status *arqp;
#ifdef DEBUG
{
"cdb: %x rqsense: "
"%x %x %x %x %x %x %x %x %x %x "
"%x %x %x %x %x %x %x %x %x %x",
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
}
}
/*
* scsa2usb_cmd_transport:
*/
static int
{
"scsa2usb_cmd_transport: pkt: 0x%p, cur_pkt = 0x%p",
/* check black-listed attrs first */
if (SCSA2USB_IS_BULK_ONLY(scsa2usbp)) {
}
/* just accept the command */
if (transport == SCSA2USB_JUST_ACCEPT) {
return (TRAN_ACCEPT);
}
/* check command set next */
if (SCSA2USB_IS_SCSI_CMDSET(scsa2usbp) ||
} else if (SCSA2USB_IS_UFI_CMDSET(scsa2usbp)) {
} else {
}
switch (transport) {
case SCSA2USB_TRANSPORT:
if (SCSA2USB_IS_BULK_ONLY(scsa2usbp)) {
} else if (SCSA2USB_IS_CB(scsa2usbp) ||
} else {
}
break;
case SCSA2USB_JUST_ACCEPT:
rval = TRAN_ACCEPT;
break;
default:
}
return (rval);
}
/*
* scsa2usb_check_bulkonly_blacklist_attrs:
* validate "scsa2usb_blacklist_attrs" (see scsa2usb.h)
* if blacklisted attrs match accept the request
* attributes checked are:-
* SCSA2USB_ATTRS_START_STOP
*/
int
{
struct scsi_inquiry *inq =
"scsa2usb_check_bulkonly_blacklist_attrs: opcode = %s",
/*
* decode and convert the packet
* for most cmds, we can bcopy the cdb
*/
switch (opcode) {
case SCMD_DOORLOCK:
return (SCSA2USB_JUST_ACCEPT);
/*
* only lock the door for CD and DVD drives
*/
break;
}
}
return (SCSA2USB_JUST_ACCEPT);
case SCMD_START_STOP:
/*
* 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.
*/
return (SCSA2USB_JUST_ACCEPT);
/*
* if the device is really a removable then
* pass it on to the device, else just accept
*/
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.
*/
unsigned int bufsize;
int count;
return (SCSA2USB_REJECT);
/* Copy no more than requested */
sizeof (struct scsi_inquiry));
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:
return (SCSA2USB_JUST_ACCEPT);
case SCMD_MODE_SENSE:
case SCMD_MODE_SELECT:
case SCMD_MODE_SENSE_G1:
case SCMD_MODE_SELECT_G1:
}
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: cmd = 0x%p pkt = 0x%p",
/*
* decode and convert the packet
* for most cmds, we can bcopy the cdb
*/
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)
*/
} else {
}
break;
case SCMD_INQUIRY:
break;
case SCMD_READ_CAPACITY:
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.
*
* 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_G5:
case SCMD_WRITE_G5:
case SCMD_READ_LONG:
case SCMD_WRITE_LONG:
case SCMD_READ_CD:
switch (scsa2usbp->
inq_dtype & DTYPE_MASK) {
case DTYPE_DIRECT:
case DTYPE_RODIRECT:
case DTYPE_OPTICAL:
return (scsa2usb_rw_transport(
default:
cmd->cmd_xfercount =
}
break;
}
break;
case SCMD_REQUEST_SENSE:
break;
case SCMD_DOORLOCK:
case SCMD_START_STOP:
case SCMD_TEST_UNIT_READY:
break;
/*
* Needed by zip protocol to reset the device
*/
case SCMD_SDIAG:
case SCMD_REZERO_UNIT:
break;
case SCMD_WRITE_VERIFY:
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:
break;
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:
}
return (SCSA2USB_JUST_ACCEPT);
}
/* FALLTHROUGH */
default:
/*
* an unknown command may be a uscsi cmd which we
* should let go thru without mapping
*/
}
break;
} /* end of switch */
"scsa2usb_handle_scsi_cmd_sub_class: opcode = 0x%x count = 0x%lx",
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
{
int rval = -1;
"scsa2usb_do_tur:");
"scsa2usb_do_tur: init pkt failed");
return (rval);
}
(char)SCMD_TEST_UNIT_READY, 0, 0);
"scsa2usb_do_tur: cmd transport failed, "
/*
* 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.
*/
"scsa2usb_do_tur: media not ready");
} else {
rval = 0;
}
return (rval);
}
/*
* scsa2usb_check_ufi_blacklist_attrs:
* validate "scsa2usb_blacklist_attrs" (see scsa2usb.h)
* if blacklisted attrs match accept the request
* attributes checked are:-
* SCSA2USB_ATTRS_GET_CONF
* SCSA2USB_ATTRS_GET_PERF
* SCSA2USB_ATTRS_GET_START_STOP
*/
static int
{
int rval = SCSA2USB_TRANSPORT;
switch (opcode) {
case SCMD_PRIN:
case SCMD_PROUT:
break;
case SCMD_MODE_SENSE:
case SCMD_MODE_SELECT:
}
break;
case SCMD_GET_CONFIGURATION:
}
break;
case SCMD_GET_PERFORMANCE:
}
break;
case SCMD_START_STOP:
/*
* media up and down. So, it doesn't make much sense
* to issue this cmd to those devices.
*/
}
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 &
if (scsa2usb_do_tur(scsa2usbp,
&pkt->pkt_address) != 0) {
/* media not ready, force cmd invalid */
}
}
}
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: cmd = 0x%p pkt = 0x%p",
/*
* 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 */
cmd->cmd_xfercount =
}
break;
case SCMD_INQUIRY:
break;
case SCMD_READ_CAPACITY:
break;
case SCMD_REQUEST_SENSE:
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_G5:
case SCMD_WRITE_G5:
case SCMD_READ_LONG:
case SCMD_WRITE_LONG:
case SCMD_READ_CD:
case SCMD_TEST_UNIT_READY:
/*
*/
break;
case SCMD_READ_FORMAT_CAP:
break;
case SCMD_WRITE_VERIFY:
break;
case SCMD_START_STOP:
/* A larger timeout is needed for 'flaky' CD-RW devices */
20 * SCSA2USB_BULK_PIPE_TIMEOUT);
}
/* FALLTHRU */
default:
/*
* all other commands don't need special mapping
*/
}
break;
} /* end of switch */
"scsa2usb_handle_ufi_subclass_cmd: opcode = 0x%x count = 0x%lx",
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
*
* 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
{
int sz;
"scsa2usb_rw_transport:");
/* set to default */
switch (opcode) {
case SCMD_READ:
/*
*/
dir = USB_EP_DIR_IN;
break;
case SCMD_WRITE:
break;
case SCMD_READ_G1:
case SCMD_READ_LONG:
dir = USB_EP_DIR_IN;
break;
case SCMD_WRITE_G1:
case SCMD_WRITE_LONG:
if (len) {
if (SCSA2USB_VALID_CDRW_BLKSZ(sz)) {
}
}
break;
case SCMD_READ_CD:
dir = USB_EP_DIR_IN;
/* Figure out the block size */
break;
case SCMD_READ_G5:
dir = USB_EP_DIR_IN;
break;
case SCMD_WRITE_G5:
break;
}
/* reduce xfer count if necessary */
if (blk_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)) {
} else {
}
}
/*
* Having figure out the 'partial' xfer len based on he
* block size; fill it in to the cmd->cmd_cdb
*/
switch (opcode) {
case SCMD_READ_CD:
break;
case SCMD_WRITE_G5:
case SCMD_READ_G5:
break;
default:
break;
}
"bcount=0x%lx lba=0x%x len=0x%lx xfercount=0x%lx total=0x%lx",
/* Set the timeout value as per command request */
/*
* 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.
*/
}
"lba 0x%x len 0x%lx xfercount 0x%lx total 0x%lx",
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
{
int cdb_len;
"scsa2usb_setup_next_xfer: opcode = 0x%x lba = 0x%x "
/*
* 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.
*
* NOTE: Always calculate lba first, as it based on previous
* commands' values.
*/
case SCMD_READ_CD:
/* calculate lba = current_lba + len_of_prev_cmd */
/* re-adjust xfer count */
break;
case SCMD_WRITE_G5:
case SCMD_READ_G5:
/* calculate lba = current_lba + len_of_prev_cmd */
if (blk_size) {
}
break;
case SCMD_WRITE_G1:
case SCMD_WRITE_LONG:
/* calculate lba = current_lba + len_of_prev_cmd */
}
/* re-adjust xfer count */
break;
default:
if (blk_size) {
}
}
/* fill in the lba */
"scsa2usb_setup_next_xfer:\n\tlba = 0x%x xfer_len = 0x%x "
}
/*
* take one request from the lun's waitQ and transport it
*/
static void
{
int rval;
if ((cmd = (scsa2usb_cmd_t *)
return;
}
/*
* if device has been disconnected, just complete it
*/
"device not accessible");
return;
}
"scsa2usb_transport_request: cmd=0x%p bp=0x%p addr=0x%p",
"scsa2usb_transport_request: transport rval = %d",
rval);
return;
}
if (ddi_in_panic()) {
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
*/
/*
* copy the timeout from the
* original packet
* for lack of a better value
*/
/* finish w/ this packet */
/*
* we have valid request sense
* data so clear the pkt_reason
*/
}
}
if ((rval != TRAN_ACCEPT) &&
}
}
/*
* scsa2usb_work_thread:
*/
static void
scsa2usb_work_thread(void *arg)
{
"scsa2usb_work_thread start: thread_id=0x%p",
(void *)scsa2usbp->scsa2usb_work_thread_id);
/* exclude ugen accesses */
while (scsa2usbp->scsa2usb_transport_busy) {
}
/* reopen the pipes if necessary */
(void) scsa2usb_open_usb_pipes(scsa2usbp);
for (;;) {
}
count = 0;
}
if (count == 0) {
break;
}
}
"scsa2usb_work_thread: exit");
}
/*
* 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
{
struct scsa2usb_cmd *cmd;
NULL) {
} /* 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
{
struct scsi_address ap;
int len = SCSA2USB_MAX_INQ_LEN;
"scsa2usb_do_inquiry: %d bytes", len);
/* is it inquiry-challenged? */
return;
}
/* limit inquiry to 36 bytes */
"scsa2usb_do_inquiry: failed");
return;
}
"scsa2usb_do_inquiry:INQUIRY");
(void) scsi_transport(pkt);
if (pkt->pkt_reason) {
"INQUIRY failed, cannot determine device type, "
/* not much hope for other cmds, reduce */
}
}
/*
* scsa2usb_fake_inquiry:
* build an inquiry for a given device that doesnt like inquiry
* commands.
*/
static void
{
int len;
"scsa2usb_fake_inquiry:");
}
}
}
}
if (dev_data->dev_product) {
}
}
/* Set the Revision to the Device */
}
/*
* scsa2usb_create_arq_pkt:
* Create and ARQ packet to get request sense data
*/
static int
{
"scsa2usb_create_arq_pkt: scsa2usbp: %p, ap: %p",
return (USB_FAILURE);
}
(char)SCMD_REQUEST_SENSE, 0, (char)SENSE_LENGTH);
return (USB_SUCCESS);
}
/*
* scsa2usb_delete_arq_pkt:
* Destroy the ARQ packet
*/
static void
{
"scsa2usb_delete_arq_pkt: cmd: 0x%p",
(void *)scsa2usbp->scsa2usb_arq_cmd);
}
}
/*
* scsa2usb_complete_arq_pkt:
* finish processing the arq packet
*/
static void
{
struct scsi_arq_status *arqp;
/* is this meaningful sense data */
}
/* we will not sense start cmd until we receive a NOT READY */
}
}
/*
*/
/*
* scsa2usb_open_usb_pipes:
* set up a pipe policy
* open usb interrupt pipe (CBI)
*/
static int
{
int rval;
"scsa2usb_open_usb_pipes: dip = 0x%p flag = 0x%x",
/*
* one pipe policy for all bulk pipes
*/
/* at least 2, for the normal and exceptional callbacks */
"scsa2usb_open_usb_pipes: opening bulk pipes");
/* Open the USB bulk-in pipe */
" failed rval = %d", rval);
return (USB_FAILURE);
}
/* Open the bulk-out pipe using the same policy */
" failed rval = %d", rval);
return (USB_FAILURE);
}
/* open interrupt pipe for CBI protocol */
if (SCSA2USB_IS_CBI(scsa2usbp)) {
USB_SUCCESS) {
"scsa2usb_open_usb_pipes: intr pipe open"
" failed rval = %d", rval);
return (USB_FAILURE);
}
}
/* get the max transfer size of the bulk pipe */
&sz) == USB_SUCCESS) {
} else {
}
/* limit the xfer size */
"scsa2usb_open_usb_pipes: max bulk transfer size = %lx",
/* Set the pipes opened flag */
/* Set the state to NONE */
}
return (USB_SUCCESS);
}
/*
* scsa2usb_close_usb_pipes:
* close all pipes synchronously
*/
void
{
"scsa2usb_close_usb_pipes: scsa2usb_state = 0x%p",
(void *)scsa2usbp);
return;
}
/* to avoid races, reset the flag first */
if (SCSA2USB_IS_CBI(scsa2usbp)) {
}
}
/*
* scsa2usb_fill_up_cdb_lba:
* fill up command CDBs' LBA part
*/
static void
{
/* zero cdb1, lba bits so they won't get copied in the new cdb */
}
/*
* scsa2usb_fill_up_ReadCD_cdb_len:
* fill up READ_CD command CDBs' len part
*/
static void
{
}
/*
* scsa2usb_fill_up_12byte_cdb_len:
* fill up generic 12-byte command CDBs' len part
*/
static void
{
}
/*
* scsa2usb_fill_up_cdb_len:
* fill up generic 10-byte command CDBs' len part
*/
static void
{
}
/*
* 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
{
int blk_size;
switch (expected_sector_type) {
case READ_CD_EST_CDDA:
break;
case READ_CD_EST_MODE2:
break;
case READ_CD_EST_MODE2FORM2:
break;
case READ_CD_EST_MODE2FORM1:
case READ_CD_EST_ALLTYPE:
case READ_CD_EST_MODE1:
default:
}
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: ");
} else {
return (NULL);
}
"scsa2usb_bp_to_mblk: "
"mp=0x%p bp=0x%p pkt=0x%p off=0x%lx sz=%lu add=0x%p",
return (mp);
}
/*
* scsa2usb_handle_data_start:
*
* 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
{
int rval = USB_SUCCESS;
#ifdef SCSA2USB_BULK_ONLY_TEST
usb_req_attrs_t attrs = 0;
#else
#endif
"scsa2usb_handle_data_start: BEGIN cmd = %p, req = %p",
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) {
req->bulk_attributes = 0;
"TEST 5: Hi > Di: rval = 0x%x", rval);
scsa2usb_test_case_5 = 0;
return (rval);
}
/*
* This happens when the host expects to send data to the
* device while the device intends to send data to the host.
*/
"TEST 8: Hi <> Do: Step 2");
scsa2usb_test_case_8 = 0;
return (rval);
}
#endif /* SCSA2USB_BULK_ONLY_TEST */
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 &&
"TEST 10: Ho <> Di: done rval = 0x%x", rval);
return (rval);
}
#endif /* SCSA2USB_BULK_ONLY_TEST */
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
*/
}
#endif /* SCSA2USB_BULK_ONLY_TEST */
break;
}
"scsa2usb_handle_data_start: rval=%d cr=%d", rval,
if (rval != USB_SUCCESS) {
/* Handle Errors now */
(void) scsa2usb_clear_ept_stall(
"bulk-in");
} else {
(void) scsa2usb_clear_ept_stall(
"bulk-out");
}
}
/* no more data to transfer after this */
}
"scsa2usb_handle_data_start: END %s data rval = %d",
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:\n\tcmd = 0x%p data = 0x%p len = 0x%x",
if (len) {
uchar_t *p;
struct scsi_inquiry *inq;
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.
*/
(len >= SCSA2USB_MAX_INQ_LEN)) {
/*
* 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) ||
}
sizeof (struct scsi_inquiry));
}
"scsi inquiry type = 0x%x",
goto handle_data;
case SCMD_READ_CAPACITY:
/* Figure out the logical block size */
if ((len >= sizeof (struct scsa2usb_read_cap)) &&
/*
* Some devices return total logical block
* number instead of highest logical block
* address. Adjust the value by minus 1.
*/
SCSA2USB_ATTRS_NO_CAP_ADJUST) == 0) {
max_lba -= 1;
}
"bytes in each logical block=0x%lx,"
"number of total logical blocks=0x%x",
max_lba + 1);
}
goto handle_data;
case SCMD_REQUEST_SENSE:
"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",
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]);
/* FALLTHROUGH */
default:
/*
* 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.
*/
}
"len = 0x%x total = 0x%lx offset = 0x%lx",
/*
* update total_xfercount now but it may be
* adjusted after receiving the residue
*/
(cmd->cmd_resid_xfercount != 0) ||
(cmd->cmd_total_xfercount == 0)) {
/* set pkt_resid to total to be sure */
}
break;
}
} else {
if (cmd->cmd_total_xfercount == 0) {
}
}
}
}
/*
* scsa2usb_init_bulk_req:
* Allocate (synchronously) and fill in a bulk-request
*/
{
flags | USB_FLAGS_SLEEP);
return (req);
}
/*
* scsa2usb_bulk_timeout:
* ensure that bulk requests do not have infinite timeout values
*/
int
{
}
/*
* scsa2usb_clear_ept_stall:
* clear endpoint stall and reset pipes
*/
int
{
int rval;
if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {
return (USB_FAILURE);
}
"scsa2usb_clear_ept_stall: on %s: ept = 0x%x rval = %d",
return (rval);
}
/*
* scsa2usb_pkt_completion:
* Handle pkt completion.
*/
static void
{
"scsa2usb_pkt_completion:\n\tscsa2usbp = 0x%p "
"reason=%d, status=%d state=0x%x stats=0x%x resid=0x%lx",
if (cmd->cmd_xfercount) {
}
} else {
}
/*
* don't zap the current state when in panic as this will
* make debugging harder
*/
"scsa2usb_pkt_completion: save last cmd, len=%ld", len);
/* save the last command */
/* reset the scsa2usb_last_cmd.status value */
}
/*
* set pkt state to NONE *before* calling back as the target
* driver will immediately submit the next packet
*/
}
}
}
/*
* Even handling functions:
*
* scsa2usb_reconnect_event_cb:
* event handling
*/
static int
{
int circ;
int rval = USB_SUCCESS;
"scsa2usb_reconnect_event_cb: dip = 0x%p", (void *)dip);
"Reinserted device is accessible again.");
}
/* stop suppressing warnings */
if (scsa2usbp->scsa2usb_ugen_hdl) {
}
return (rval);
}
/*
* scsa2usb_all_waitQs_empty:
* check if all waitQs empty
*/
static int
{
return (USB_FAILURE);
}
}
return (USB_SUCCESS);
}
/*
* scsa2usb_disconnect_event_cb:
* callback for disconnect events
*/
static int
{
int circ, i;
int rval = USB_SUCCESS;
"scsa2usb_disconnect_event_cb: dip = 0x%p", (void *)dip);
/*
* wait till the work thread is done, carry on regardless
* if not.
*/
for (i = 0; i < SCSA2USB_DRAIN_TIMEOUT; i++) {
USB_SUCCESS)) {
break;
}
}
}
if (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: dip = 0x%p, scsa2usbp = 0x%p",
/*
* determine if this device is on the blacklist
* or if a conf file entry has disabled PM
*/
"device cannot be power managed");
return;
}
/* Allocate the PM state structure */
USB_SUCCESS) {
}
}
}
/*
* scsa2usb_raise_power:
* check if the device is using full power or not
*/
static void
{
"scsa2usb_raise_power:");
if (scsa2usbp->scsa2usb_pm) {
0, USB_DEV_OS_FULL_PWR);
}
}
}
/*
* functions to handle power transition for OS levels 0 -> 3
*/
static int
{
int rval;
switch (scsa2usbp->scsa2usb_dev_state) {
case USB_DEV_ONLINE:
/* Deny the powerdown request if the device is busy */
return (USB_FAILURE);
}
/*
* stop polling on interrupt pipe
*/
/* Issue USB D3 command to the device here */
/* FALLTHRU */
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
case USB_DEV_PWRED_DOWN:
default:
return (USB_SUCCESS);
}
}
static int
{
int rval;
/* Issue USB D2 command to the device here */
return (DDI_FAILURE);
}
static int
{
int rval;
/* Issue USB D1 command to the device here */
return (DDI_FAILURE);
}
static int
{
int rval;
/*
* PM framework tries to put us in full power
* during system shutdown. If we are disconnected
* return success anyways
*/
/* Issue USB D0 command to the device here */
}
return (DDI_SUCCESS);
}
/*
* scsa2usb_power:
* power entry point
*/
/* ARGSUSED */
static int
{
int rval = DDI_FAILURE;
"scsa2usb_power: Begin scsa2usbp (%p): level = %d",
if (SCSA2USB_BUSY(scsa2usbp)) {
"scsa2usb_power: busy");
return (rval);
}
"scsa2usb_power: pm NULL");
return (rval);
}
/* check if we are transitioning to a legal power level */
"scsa2usb_power: illegal power level = %d "
return (rval);
}
switch (level) {
case USB_DEV_OS_PWR_OFF :
break;
case USB_DEV_OS_PWR_1 :
break;
case USB_DEV_OS_PWR_2 :
break;
case USB_DEV_OS_FULL_PWR :
break;
}
}
static void
{
if (scsa2usbp->scsa2usb_pm) {
"scsa2usb_pm_busy_component: %d",
DDI_SUCCESS) {
"scsa2usb_pm_busy_component failed: %d",
return;
}
}
}
/*
* scsa2usb_pm_idle_component:
* idles the device
*/
static void
{
if (scsa2usbp->scsa2usb_pm) {
DDI_SUCCESS) {
"scsa2usb_pm_idle_component: %d",
}
}
}
#ifdef DEBUG
/*
* scsa2usb_print_cdb:
* prints CDB
*/
void
{
"cmd = 0x%p opcode=%s "
"cdb: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
(void *)cmd,
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
{
int i, rval;
/* should we create a larger mblk? */
/* fill up the data mblk */
for (i = 0; i < len; i++) {
}
"scsa2usb_test_mblk: Sent Data Out rval = 0x%x", rval);
}
#endif /* SCSA2USB_BULK_ONLY_TEST */