#include <sys/usb/usba/usbai_version.h>
#include <sys/scsi/scsi.h>
#include <sys/callb.h>
#include <sys/strsubr.h>
#include <sys/usb/usba.h>
#include <sys/usb/usba/usba_private.h>
#include <sys/usb/usba/usba_ugen.h>
#include <sys/usb/clients/mass_storage/usb_cbi.h>
#include <sys/usb/scsa2usb/scsa2usb.h>
int scsa2usb_cbi_transport(scsa2usb_state_t *, scsa2usb_cmd_t *);
static int scsa2usb_handle_cbi_status(usb_intr_req_t *);
static void scsa2usb_cbi_reset_recovery(scsa2usb_state_t *);
static void scsa2usb_cbi_handle_error(scsa2usb_state_t *, int, usb_cr_t);
static usb_intr_req_t *scsa2usb_cbi_start_intr_polling(scsa2usb_state_t *);
void scsa2usb_cbi_stop_intr_polling(scsa2usb_state_t *);
extern void scsa2usb_setup_next_xfer(scsa2usb_state_t *, scsa2usb_cmd_t *);
extern int scsa2usb_handle_data_start(scsa2usb_state_t *,
scsa2usb_cmd_t *, usb_bulk_req_t *);
extern void scsa2usb_handle_data_done(scsa2usb_state_t *, scsa2usb_cmd_t *,
usb_bulk_req_t *);
extern usb_bulk_req_t *scsa2usb_init_bulk_req(scsa2usb_state_t *,
size_t, uint_t, usb_req_attrs_t, usb_flags_t);
extern int scsa2usb_clear_ept_stall(scsa2usb_state_t *, uint_t,
usb_pipe_handle_t, char *);
extern void scsa2usb_close_usb_pipes(scsa2usb_state_t *);
#ifdef DEBUG
extern void scsa2usb_print_cdb(scsa2usb_state_t *, scsa2usb_cmd_t *);
#endif
int
scsa2usb_cbi_transport(scsa2usb_state_t *scsa2usbp, scsa2usb_cmd_t *cmd)
{
int i, rval = TRAN_ACCEPT;
mblk_t *data;
usb_cr_t completion_reason;
usb_cb_flags_t cb_flags;
usb_bulk_req_t *data_req;
usb_intr_req_t *status_req;
USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"scsa2usb_cbi_transport: cmd = 0x%p", (void *)cmd);
ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));
Cmd_Phase:
if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {
return (TRAN_FATAL_ERROR);
}
data = allocb_wait(CBI_CLASS_CMD_LEN, BPRI_LO, STR_NOSIG, NULL);
for (i = 0; i < CBI_CLASS_CMD_LEN; i++) {
*data->b_wptr++ = cmd->cmd_cdb[i];
}
SCSA2USB_PRINT_CDB(scsa2usbp, cmd);
mutex_exit(&scsa2usbp->scsa2usb_mutex);
rval = usb_pipe_sync_ctrl_xfer(scsa2usbp->scsa2usb_dip,
scsa2usbp->scsa2usb_default_pipe,
CBI_REQUEST_TYPE,
0,
CBI_WVALUE,
scsa2usbp->scsa2usb_intfc_num,
CBI_CLASS_CMD_LEN,
&data,
USB_ATTRS_PIPE_RESET,
&completion_reason, &cb_flags, USB_FLAGS_SLEEP);
mutex_enter(&scsa2usbp->scsa2usb_mutex);
USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"scsa2usb_cbi_transport: sent cmd = 0x%x rval = %d",
cmd->cmd_cdb[SCSA2USB_OPCODE], rval);
SCSA2USB_FREE_MSG(data);
if (rval != USB_SUCCESS) {
scsa2usb_cbi_handle_error(scsa2usbp, rval, completion_reason);
return (TRAN_FATAL_ERROR);
}
cmd->cmd_resid_xfercount = 0;
if (cmd->cmd_xfercount) {
data_req = scsa2usb_init_bulk_req(scsa2usbp, 0,
cmd->cmd_timeout, USB_ATTRS_PIPE_RESET, USB_FLAGS_SLEEP);
rval = scsa2usb_handle_data_start(scsa2usbp, cmd,
data_req);
scsa2usb_handle_data_done(scsa2usbp, cmd,
data_req);
if (rval != USB_SUCCESS) {
if (data_req->bulk_completion_reason == USB_CR_STALL) {
if (scsa2usbp->scsa2usb_cur_pkt) {
scsa2usbp->scsa2usb_cur_pkt->
pkt_reason = CMD_TRAN_ERR;
}
} else {
scsa2usb_cbi_handle_error(scsa2usbp,
rval, data_req->bulk_completion_reason);
SCSA2USB_FREE_BULK_REQ(data_req);
return (TRAN_FATAL_ERROR);
}
}
SCSA2USB_FREE_BULK_REQ(data_req);
}
if (SCSA2USB_IS_CB(scsa2usbp)) {
USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"scsa2usb_cbi_transport: CB done rval = %d", rval);
goto end_it;
}
if ((status_req = scsa2usb_cbi_start_intr_polling(scsa2usbp)) == NULL) {
return (TRAN_FATAL_ERROR);
}
rval = scsa2usb_handle_cbi_status(status_req);
usb_free_intr_req(status_req);
if (SCSA2USB_IS_CBI(scsa2usbp)) {
scsa2usb_cbi_stop_intr_polling(scsa2usbp);
}
end_it:
if ((rval == USB_SUCCESS) &&
(scsa2usbp->scsa2usb_cur_pkt->pkt_reason == CMD_CMPLT) &&
(cmd->cmd_xfercount != 0) &&
!cmd->cmd_done) {
scsa2usb_setup_next_xfer(scsa2usbp, cmd);
goto Cmd_Phase;
} else {
if (SCSA2USB_IS_CB(scsa2usbp)) {
cmd->cmd_done = 1;
SCSA2USB_SET_PKT_DO_COMP_STATE(scsa2usbp);
}
}
return (rval == USB_SUCCESS ? TRAN_ACCEPT : TRAN_FATAL_ERROR);
}
static void
scsa2usb_cbi_handle_error(scsa2usb_state_t *scsa2usbp, int rval, usb_cr_t cr)
{
struct scsi_pkt *pkt = scsa2usbp->scsa2usb_cur_pkt;
USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"scsa2usb_cbi_handle_error: data error %d cr = %d", rval, cr);
SCSA2USB_SET_PKT_DO_COMP_STATE(scsa2usbp);
switch (cr) {
case USB_CR_STALL:
if (pkt) {
pkt->pkt_reason = CMD_TRAN_ERR;
*(pkt->pkt_scbp) = STATUS_CHECK;
}
break;
case USB_CR_TIMEOUT:
if (pkt) {
pkt->pkt_reason = CMD_TIMEOUT;
pkt->pkt_statistics |= STAT_TIMEOUT;
}
break;
case USB_CR_DEV_NOT_RESP:
scsa2usb_cbi_stop_intr_polling(scsa2usbp);
if (pkt) {
pkt->pkt_reason = CMD_DEV_GONE;
pkt->pkt_state = STATE_GOT_BUS;
}
break;
default:
if (pkt) {
pkt->pkt_reason = CMD_TRAN_ERR;
}
scsa2usb_cbi_reset_recovery(scsa2usbp);
}
}
static usb_intr_req_t *
scsa2usb_cbi_start_intr_polling(scsa2usb_state_t *scsa2usbp)
{
int rval;
usb_pipe_state_t pipe_state;
usb_intr_req_t *req = NULL;
USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"scsa2usb_cbi_start_intr_polling:");
if (!SCSA2USB_IS_CBI(scsa2usbp)) {
return (NULL);
}
req = usb_alloc_intr_req(scsa2usbp->scsa2usb_dip, 0, USB_FLAGS_SLEEP);
req->intr_client_private = (usb_opaque_t)scsa2usbp;
req->intr_attributes = USB_ATTRS_ONE_XFER | USB_ATTRS_PIPE_RESET |
USB_ATTRS_SHORT_XFER_OK;
req->intr_len = scsa2usbp->scsa2usb_intr_ept.wMaxPacketSize;
req->intr_timeout = 20;
mutex_exit(&scsa2usbp->scsa2usb_mutex);
if ((rval = usb_pipe_intr_xfer(scsa2usbp->scsa2usb_intr_pipe, req,
USB_FLAGS_SLEEP)) != USB_SUCCESS) {
mutex_enter(&scsa2usbp->scsa2usb_mutex);
USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"polling failed rval: %d", rval);
if (req->intr_completion_reason == USB_CR_STALL) {
(void) scsa2usb_clear_ept_stall(scsa2usbp,
scsa2usbp->scsa2usb_intr_ept.bEndpointAddress,
scsa2usbp->scsa2usb_intr_pipe, "intr");
}
scsa2usb_cbi_handle_error(scsa2usbp, rval,
req->intr_completion_reason);
mutex_exit(&scsa2usbp->scsa2usb_mutex);
usb_free_intr_req(req);
req = NULL;
}
rval = usb_pipe_get_state(scsa2usbp->scsa2usb_intr_pipe,
&pipe_state, USB_FLAGS_SLEEP);
if (pipe_state != USB_PIPE_STATE_ACTIVE) {
USB_DPRINTF_L2(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"intr pipes state: %d, rval: %d", pipe_state, rval);
}
mutex_enter(&scsa2usbp->scsa2usb_mutex);
return (req);
}
void
scsa2usb_cbi_stop_intr_polling(scsa2usb_state_t *scsa2usbp)
{
USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"scsa2usb_cbi_stop_intr_polling:");
ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));
if (!SCSA2USB_IS_CBI(scsa2usbp)) {
return;
}
if (scsa2usbp->scsa2usb_intr_pipe) {
mutex_exit(&scsa2usbp->scsa2usb_mutex);
usb_pipe_stop_intr_polling(scsa2usbp->scsa2usb_intr_pipe,
USB_FLAGS_SLEEP);
mutex_enter(&scsa2usbp->scsa2usb_mutex);
}
}
static int
scsa2usb_handle_cbi_status(usb_intr_req_t *req)
{
int rval = USB_SUCCESS;
int status;
char *msg;
scsa2usb_cmd_t *cmd;
scsa2usb_state_t *scsa2usbp = (scsa2usb_state_t *)
req->intr_client_private;
USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"scsa2usb_handle_cbi_status: req: 0x%p", (void *)req);
ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));
ASSERT(req->intr_data != NULL);
cmd = PKT2CMD(scsa2usbp->scsa2usb_cur_pkt);
status = *(req->intr_data->b_rptr + 1) & CBI_STATUS_MASK;
if ((cmd->cmd_cdb[SCSA2USB_OPCODE] == SCMD_REQUEST_SENSE) ||
(cmd->cmd_cdb[SCSA2USB_OPCODE] == SCMD_INQUIRY)) {
USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"scsa2usb_handle_cbi_status: CBI STATUS = (0x%x, 0x%x)",
*(req->intr_data->b_rptr), *(req->intr_data->b_rptr+1));
SCSA2USB_SET_PKT_DO_COMP_STATE(scsa2usbp);
return (USB_SUCCESS);
}
switch (status) {
case CBI_STATUS_PASS:
msg = "PASSED";
if (*(req->intr_data->b_rptr)) {
*(scsa2usbp->scsa2usb_cur_pkt->pkt_scbp) = STATUS_CHECK;
cmd->cmd_done = 1;
}
break;
case CBI_STATUS_FAILED:
case CBI_STATUS_PERSISTENT_FAIL:
msg = (status == CBI_STATUS_PERSISTENT_FAIL) ?
"PERSISTENT_FAILURE" : "FAILED";
*(scsa2usbp->scsa2usb_cur_pkt->pkt_scbp) = STATUS_CHECK;
cmd->cmd_done = 1;
break;
case CBI_STATUS_PHASE_ERR:
msg = "PHASE_ERR";
scsa2usb_cbi_reset_recovery(scsa2usbp);
rval = USB_FAILURE;
break;
}
USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"CBI STATUS = 0x%x %s (0x%x, 0x%x)", status, msg,
*(req->intr_data->b_rptr), *(req->intr_data->b_rptr+1));
SCSA2USB_SET_PKT_DO_COMP_STATE(scsa2usbp);
return (rval);
}
static void
scsa2usb_cbi_reset_recovery(scsa2usb_state_t *scsa2usbp)
{
int i, rval;
mblk_t *cdb;
usb_cr_t completion_reason;
usb_cb_flags_t cb_flags;
USB_DPRINTF_L4(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"scsa2usb_cbi_reset_recovery: (0x%p)", (void *)scsa2usbp);
ASSERT(mutex_owned(&scsa2usbp->scsa2usb_mutex));
if (!(SCSA2USB_DEVICE_ACCESS_OK(scsa2usbp))) {
return;
}
if (scsa2usbp->scsa2usb_cur_pkt) {
scsa2usbp->scsa2usb_cur_pkt->pkt_statistics |= STAT_DEV_RESET;
}
cdb = allocb_wait(CBI_CLASS_CMD_LEN, BPRI_LO, STR_NOSIG, NULL);
*cdb->b_wptr++ = SCMD_SDIAG;
*cdb->b_wptr++ = CBI_SELF_TEST;
for (i = 2; i < CBI_CLASS_CMD_LEN; i++) {
*cdb->b_wptr++ = CBI_CBR_VALUE;
}
scsa2usbp->scsa2usb_pipe_state = SCSA2USB_PIPE_DEV_RESET;
mutex_exit(&scsa2usbp->scsa2usb_mutex);
rval = usb_pipe_sync_ctrl_xfer(scsa2usbp->scsa2usb_dip,
scsa2usbp->scsa2usb_default_pipe,
CBI_REQUEST_TYPE,
0,
CBI_WVALUE,
scsa2usbp->scsa2usb_intfc_num,
CBI_CLASS_CMD_LEN,
&cdb,
0, &completion_reason, &cb_flags, 0);
mutex_enter(&scsa2usbp->scsa2usb_mutex);
USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"\tCBI RESET: rval = %x cr = %x", rval, completion_reason);
if (rval != USB_SUCCESS) {
goto exc_exit;
}
rval = scsa2usb_clear_ept_stall(scsa2usbp,
scsa2usbp->scsa2usb_bulkin_ept.bEndpointAddress,
scsa2usbp->scsa2usb_bulkin_pipe, "bulk-in");
USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"\tclear stall on bulk-in pipe: %d", rval);
if (rval != USB_SUCCESS) {
goto exc_exit;
}
rval = scsa2usb_clear_ept_stall(scsa2usbp,
scsa2usbp->scsa2usb_bulkout_ept.bEndpointAddress,
scsa2usbp->scsa2usb_bulkout_pipe, "bulk-out");
USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"\tclear stall on bulk-out pipe: %d", rval);
if (rval != USB_SUCCESS) {
goto exc_exit;
}
if (SCSA2USB_IS_CBI(scsa2usbp)) {
rval = scsa2usb_clear_ept_stall(scsa2usbp,
scsa2usbp->scsa2usb_intr_ept.bEndpointAddress,
scsa2usbp->scsa2usb_intr_pipe, "intr");
USB_DPRINTF_L3(DPRINT_MASK_SCSA, scsa2usbp->scsa2usb_log_handle,
"\tclear stall on intr pipe: %d", rval);
}
exc_exit:
SCSA2USB_FREE_MSG(cdb);
scsa2usbp->scsa2usb_pipe_state &= ~SCSA2USB_PIPE_DEV_RESET;
}