/* ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /*-* * @file usb_msc.c * @brief USB Mass Storage Class code. * * @addtogroup USB_MSC * @{ */ #include "ch.h" #include "hal.h" #include "usb_msc.h" /*===========================================================================*/ /* Driver exported variables. */ /*===========================================================================*/ /*===========================================================================*/ /* Driver local variables. */ /*===========================================================================*/ /** * @brief Zero-filled constant buffer. */ static const uint8_t zerobuf[4] = {0, 0, 0, 0}; /** * @brief Answer to the INQUIRY command. */ static const uint8_t scsi_inquiry_data[] = { 0x00, /* Direct Access Device. */ 0x80, /* RMB = 1: Removable Medium. */ 0x02, /* ISO, ECMA, ANSI = 2. */ 0x00, /* UFI response format. */ 36 - 4, /* Additional Length. */ 0x00, 0x00, 0x00, /* Vendor Identification */ 'C', 'h', 'i', 'b', 'i', 'O', 'S', ' ', /* Product Identification */ 'S', 'D', ' ', 'F', 'l', 'a', 's', 'h', ' ', 'D', 'i', 's', 'k', ' ', ' ', ' ', /* Product Revision Level */ '1', '.', '0', ' ' }; /** * @brief Generic buffer. */ uint8_t buf[16]; /*===========================================================================*/ /* MMC interface code. */ /*===========================================================================*/ /*===========================================================================*/ /* SCSI emulation code. */ /*===========================================================================*/ static uint8_t scsi_read_format_capacities(uint32_t *nblocks, uint32_t *secsize) { *nblocks = 1024; *secsize = 512; return 3; /* No Media.*/ } /*===========================================================================*/ /* Mass Storage Class related code. */ /*===========================================================================*/ /** * @brief MSC state machine current state. */ static mscstate_t msc_state; /** * @brief Received CBW. */ static msccbw_t CBW; /** * @brief CSW to be transmitted. */ static msccsw_t CSW; /** * @brief MSC state machine initialization. * * @param[in] usbp pointer to the @p USBDriver object */ static void msc_reset(USBDriver *usbp) { msc_state = MSC_IDLE; chSysLockFromIsr(); usbStartReceiveI(usbp, MSC_DATA_OUT_EP, (uint8_t *)&CBW, sizeof CBW); chSysUnlockFromIsr(); } static void msc_transmit(USBDriver *usbp, const uint8_t *p, size_t n) { if (n > CBW.dCBWDataTransferLength) n = CBW.dCBWDataTransferLength; CSW.dCSWDataResidue = CBW.dCBWDataTransferLength - (uint32_t)n; chSysLockFromIsr(); usbStartTransmitI(usbp, MSC_DATA_IN_EP, p, n); chSysUnlockFromIsr(); } static void msc_sendstatus(USBDriver *usbp) { msc_state = MSC_SENDING_CSW; chSysLockFromIsr(); usbStartTransmitI(usbp, MSC_DATA_IN_EP, (uint8_t *)&CSW, sizeof CSW); chSysUnlockFromIsr(); } static bool_t msc_decode(USBDriver *usbp) { uint32_t nblocks, secsize; switch (CBW.CBWCB[0]) { case SCSI_REQUEST_SENSE: break; case SCSI_INQUIRY: msc_transmit(usbp, (uint8_t *)&scsi_inquiry_data, sizeof scsi_inquiry_data); CSW.bCSWStatus = MSC_CSW_STATUS_PASSED; break; case SCSI_READ_FORMAT_CAPACITIES: buf[8] = scsi_read_format_capacities(&nblocks, &secsize); buf[0] = buf[1] = buf[2] = 0; buf[3] = 8; buf[4] = (uint8_t)(nblocks >> 24); buf[5] = (uint8_t)(nblocks >> 16); buf[6] = (uint8_t)(nblocks >> 8); buf[7] = (uint8_t)(nblocks >> 0); buf[9] = (uint8_t)(secsize >> 16); buf[10] = (uint8_t)(secsize >> 8); buf[11] = (uint8_t)(secsize >> 0); msc_transmit(usbp, buf, 12); CSW.bCSWStatus = MSC_CSW_STATUS_PASSED; break; default: return TRUE; } return FALSE; } /*===========================================================================*/ /* Driver exported functions. */ /*===========================================================================*/ /** * @brief Default requests hook. * @details The application must use this function as callback for the * messages hook. * The following requests are emulated: * - MSC_GET_MAX_LUN_COMMAND. * - MSC_MASS_STORAGE_RESET_COMMAND. * . * * @param[in] usbp pointer to the @p USBDriver object * @return The hook status. * @retval TRUE Message handled internally. * @retval FALSE Message not handled. */ bool_t mscRequestsHook(USBDriver *usbp) { if ((usbp->setup[0] & (USB_RTYPE_TYPE_MASK | USB_RTYPE_RECIPIENT_MASK)) == (USB_RTYPE_TYPE_CLASS | USB_RTYPE_RECIPIENT_INTERFACE)) { switch (usbp->setup[1]) { case MSC_GET_MAX_LUN_COMMAND: usbSetupTransfer(usbp, (uint8_t *)zerobuf, 1, NULL); return TRUE; case MSC_MASS_STORAGE_RESET_COMMAND: msc_reset(usbp); usbSetupTransfer(usbp, NULL, 0, NULL); return TRUE; default: return FALSE; } } return FALSE; } /** * @brief Default data transmitted callback. * @details The application must use this function as callback for the IN * data endpoint. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number */ void mscDataTransmitted(USBDriver *usbp, usbep_t ep) { switch (msc_state) { case MSC_DATA_IN: CSW.dCSWSignature = MSC_CSW_SIGNATURE; CSW.dCSWTag = CBW.dCBWTag; chSysLockFromIsr(); usbStartTransmitI(usbp, ep, (uint8_t *)&CSW, sizeof CSW); chSysUnlockFromIsr(); msc_state = MSC_SENDING_CSW; break; case MSC_SENDING_CSW: chSysLockFromIsr(); usbStartReceiveI(usbp, MSC_DATA_OUT_EP, (uint8_t *)&CBW, sizeof CBW); chSysUnlockFromIsr(); msc_state = MSC_IDLE; break; default: ; } } /** * @brief Default data received callback. * @details The application must use this function as callback for the OUT * data endpoint. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number */ void mscDataReceived(USBDriver *usbp, usbep_t ep) { size_t n; n = usbGetReceiveTransactionSizeI(usbp, ep); switch (msc_state) { case MSC_IDLE: if ((n != sizeof(msccbw_t)) || (CBW.dCBWSignature != MSC_CBW_SIGNATURE)) goto stall_out; /* 6.6.1 */ /* Decoding SCSI command.*/ if (msc_decode(usbp)) { if (CBW.dCBWDataTransferLength == 0) { CSW.bCSWStatus = MSC_CSW_STATUS_FAILED; CSW.dCSWDataResidue = 0; msc_sendstatus(usbp); return; } goto stall_both; } /* Commands with zero transfer length, 5.1.*/ if (CBW.dCBWDataTransferLength == 0) { msc_sendstatus(usbp); return; } /* Transfer direction.*/ if (CBW.bmCBWFlags & 0x80) { /* IN, Device to Host.*/ msc_state = MSC_DATA_IN; } else { /* OUT, Host to Device.*/ msc_state = MSC_DATA_OUT; } break; case MSC_DATA_OUT: break; default: ; } return; stall_out: msc_state = MSC_ERROR; chSysLockFromIsr(); usbStallReceiveI(usbp, ep); chSysUnlockFromIsr(); return; stall_both: msc_state = MSC_ERROR; chSysLockFromIsr(); usbStallTransmitI(usbp, ep); usbStallReceiveI(usbp, ep); chSysUnlockFromIsr(); return; } /** @} */