/* ---------------------------------------------------------------------------- * ATMEL Microcontroller Software Support * ---------------------------------------------------------------------------- * Copyright (c) 2008, Atmel Corporation * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the disclaimer below. * * Atmel's name may not be used to endorse or promote products derived from * this software without specific prior written permission. * * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ---------------------------------------------------------------------------- */ /** \file * \addtogroup usbd_msd *@{ */ /*----------------------------------------------------------------------------- * Includes *-----------------------------------------------------------------------------*/ #include "SBCMethods.h" #include "MSDDStateMachine.h" /*----------------------------------------------------------------------------- * Internal functions *-----------------------------------------------------------------------------*/ /** * Returns the expected transfer length and direction (IN, OUT or don't care) * from the host point-of-view. * \param cbw Pointer to the CBW to examinate * \param pLength Expected length of command * \param pType Expected direction of command */ static void MSDD_GetCommandInformation(MSCbw *cbw, unsigned int *length, unsigned char *type) { /* Expected host transfer direction and length */ (*length) = cbw->dCBWDataTransferLength; if (*length == 0) { (*type) = MSDD_NO_TRANSFER; } else if ((cbw->bmCBWFlags & MSD_CBW_DEVICE_TO_HOST) != 0) { (*type) = MSDD_DEVICE_TO_HOST; } else { (*type) = MSDD_HOST_TO_DEVICE; } } /** * Pre-processes a command by checking the differences between the host and * device expectations in term of transfer type and length. * Once one of the thirteen cases is identified, the actions to do during the * post-processing phase are stored in the dCase variable of the command * state. * \param pMsdDriver Pointer to a MSDDriver instance * \return 1 if the command is supported, false otherwise */ static unsigned char MSDD_PreProcessCommand(MSDDriver *pMsdDriver) { unsigned int hostLength = 0; unsigned int deviceLength = 0; unsigned char hostType; unsigned char deviceType; unsigned char isCommandSupported; MSDCommandState *commandState = &(pMsdDriver->commandState); MSCsw *csw = &(commandState->csw); MSCbw *cbw = &(commandState->cbw); MSDLun *lun = &(pMsdDriver->luns[(unsigned char) cbw->bCBWLUN]); /* Get information about the command */ /* Host-side */ MSDD_GetCommandInformation(cbw, &hostLength, &hostType); /* Device-side */ isCommandSupported = SBC_GetCommandInformation(cbw->pCommand, &deviceLength, &deviceType, lun); /* Initialize data residue and result status */ csw->dCSWDataResidue = 0; csw->bCSWStatus = MSD_CSW_COMMAND_PASSED; /* Check if the command is supported */ if (isCommandSupported) { /* Identify the command case */ if(hostType == MSDD_NO_TRANSFER) { /* Case 1 (Hn = Dn) */ if(deviceType == MSDD_NO_TRANSFER) { /*TRACE_WARNING("Case 1\n\r"); */ commandState->postprocess = 0; commandState->length = 0; } else if(deviceType == MSDD_DEVICE_TO_HOST) { /* Case 2 (Hn < Di) */ TRACE_WARNING( "MSDD_PreProcessCommand: Case 2\n\r"); commandState->postprocess = MSDD_CASE_PHASE_ERROR; commandState->length = 0; } else { /*if(deviceType == MSDD_HOST_TO_DEVICE) { */ /* Case 3 (Hn < Do) */ TRACE_WARNING( "MSDD_PreProcessCommand: Case 3\n\r"); commandState->postprocess = MSDD_CASE_PHASE_ERROR; commandState->length = 0; } } /* Case 4 (Hi > Dn) */ else if(hostType == MSDD_DEVICE_TO_HOST) { if(deviceType == MSDD_NO_TRANSFER) { TRACE_WARNING( "MSDD_PreProcessCommand: Case 4\n\r"); commandState->postprocess = MSDD_CASE_STALL_IN; commandState->length = 0; csw->dCSWDataResidue = hostLength; } else if(deviceType == MSDD_DEVICE_TO_HOST) { if(hostLength > deviceLength) { /* Case 5 (Hi > Di) */ TRACE_WARNING( "MSDD_PreProcessCommand: Case 5\n\r"); commandState->postprocess = MSDD_CASE_STALL_IN; commandState->length = deviceLength; csw->dCSWDataResidue = hostLength - deviceLength; } else if(hostLength == deviceLength) { /* Case 6 (Hi = Di) */ commandState->postprocess = 0; commandState->length = deviceLength; } else { /*if(hostLength < deviceLength) { */ /* Case 7 (Hi < Di) */ TRACE_WARNING( "MSDD_PreProcessCommand: Case 7\n\r"); commandState->postprocess = MSDD_CASE_PHASE_ERROR; commandState->length = hostLength; } } else { /*if(deviceType == MSDD_HOST_TO_DEVICE) { */ /* Case 8 (Hi <> Do) */ TRACE_WARNING( "MSDD_PreProcessCommand: Case 8\n\r"); commandState->postprocess = MSDD_CASE_STALL_IN | MSDD_CASE_PHASE_ERROR; commandState->length = 0; } } else if(hostType == MSDD_HOST_TO_DEVICE) { if(deviceType == MSDD_NO_TRANSFER) { /* Case 9 (Ho > Dn) */ TRACE_WARNING( "MSDD_PreProcessCommand: Case 9\n\r"); commandState->postprocess = MSDD_CASE_STALL_OUT; commandState->length = 0; csw->dCSWDataResidue = hostLength; } else if(deviceType == MSDD_DEVICE_TO_HOST) { /* Case 10 (Ho <> Di) */ TRACE_WARNING( "MSDD_PreProcessCommand: Case 10\n\r"); commandState->postprocess = MSDD_CASE_STALL_OUT | MSDD_CASE_PHASE_ERROR; commandState->length = 0; } else { /*if(deviceType == MSDD_HOST_TO_DEVICE) { */ if(hostLength > deviceLength) { /* Case 11 (Ho > Do) */ TRACE_WARNING( "MSDD_PreProcessCommand: Case 11\n\r"); commandState->postprocess = MSDD_CASE_STALL_OUT; /* commandState->length = deviceLength; */ /* csw->dCSWDataResidue = hostLength - deviceLength; */ commandState->length = 0; csw->dCSWDataResidue = deviceLength; } else if(hostLength == deviceLength) { /* Case 12 (Ho = Do) */ /*TRACE_WARNING( */ /* "MSDD_PreProcessCommand: Case 12\n\r"); */ commandState->postprocess = 0; commandState->length = deviceLength; } else { /*if(hostLength < deviceLength) { */ /* Case 13 (Ho < Do) */ TRACE_WARNING( "MSDD_PreProcessCommand: Case 13\n\r"); commandState->postprocess = MSDD_CASE_PHASE_ERROR; commandState->length = hostLength; } } } } return isCommandSupported; } /** * Post-processes a command given the case identified during the * pre-processing step. * Depending on the case, one of the following actions can be done: * - Bulk IN endpoint is stalled * - Bulk OUT endpoint is stalled * - CSW status set to phase error * \param pMsdDriver Pointer to a MSDDriver instance * \return If the device is halted */ static unsigned char MSDD_PostProcessCommand(MSDDriver *pMsdDriver) { MSDCommandState *commandState = &(pMsdDriver->commandState); MSCsw *csw = &(commandState->csw); unsigned char haltStatus = 0; /* STALL Bulk IN endpoint ? */ if ((commandState->postprocess & MSDD_CASE_STALL_IN) != 0) { TRACE_INFO_WP("StallIn "); //MSDD_Halt(MSDD_CASE_STALL_IN); USBD_Halt(commandState->pipeIN); haltStatus = 1; } /* STALL Bulk OUT endpoint ? */ if ((commandState->postprocess & MSDD_CASE_STALL_OUT) != 0) { TRACE_INFO_WP("StallOut "); //MSDD_Halt(MSDD_CASE_STALL_OUT); USBD_Halt(commandState->pipeOUT); haltStatus = 1; } /* Set CSW status code to phase error ? */ if ((commandState->postprocess & MSDD_CASE_PHASE_ERROR) != 0) { TRACE_INFO_WP("PhaseErr "); csw->bCSWStatus = MSD_CSW_PHASE_ERROR; } return haltStatus; } /** * Processes the latest command received by the %device. * \param pMsdDriver Pointer to a MSDDriver instance * \return 1 if the command has been completed, false otherwise. */ static unsigned char MSDD_ProcessCommand(MSDDriver * pMsdDriver) { unsigned char status; MSDCommandState *commandState = &(pMsdDriver->commandState); MSCbw *cbw = &(commandState->cbw); MSCsw *csw = &(commandState->csw); MSDLun *lun = &(pMsdDriver->luns[(unsigned char) cbw->bCBWLUN]); unsigned char isCommandComplete = 0; /* Check if LUN is valid */ if (cbw->bCBWLUN > pMsdDriver->maxLun) { TRACE_WARNING( "MSDD_ProcessCommand: LUN %d not exist\n\r", cbw->bCBWLUN); status = MSDD_STATUS_ERROR; } else { /* Process command */ if (pMsdDriver->maxLun > 0) { TRACE_INFO_WP("LUN%d ", cbw->bCBWLUN); } status = SBC_ProcessCommand(lun, commandState); } /* Check command result code */ if (status == MSDD_STATUS_PARAMETER) { TRACE_WARNING( "MSDD_ProcessCommand: Unknown cmd 0x%02X\n\r", cbw->pCommand[0]); /* Update sense data */ SBC_UpdateSenseData(&(lun->requestSenseData), SBC_SENSE_KEY_ILLEGAL_REQUEST, SBC_ASC_INVALID_FIELD_IN_CDB, 0); /* Result codes */ csw->bCSWStatus = MSD_CSW_COMMAND_FAILED; isCommandComplete = 1; /* stall the request, IN or OUT */ if (((cbw->bmCBWFlags & MSD_CBW_DEVICE_TO_HOST) == 0) && (cbw->dCBWDataTransferLength > 0)) { /* Stall the OUT endpoint : host to device */ /* MSDD_Halt(MSDD_CASE_STALL_OUT); */ commandState->postprocess = MSDD_CASE_STALL_OUT; TRACE_INFO_WP("StaOUT "); } else { /* Stall the IN endpoint : device to host */ /* MSDD_Halt(MSDD_CASE_STALL_IN); */ commandState->postprocess = MSDD_CASE_STALL_IN; TRACE_INFO_WP("StaIN "); } } else if (status == MSDD_STATUS_ERROR) { TRACE_WARNING("MSD_ProcessCommand: Cmd %x fail\n\r", ((SBCCommand*)commandState->cbw.pCommand)->bOperationCode); /* Update sense data */ SBC_UpdateSenseData(&(lun->requestSenseData), SBC_SENSE_KEY_MEDIUM_ERROR, SBC_ASC_INVALID_FIELD_IN_CDB, 0); /* Result codes */ csw->bCSWStatus = MSD_CSW_COMMAND_FAILED; isCommandComplete = 1; } else if (status == MSDD_STATUS_RW) { csw->bCSWStatus = MSD_CSW_COMMAND_FAILED; isCommandComplete = 1; } else { /* Update sense data */ SBC_UpdateSenseData(&(lun->requestSenseData), SBC_SENSE_KEY_NO_SENSE, 0, 0); /* Is command complete ? */ if (status == MSDD_STATUS_SUCCESS) { isCommandComplete = 1; } } /* Check if command has been completed */ if (isCommandComplete) { TRACE_INFO_WP("Cplt "); /* Adjust data residue */ if (commandState->length != 0) { csw->dCSWDataResidue += commandState->length; /* STALL the endpoint waiting for data */ if ((cbw->bmCBWFlags & MSD_CBW_DEVICE_TO_HOST) == 0) { /* Stall the OUT endpoint : host to device */ /* MSDD_Halt(MSDD_CASE_STALL_OUT); */ commandState->postprocess = MSDD_CASE_STALL_OUT; TRACE_INFO_WP("StaOUT "); } else { /* Stall the IN endpoint : device to host */ /* MSDD_Halt(MSDD_CASE_STALL_IN); */ commandState->postprocess = MSDD_CASE_STALL_IN; TRACE_INFO_WP("StaIN "); } } /* Reset command state */ commandState->state = 0; } return isCommandComplete; } /** * State machine for the MSD %device driver * \param pMsdDriver Pointer to a MSDDriver instance */ void MSDD_StateMachine(MSDDriver * pMsdDriver) { MSDCommandState *commandState = &(pMsdDriver->commandState); MSCbw *cbw = &(commandState->cbw); MSCsw *csw = &(commandState->csw); MSDTransfer *transfer = &(commandState->transfer); unsigned char status; /* Identify current driver state */ switch (pMsdDriver->state) { /*---------------------- */ case MSDD_STATE_READ_CBW: /*---------------------- */ /* Start the CBW read operation */ transfer->semaphore = 0; #if 1 status = USBD_Read(commandState->pipeOUT, cbw, MSD_CBW_SIZE, (TransferCallback) MSDDriver_Callback, (void *) transfer); #else status = MSDD_Read(cbw, MSD_CBW_SIZE, (TransferCallback) MSDDriver_Callback, (void *) transfer); #endif /* Check operation result code */ if (status == USBD_STATUS_SUCCESS) { /* If the command was successful, wait for transfer */ pMsdDriver->state = MSDD_STATE_WAIT_CBW; } break; /*---------------------- */ case MSDD_STATE_WAIT_CBW: /*---------------------- */ /* Check transfer semaphore */ if (transfer->semaphore > 0) { /* Take semaphore and terminate transfer */ transfer->semaphore--; /* Check if transfer was successful */ if (transfer->status == USBD_STATUS_SUCCESS) { TRACE_INFO_WP("------------------------------\n\r"); /* Process received command */ pMsdDriver->state = MSDD_STATE_PROCESS_CBW; } else if (transfer->status == USBD_STATUS_RESET) { TRACE_INFO("MSDD_StateMachine: EP resetted\n\r"); pMsdDriver->state = MSDD_STATE_READ_CBW; } else { TRACE_WARNING( "MSDD_StateMachine: Failed to read CBW\n\r"); pMsdDriver->state = MSDD_STATE_READ_CBW; } } break; /*------------------------- */ case MSDD_STATE_PROCESS_CBW: /*------------------------- */ /* Check if this is a new command */ if (commandState->state == 0) { /* Copy the CBW tag */ csw->dCSWTag = cbw->dCBWTag; /* Check that the CBW is 31 bytes long */ if ((transfer->transferred != MSD_CBW_SIZE) || (transfer->remaining != 0)) { TRACE_WARNING( "MSDD_StateMachine: Invalid CBW (len %d)\n\r", (int)transfer->transferred); /* Wait for a reset recovery */ pMsdDriver->waitResetRecovery = 1; /* Halt the Bulk-IN and Bulk-OUT pipes */ //MSDD_Halt(MSDD_CASE_STALL_OUT | MSDD_CASE_STALL_IN); USBD_Halt(commandState->pipeIN); USBD_Halt(commandState->pipeOUT); csw->bCSWStatus = MSD_CSW_COMMAND_FAILED; pMsdDriver->state = MSDD_STATE_READ_CBW; } /* Check the CBW Signature */ else if (cbw->dCBWSignature != MSD_CBW_SIGNATURE) { TRACE_WARNING( "MSD_BOTStateMachine: Invalid CBW (Bad signature)\n\r"); /* Wait for a reset recovery */ pMsdDriver->waitResetRecovery = 1; /* Halt the Bulk-IN and Bulk-OUT pipes */ //MSDD_Halt(MSDD_CASE_STALL_OUT | MSDD_CASE_STALL_IN); USBD_Halt(commandState->pipeIN); USBD_Halt(commandState->pipeOUT); csw->bCSWStatus = MSD_CSW_COMMAND_FAILED; pMsdDriver->state = MSDD_STATE_READ_CBW; } else { /* Pre-process command */ MSDD_PreProcessCommand(pMsdDriver); } } /* Process command */ if (csw->bCSWStatus == MSDD_STATUS_SUCCESS) { if (MSDD_ProcessCommand(pMsdDriver)) { /* Post-process command if it is finished */ if (MSDD_PostProcessCommand(pMsdDriver)) { TRACE_INFO_WP("WaitHALT "); pMsdDriver->state = MSDD_STATE_WAIT_HALT; } else { pMsdDriver->state = MSDD_STATE_SEND_CSW; } } TRACE_INFO_WP("\n\r"); } break; /*---------------------- */ case MSDD_STATE_SEND_CSW: /*---------------------- */ /* Set signature */ csw->dCSWSignature = MSD_CSW_SIGNATURE; /* Start the CSW write operation */ #if 1 status = USBD_Write(commandState->pipeIN, csw, MSD_CSW_SIZE, (TransferCallback) MSDDriver_Callback, (void *) transfer); #else status = MSDD_Write(csw, MSD_CSW_SIZE, (TransferCallback) MSDDriver_Callback, (void *) transfer); #endif /* Check operation result code */ if (status == USBD_STATUS_SUCCESS) { TRACE_INFO_WP("SendCSW "); /* Wait for end of transfer */ pMsdDriver->state = MSDD_STATE_WAIT_CSW; } break; /*---------------------- */ case MSDD_STATE_WAIT_CSW: /*---------------------- */ /* Check transfer semaphore */ if (transfer->semaphore > 0) { /* Take semaphore and terminate transfer */ transfer->semaphore--; /* Check if transfer was successful */ if (transfer->status == USBD_STATUS_RESET) { TRACE_INFO("MSDD_StateMachine: EP resetted\n\r"); } else if (transfer->status == USBD_STATUS_ABORTED) { TRACE_WARNING( "MSDD_StateMachine: Failed to send CSW\n\r"); } else { TRACE_INFO_WP("ok"); } /* Read new CBW */ pMsdDriver->state = MSDD_STATE_READ_CBW; } break; /*---------------------- */ case MSDD_STATE_WAIT_HALT: /*---------------------- */ //if (MSDD_IsHalted() == 0) { if (!USBD_IsHalted(commandState->pipeIN)) { pMsdDriver->state = MSDD_STATE_SEND_CSW; } break; } } /**@}*/