/*
* 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.
*/
/*
*
* This file implements USB Mass Storage Class
* Bulk Only (BO) transport v1.0
*/
/*
* Function Prototypes
*/
scsa2usb_cmd_t *);
mblk_t *);
static void scsa2usb_bulk_only_reset_recovery(scsa2usb_state_t *);
static void scsa2usb_bulk_only_handle_error(scsa2usb_state_t *,
usb_bulk_req_t *);
static int scsa2usb_handle_status_start(scsa2usb_state_t *,
usb_bulk_req_t *);
/* extern functions */
extern int scsa2usb_handle_data_start(scsa2usb_state_t *,
scsa2usb_cmd_t *, usb_bulk_req_t *);
usb_bulk_req_t *);
extern int scsa2usb_bulk_timeout(int);
usb_pipe_handle_t, char *);
extern void scsa2usb_close_usb_pipes(scsa2usb_state_t *);
#ifdef DEBUG /* debugging information */
#endif /* DEBUG */
#ifdef SCSA2USB_BULK_ONLY_TEST
/*
* Test 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.
*/
int scsa2usb_test_case_2 = 0;
int scsa2usb_test_case_3 = 0;
int scsa2usb_test_case_4 = 0;
int scsa2usb_test_case_7 = 0;
extern int scsa2usb_test_case_8;
int scsa2usb_test_case_9 = 0;
extern int scsa2usb_test_case_10;
int scsa2usb_test_case_13 = 0;
#endif /* SCSA2USB_BULK_ONLY_TEST */
/*
* scsa2usb_bulk_only_transport:
* Implements the BO state machine by these steps:
* a) Issues CBW to a Bulk Only device.
* b) Start Data Phase if applicable
* c) Start Status Phase
*
* returns TRAN_* values
*
* scsa2usb_bulk_only_state_machine:
*
* scsa2usb_bulk_only_transport() handles the normal transitions or
* continuation after clearing stalls or error recovery.
*
* Command Phase:
* prepare a valid CBW and transport it on bulk-out pipe
* if error on bulkout:
* set pkt_reason to CMD_TRAN_ERR
* new pkt state is SCSA2USB_PKT_DO_COMP
* reset recovery synchronously
* else
* proceed to data phase
*
* Data Phase:
* if data in:
* setup data in on bulkin
* else if data out:
* setup data out on bulkout
*
* data: (in)
* copy data transferred so far, no more data to transfer
*
* if stall on bulkin pipe
* terminate data transfers, set cmd_done
* clear stall on bulkin syncrhonously
* else if other exception
* set pkt_reason to CMD_TRAN_ERR
* new pkt state is SCSA2USB_PKT_DO_COMP
* reset recovery syncrhonously
* else (no error)
* receive status
*
* data: (out)
* if stall on bulkout pipe
* terminate data transfers, set cmd_done
* clear stall on bulkout synchronously USBA
* else if other exception
* set pkt_reason to CMD_TRAN_ERR
* new pkt state is SCSA2USB_PKT_DO_COMP
* reset recovery synchronously
* else (no error)
* receive status
*
* Status Phase:
*
* if stall (first attempt)
* new pkt state is SCSA2USB_PKT_PROCESS_CSW
* setup receiving status on bulkin
* if stall (second attempt)
* new pkt state is SCSA2USB_PKT_DO_COMP
* reset recovery synchronously, we are hosed.
* else
* goto check CSW
* else
* goto check CSW
*
* check CSW:
* - check length equals 13, signature, and matching tag
* - check status is less than or equal to 2
* - check residue is less than or equal to data length
* adjust residue based on if we got valid data
*
* if not OK
* new pkt state is SCSA2USB_PKT_DO_COMP
* set pkt reason CMD_TRAN_ERR
* reset recovery synchronously, we are hosed
* else if phase error
* new pkt state is SCSA2USB_PKT_DO_COMP
* set pkt reason CMD_TRAN_ERR
* reset recovery synchronously
* else if (status < 2)
* if status is equal to 1
* set check condition
* if residue
* calculate residue from data xferred and DataResidue
*
* set pkt_residue
* goto SCSA2USB_PKT_DO_COMP
*
* The reset recovery walks sequentially thru device reset, clearing
* stalls and pipe resets. When the reset recovery completes we return
* to the taskq thread.
*
* Clearing stalls clears the stall condition, resets the pipe, and
* then returns to the transport.
*/
int
{
int rval;
int nretry;
/*
* Start Command Phase
* Initialize a bulk_req_t
*/
/* Send a Bulk Command Block Wrapper (CBW) to the device */
"scsa2usb_bulk_only_transport: "
"sent cmd = 0x%x Tag = 0x%x DataXferLen = 0x%lx rval = %d",
rval);
if (rval != USB_SUCCESS) {
return (TRAN_FATAL_ERROR);
}
/* free the data */
/*
* Start Data Phase
* re-set timeout
*/
/*
* we've not transferred any data yet; updated in
* scsa2usb_handle_data_done
*/
if (cmd->cmd_xfercount) {
/* handle data returned, if any */
if (rval != USB_SUCCESS) {
"data xfer phase, error = %d, cr = %d",
/*
* we ran into an error
*/
if (scsa2usbp->scsa2usb_cur_pkt) {
}
} else {
return (TRAN_FATAL_ERROR);
}
} /* end of else */
/* free the data */
}
/*
* Start status phase
* read in CSW
*/
if ((rval != USB_SUCCESS) &&
/*
* We ran into STALL condition here.
* If the condition cannot be cleared
* successfully, retry for limited times.
*/
} else {
break;
}
}
if (rval == USB_SUCCESS) {
/* process CSW */
} else {
return (TRAN_FATAL_ERROR);
}
goto Cmd_Phase;
}
}
/*
* scsa2usb_fill_in_cbw:
* Fill in a CBW request packet. This
* packet is transported to the device
*/
static void
{
int i;
int len;
#ifdef SCSA2USB_BULK_ONLY_TEST
/* Host expects no data. The device wants data. Hn < Di */
scsa2usb_test_case_2 = len = 0;
}
/* Host expects no data. The device wants data. Hn < Do */
if (cdb[0] == SCMD_WRITE_G1) {
scsa2usb_test_case_3 = len = 0;
}
}
cdb[0] = 0x5e;
"TEST 4: Hi > Dn: changed cdb to 0x%x", cdb[0]);
scsa2usb_test_case_4 = 0;
}
len -= 0x10;
scsa2usb_test_case_7 = 0;
}
}
"TEST 9: Ho <> Di (%x)", cdb[0]);
scsa2usb_test_case_9 = 0;
}
}
/*
* This case occurs when the device intends to receive
* more data from the host than the host sends.
*/
if (scsa2usb_test_case_13) {
len -= 30;
}
}
#endif /* SCSA2USB_BULK_ONLY_TEST */
/* Copy the CDB out */
for (i = 0; i < CBW_CDB_LEN; i++) {
}
#ifdef DUMP_CWB
{
char *buf;
int i;
for (i = 0; i < len; i++) {
}
}
#endif
}
/*
* scsa2usb_bulk_only_handle_error:
* handle transport errors and start recovery
*/
static void
{
"scsa2usb_bulk_only_handle_error: req = 0x%p, cr = 0x%x",
if (req) {
/* invoke reset recovery */
switch (req->bulk_completion_reason) {
case USB_CR_STALL:
if (pkt) {
}
break;
case USB_CR_TIMEOUT:
if (pkt) {
}
break;
case USB_CR_DEV_NOT_RESP:
if (pkt) {
/* scsi_poll relies on this */
}
break;
default:
if (pkt) {
}
}
}
}
/*
* scsa2usb_handle_status_start:
* Receive status data
*/
static int
{
int rval;
"scsa2usb_handle_status_start: req = 0x%p", (void *)req);
/* setup up for receiving CSW */
#ifdef SCSA2USB_BULK_ONLY_TEST
req->bulk_attributes = 0;
#else
#endif /* SCSA2USB_BULK_ONLY_TEST */
/* Issue the request */
"scsa2usb_handle_status_start: END rval = 0x%x", rval);
if (rval != USB_SUCCESS) {
return (rval);
}
(void) scsa2usb_clear_ept_stall(scsa2usbp,
}
}
return (rval);
}
/*
* scsa2usb_handle_csw_result:
* Handle status results
*/
static int
{
int residue;
/*
* This shouldn't happen. It implies the device's
* firmware is bad and has returned NULL CSW.
* return failure back.
*/
"scsa2usb_handle_csw_result: data == NULL");
return (USB_FAILURE);
}
/* check if we got back CSW_LEN or not */
"scsa2usb_handle_csw_result: no enough data (%ld)",
return (USB_FAILURE);
}
/* Read into csw */
"CSW: Signature = 0x%x Status = 0%x Tag = 0x%x Residue = 0x%x",
/* Check for abnormal errors */
(status > CSW_STATUS_PHASE_ERROR)) {
"CSW_ERR: Status = 0x%x, Tag = 0x%x xfercount = 0x%lx",
return (USB_FAILURE);
}
switch (status) {
case CSW_STATUS_GOOD:
/*
* Fail the command if the device misbehaves and
* gives a good status but doesn't transfer any data.
* Otherwise we'll get into an infinite retry loop.
*
* We test only against cmd_total_xfercount here and
* assume that this will not happen on a command that
* transfers a large amount of data and therefore may
* be split into separate transfers. For a large data
* transfer it is assumed that the device will return
* an error status if the transfer does not occur.
* this isn't quite correct because a subsequent request
* sense may not give a valid sense key.
*/
cmd->cmd_xfercount = 0;
} else {
msg = "CSW GOOD";
}
break;
case CSW_STATUS_FAILED:
break;
case CSW_STATUS_PHASE_ERROR:
"scsa2usb_handle_csw_result: Phase Error");
/* invoke reset recovery */
return (USB_FAILURE);
default: /* shouldn't happen anymore */
"scsa2usb_handle_csw_result: Invalid CSW");
/* invoke reset recovery */
return (USB_SUCCESS);
} /* end of switch */
/* Set resid */
"total=0x%lx cmd_xfercount=0x%lx residue=0x%x "
"cmd_offset=0x%lx",
/*
* we need to adjust using the residue and
* assume worst case. Some devices lie about
* residue. some report a residue greater than
* the residue we have calculated.
* first adjust back the total_xfercount
*/
/*
* we need to adjust cmd_offset as well, or the data
* buffer for subsequent transfer may exceed the buffer
* boundary
*/
/*
* now take the min of the reported residue by
* the device and the requested xfer count
* (just in case the device reported a residue greater
* than our request count).
* then take the max of this residue and the residue
* that the HCD reported and subtract this from
* the request count. This is the actual number
* of valid bytes transferred during the last transfer
* which we now subtract from the total_xfercount
*/
if ((!(scsa2usbp->scsa2usb_attrs &
(residue < 0) ||
/* some devices lie about the resid, ignore */
cmd->cmd_offset +=
} else {
cmd->cmd_xfercount -
cmd->cmd_offset +=
cmd->cmd_xfercount -
/*
* if HCD does not report residue while the device
* reports a residue equivalent to the xfercount,
* it is very likely the device lies about the
* residue. we need to stop the command, or we'll
* get into an infinite retry loop.
*/
if ((cmd->cmd_resid_xfercount == 0) &&
cmd->cmd_xfercount = 0;
}
}
}
"scsa2usb_handle_csw_result: %s, resid: 0x%lx",
/* we are done and ready to callback */
return (rval);
}
/*
* scsa2usb_bulk_only_reset_recovery:
* Reset the USB device step-wise in case of errors.
* NOTE that the order of reset is very important.
*/
static void
{
int rval;
"scsa2usb_bulk_only_reset_recovery: scsa2usbp = 0x%p",
(void *)scsa2usbp);
if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {
return;
}
/*
* assume that the reset will be successful. if it isn't, retrying
* from target driver won't help much
*/
if (scsa2usbp->scsa2usb_cur_pkt) {
}
/* set the reset condition */
/* Send a sync DEVICE-RESET request to the device */
0, /* wValue */
0, /* wLength */
"\tbulk-only device-reset rval: %d", rval);
if (rval != USB_SUCCESS) {
goto exc_exit;
}
/* reset and clear STALL on bulk-in pipe */
"\tbulk-in pipe clear stall: %d", rval);
if (rval != USB_SUCCESS) {
goto exc_exit;
}
/* reset and clear STALL on bulk-out pipe */
"\tbulk-out pipe clear stall: %d", rval);
/* clear the reset condition */
}
/*
* scsa2usb_bulk_only_get_max_lun:
* this function returns the number of LUNs supported by the device
*/
int
{
"scsa2usb_bulk_only_get_max_lun:");
BULK_ONLY_GET_MAXLUN_BMREQ, /* bmRequestType */
BULK_ONLY_GET_MAXLUN_REQ, /* bRequest */
0, /* wValue */
1, /* wLength */
&data, 0,
&completion_reason, &cb_flags, 0);
if (rval != USB_SUCCESS) {
"get max lun failed, rval=%d cr=%d cb=0x%x data=0x%p",
} else {
/*
* This check ensures that we have valid data returned back.
* Otherwise we assume that device supports only one LUN.
*/
"device reported incorrect luns (adjusting to 1)");
} else {
/*
* Set scsa2usb_n_luns to value returned by the device
* plus 1. (See Section 3.2)
*/
/*
* In case a device returns incorrect LUNs
* which are more than 15 or negative or 0;
* we assume 1.
*/
"device reported %d luns "
"(adjusting to 1)", luns);
luns = 1;
}
}
}
return (luns);
}