/* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * scsa2usb_ms_cbi.c: * * This file implements USB Mass Storage Class * Control Bulk Interrupt (CB/CBI) transport v1.0 * http://www.usb.org/developers/data/devclass/usbmass-cbi10.pdf */ #include #include #include /* needed by scsa2usb.h */ #include #include #include #include #include #include /* * Function Prototypes */ 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 functions */ 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 /* debugging information */ extern void scsa2usb_print_cdb(scsa2usb_state_t *, scsa2usb_cmd_t *); #endif /* DEBUG */ /* * scsa2usb_cbi_transport: * Implements the CB/CBI state machine by these steps: * a) Issues command to the device over control pipe. * b) Start Data Phase if applicable * c) Start Status Phase * * returns TRAN_* values and not USB_SUCCESS/FAILURE * * scsa2usb_cbi_transport() handles the normal transitions or * continuation after clearing stalls or error recovery. * * Command Phase: * prepare a valid command and transport it on default pipe * if error on default-pipe: * set pkt_reason to CMD_TRAN_ERR * new pkt state is SCSA2USB_PKT_DO_COMP * do 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 * do reset recovery synchronously * else (no error) * receive status * * data: (out) * if stall on bulkout pipe * terminate data transfers * 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 * do reset recovery synchronously * else (no error) * receive status * * Status Phase: (on Interrupt pipe for CBI devices only) * if error * if stall * new pkt state is SCSA2USB_PKT_DO_COMP * clear stall on interrupt pipe * else * set pkt_reason to CMD_TRAN_ERR * new pkt state is SCSA2USB_PKT_DO_COMP * do reset recovery synchronously * else (no error) * goto read status * * read status: * if not OK or phase error * new pkt state is SCSA2USB_PKT_DO_COMP * set pkt reason CMD_TRAN_ERR * reset recovery synchronously * else (status ok) * 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 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); } /* * Start command phase (C - in CBI) */ data = allocb_wait(CBI_CLASS_CMD_LEN, BPRI_LO, STR_NOSIG, NULL); /* Initialize the data */ for (i = 0; i < CBI_CLASS_CMD_LEN; i++) { *data->b_wptr++ = cmd->cmd_cdb[i]; } SCSA2USB_PRINT_CDB(scsa2usbp, cmd); /* print the CDB */ /* Send the Command to the device */ mutex_exit(&scsa2usbp->scsa2usb_mutex); rval = usb_pipe_sync_ctrl_xfer(scsa2usbp->scsa2usb_dip, scsa2usbp->scsa2usb_default_pipe, CBI_REQUEST_TYPE, /* bmRequestType */ 0, /* bRequest */ CBI_WVALUE, /* wValue */ scsa2usbp->scsa2usb_intfc_num, /* wIndex */ CBI_CLASS_CMD_LEN, /* wLength */ &data, /* data */ USB_ATTRS_PIPE_RESET, /* attributes */ &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); /* get rid of the data */ if (rval != USB_SUCCESS) { scsa2usb_cbi_handle_error(scsa2usbp, rval, completion_reason); return (TRAN_FATAL_ERROR); } /* * Xferred command to the device. * Start data phase (B - in CBI) */ /* * we've not transferred any data yet; updated in * scsa2usb_handle_data_done */ cmd->cmd_resid_xfercount = 0; /* if data to be xferred ? */ if (cmd->cmd_xfercount) { /* Initialize a bulk_req_t */ data_req = scsa2usb_init_bulk_req(scsa2usbp, 0, cmd->cmd_timeout, USB_ATTRS_PIPE_RESET, USB_FLAGS_SLEEP); /* start I/O to/from the device */ rval = scsa2usb_handle_data_start(scsa2usbp, cmd, data_req); /* handle data returned */ scsa2usb_handle_data_done(scsa2usbp, cmd, data_req); if (rval != USB_SUCCESS) { /* * we ran into an error and it wasn't a STALL */ 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); /* get rid of req */ SCSA2USB_FREE_BULK_REQ(data_req); return (TRAN_FATAL_ERROR); } } SCSA2USB_FREE_BULK_REQ(data_req); /* get rid of bulk_req */ } /* CB devices don't do status over interrupt pipe */ 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; } /* * Start status phase (I - in CBI) */ /* Get Status over interrupt pipe */ if ((status_req = scsa2usb_cbi_start_intr_polling(scsa2usbp)) == NULL) { return (TRAN_FATAL_ERROR); /* lack of better return code */ } rval = scsa2usb_handle_cbi_status(status_req); usb_free_intr_req(status_req); /* stop interrupt pipe polling (CBI only) */ if (SCSA2USB_IS_CBI(scsa2usbp)) { scsa2usb_cbi_stop_intr_polling(scsa2usbp); } end_it: if ((rval == USB_SUCCESS) && /* CSW was ok */ (scsa2usbp->scsa2usb_cur_pkt->pkt_reason == CMD_CMPLT) && (cmd->cmd_xfercount != 0) && /* more data to xfer */ !cmd->cmd_done) { /* we aren't done yet */ 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); } /* * scsa2usb_cbi_handle_error: * handle errors from transport that are not STALL conditions */ 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); /* do reset error recovery */ 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; /* scsi_poll relies on this */ pkt->pkt_state = STATE_GOT_BUS; } break; default: if (pkt) { pkt->pkt_reason = CMD_TRAN_ERR; } scsa2usb_cbi_reset_recovery(scsa2usbp); } } /* * scsa2usb_cbi_start_intr_polling: * start polling asynchronously without notification */ 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; /* arbitrary large for now */ 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); /* clear stall */ if (req->intr_completion_reason == USB_CR_STALL) { (void) scsa2usb_clear_ept_stall(scsa2usbp, scsa2usbp->scsa2usb_intr_ept.bEndpointAddress, scsa2usbp->scsa2usb_intr_pipe, "intr"); } /* handle other errors here */ 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); } /* * scsa2usb_cbi_stop_intr_polling: * Stop polling on interrupt pipe (for status) */ 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); } } /* * scsa2usb_handle_cbi_status: * Handle CBI status results */ 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; /* * CBI status contains ASC and ASCQ. * SCMD_REQUEST_SENSE and SCMD_INQUIRY don't affect the sense data * on CBI devices. So, we can ignore that info for these 2 commands. * * (See details in UFI spec section 3.5 - that says that INQUIRY, * SEND_DIAG, and REQUEST_SENSE ought to be supported by any deivce * irrespective). */ 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"; /* non-zero command completion interrupt */ 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)); /* we are done and ready to callback */ SCSA2USB_SET_PKT_DO_COMP_STATE(scsa2usbp); return (rval); } /* * scsa2usb_cbi_reset_recovery: * Reset the USB device in case of errors. */ 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; } /* Allocate an mblk for CBR */ cdb = allocb_wait(CBI_CLASS_CMD_LEN, BPRI_LO, STR_NOSIG, NULL); *cdb->b_wptr++ = SCMD_SDIAG; /* Set it to DIAG */ *cdb->b_wptr++ = CBI_SELF_TEST; /* Set it to reset */ for (i = 2; i < CBI_CLASS_CMD_LEN; i++) { *cdb->b_wptr++ = CBI_CBR_VALUE; /* Set it to 0xff */ } scsa2usbp->scsa2usb_pipe_state = SCSA2USB_PIPE_DEV_RESET; /* * Send a Reset request to the device */ mutex_exit(&scsa2usbp->scsa2usb_mutex); rval = usb_pipe_sync_ctrl_xfer(scsa2usbp->scsa2usb_dip, scsa2usbp->scsa2usb_default_pipe, CBI_REQUEST_TYPE, /* bmRequestType */ 0, /* bRequest */ CBI_WVALUE, /* wValue */ scsa2usbp->scsa2usb_intfc_num, /* wIndex address */ CBI_CLASS_CMD_LEN, /* wLength */ &cdb, /* data to be sent */ 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; } /* reset and clear STALL on bulk-in pipe */ 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; } /* reset and clear STALL on bulk-out pipe */ 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; } /* reset and clear STALL on interrupt pipe */ 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); /* Free the data */ scsa2usbp->scsa2usb_pipe_state &= ~SCSA2USB_PIPE_DEV_RESET; }