diff --git a/firmware/hw_layer/mass_storage/Readme.md b/firmware/hw_layer/mass_storage/Readme.md new file mode 100644 index 0000000000..4a1c314432 --- /dev/null +++ b/firmware/hw_layer/mass_storage/Readme.md @@ -0,0 +1,40 @@ +https://github.com/tegesoft/Chibios-USB-Devices/ +http://www.chibios.com/forum/viewtopic.php?f=3&t=619 + +Mass Storage Device +=================== + +This driver implements a USB mass storage device. It requires a Chibios block device (e.g mmc_spi or SDC) + +Example usage: +-------------- +```c + +USBMassStorageDriver UMSD1; + +mmcObjectInit(&MMCD1); +mmcStart(&MMCD1, &mmccfg); +mmcConnect(&MMCD1); + +msdInit(&USBD1, &MMCD1, &UMSD1); +``` + +Events: +-------------- +```c +chEvtRegisterMask(&UMSD1.evt_connected, &listener_connected, 1); +chEvtRegisterMask(&UMSD1.evt_ejected, &listener_ejected, 2); + +while(TRUE) { + if(chEvtWaitOneTimeout(1, TIME_IMMEDIATE)) { + /* drive is now connected */ + + /* wait until the drive is ejected */ + chEvtWaitOne(2); + + /* drive is now ejected. do something */ + } + + chThdSleepMilliseconds(1000); +} +``` \ No newline at end of file diff --git a/firmware/hw_layer/mass_storage/usb_msd.c b/firmware/hw_layer/mass_storage/usb_msd.c new file mode 100644 index 0000000000..c50dbd0120 --- /dev/null +++ b/firmware/hw_layer/mass_storage/usb_msd.c @@ -0,0 +1,1412 @@ +/* + ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010, + 2011,2012,2013 Giovanni Di Sirio. + + This file is part of ChibiOS/RT. + + ChibiOS/RT is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS/RT is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * @file usb_msd.c + * @brief USB Mass Storage Driver code. + * + * @addtogroup MSD_USB + * @{ + */ + +#define HAL_USE_MASS_STORAGE_USB true + +#include "ch.h" +#include "hal.h" +#include "usb_msd.h" +#include "chprintf.h" +#include "string.h" + + +#if HAL_USE_MASS_STORAGE_USB || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ + +#define MSD_ENABLE_PERF_DEBUG_GPIOS FALSE +#define MSD_DEBUG_NESTING FALSE +#define MSD_DEBUG FALSE +//#define MSD_DEBUG (palReadPad(GPIOI, GPIOI_PIN4)) + +#define msd_debug_print(chp_arg, args ...) if (MSD_DEBUG && chp_arg != NULL ) { chprintf(chp_arg, args); } +#define msd_debug_nest_print(chp_arg, args ...) if ( MSD_DEBUG_NESTING && chp_arg != NULL ) { chprintf(chp_arg, args); } +#define msd_debug_err_print(chp_arg, args ...) if ( chp_arg != NULL ) { chprintf(chp_arg, args); } + + + + + +//#define MSD_DEBUG_SCSI_COMMAND_STATE_STRING(description) g_debug_info.msd.scsi_command_state = description; + +#if !defined(MSD_RW_LED_ON) +#define MSD_RW_LED_ON() +#endif + +#if !defined(MSD_RW_LED_OFF) +#define MSD_RW_LED_OFF() +#endif + +#if !defined(MSD_R_LED_ON) +#define MSD_R_LED_ON() +#endif + +#if !defined(MSD_R_LED_OFF) +#define MSD_R_LED_OFF() +#endif + +#if !defined(MSD_W_LED_ON) +#define MSD_W_LED_ON() +#endif + +#if !defined(MSD_W_LED_OFF) +#define MSD_W_LED_OFF() +#endif + + + +/*===========================================================================*/ +/* Driver exported variables. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver local variables and types. */ +/*===========================================================================*/ + + + +static msg_t MassStorageUSBTransferThd(void *arg); +static msg_t MassStorageThd(void *arg); + +static Thread *msdThd = NULL; +static Thread *msdUSBTransferThd = NULL; + +#define WAIT_ISR_SUCCESS 0 +#define WAIT_ISR_BUSS_RESET_OR_RECONNECT 1 + +static uint8_t msdWaitForISR(USBMassStorageDriver *msdp, const bool_t check_reset, const msd_wait_mode_t wait_mode); +static void msdSetDefaultSenseKey(USBMassStorageDriver *msdp); + +#define BLOCK_SIZE_INCREMENT 512 +#define BLOCK_WRITE_ITTERATION_COUNT 16 + +#define MSD_START_TRANSMIT(msdp) \ + chSysLock(); \ + msdp->bulk_in_interupt_flag = false; \ + usbStartTransmitI(msdp->usbp, msdp->ms_ep_number); \ + chSysUnlock(); + + +#define MSD_START_RECEIVED(msdp) \ + chSysLock(); \ + msdp->bulk_out_interupt_flag = false; \ + usbStartReceiveI(msdp->usbp, msdp->ms_ep_number); \ + chSysUnlock(); + +typedef enum { + MSD_USB_TRANSFER_STATUS_RUNNING = 0, + MSD_USB_TRANSFER_STATUS_DONE_SUCCESSFUL, + MSD_USB_TRANSFER_STATUS_DONE_FAILED, +} msd_usb_transfer_status_t; + +typedef struct { + //uint8_t is_transfer_done; + msd_usb_transfer_status_t transfer_status; + /*Number of blocks actually read from USB IN endpoint that should be written to SD card*/ + int num_blocks_to_write; + /*Number of blocks to read from USB IN endpoint*/ + int max_blocks_to_read; + uint8_t buf[(BLOCK_SIZE_INCREMENT * BLOCK_WRITE_ITTERATION_COUNT)]; +} rw_usb_sd_buffer_t; + +static volatile rw_usb_sd_buffer_t rw_ping_pong_buffer[2]; +static uint8_t read_buffer[2][BLOCK_SIZE_INCREMENT]; + + +inline uint32_t swap_uint32( uint32_t val ) { + val = ((val << 8) & 0xFF00FF00 ) | ((val >> 8) & 0xFF00FF ); + return ((val << 16) & 0xFFFF0000) | ((val >> 16) & 0x0000FFFF); +} + +#define swap_uint16(x) ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8)) + +inline uint32_t swap_4byte_buffer(uint8_t *buff) { + //Note: this is specifically to avoid pointer aliasing and de-referencing words on non-word boundaries + uint32_t temp = 0; + memcpy(&temp, buff, sizeof(temp)); + return(swap_uint32(temp)); +} + +inline uint16_t swap_2byte_buffer(uint8_t *buff) { + //Note: this is specifically to avoid pointer aliasing and de-referencing words on non-half-word boundaries + uint16_t temp = 0; + memcpy(&temp, buff, sizeof(temp)); + return(swap_uint16(temp)); +} + +/** + * @brief USB Event handler calback + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep USB Endpoint Number + * + * @api + */ +void msdBulkInCallbackComplete(USBDriver *usbp, usbep_t ep) { + (void)usbp; + (void)ep; + + if (ep > 0 && usbp->in_params[ep - 1] != NULL) { + USBMassStorageDriver *msdp = (USBMassStorageDriver *)usbp->in_params[ep - 1]; + + chSysLockFromIsr(); + chBSemSignalI(&(msdp->bsem)); + + msdp->bulk_in_interupt_flag = true; + + + chSysUnlockFromIsr(); + } +} + +/** + * @brief USB Event handler calback + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep USB Endpoint Number + * + * @api + */ +void msdBulkOutCallbackComplete(USBDriver *usbp, usbep_t ep) { + (void)usbp; + (void)ep; + + if (ep > 0 && usbp->in_params[ep - 1] != NULL) { + USBMassStorageDriver *msdp = (USBMassStorageDriver *)usbp->in_params[ep - 1]; + + chSysLockFromIsr(); + chBSemSignalI(&(msdp->bsem)); + + msdp->bulk_out_interupt_flag = true; + + chSysUnlockFromIsr(); + } +} + + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + + +/** + * @brief This function will initialize a USBMassStorageDriver structure. + * + * @pre Upon entry, the BlockDevice state should be BLK_READY. If it's not BLK_READY, then this will + * wait untile it becomes BLK_READY. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] bbdp pointer to the @p BaseBlockDevice object, such as an SDCDriver object + * @param[in] msdp pointer to the @p USBMassStorageDriver object + * @param[in] ms_ep_number USB Endpoint Number to be used by the mass storage endpoint + * + * @init + */ +usb_msd_driver_state_t msdInit(USBDriver *usbp, BaseBlockDevice *bbdp, USBMassStorageDriver *msdp, + const usbep_t ms_ep_number, const uint16_t msd_interface_number) { + + msdp->usbp = usbp; + msdp->driver_state = USB_MSD_DRIVER_OK; + msdp->state = MSD_STATE_IDLE; + msdp->trigger_transfer_index = UINT32_MAX; + msdp->bbdp = bbdp; + msdp->ms_ep_number = ms_ep_number; + msdp->msd_interface_number = msd_interface_number; + msdp->chp = NULL; + msdp->enable_media_removial = true; + msdp->block_dev_info_valid_flag = false; + + chEvtInit(&msdp->evt_connected); + chEvtInit(&msdp->evt_ejected); + + /* Initialize binary semaphore as taken, will cause the thread to initially + * wait on the */ + chBSemInit(&msdp->bsem, TRUE); + /* Initialize binary semaphore as NOT taken */ + chBSemInit(&msdp->usb_transfer_thread_bsem, FALSE); + chBSemInit(&msdp->mass_sorage_thd_bsem, FALSE); + + /* Initialize sense structure to zero */ + memset(&msdp->sense, 0, sizeof(msdp->sense)); + + /* Response code = 0x70, additional sense length = 0x0A */ + msdp->sense.byte[0] = 0x70;//FIXME use #define, what is this??? + msdp->sense.byte[7] = 0x0A;//FIXME use #define, what is this??? + + /* make sure block device is working and get info */ + msdSetDefaultSenseKey(msdp); + + const uint32_t sleep_ms = 50; + uint32_t t; + for(t = 0; t <= 250 && (blkGetDriverState(bbdp) != BLK_READY); t += sleep_ms ) { + chThdSleepMilliseconds(sleep_ms); + } + + if( blkGetDriverState(bbdp) == BLK_READY ) { + blkGetInfo(bbdp, &msdp->block_dev_info); + msdp->block_dev_info_valid_flag = true; + } else { + //msdp->driver_state = USB_MSD_DRIVER_ERROR_BLK_DEV_NOT_READY; + } + + usbp->in_params[ms_ep_number - 1] = (void *)msdp; + + return(msdp->driver_state); +} + +/** + * @brief Starts data handling threads for USB mass storage driver + * + * @param[in] msdp pointer to the @p USBMassStorageDriver object + * + * @api + */ +usb_msd_driver_state_t msdStart(USBMassStorageDriver *msdp) { + /*upon entry, USB bus should be disconnected*/ + + if (msdThd == NULL ) { + msdThd = chThdCreateStatic(msdp->waMassStorage, sizeof(msdp->waMassStorage), NORMALPRIO, + MassStorageThd, msdp); + } + + if (msdUSBTransferThd == NULL ) { + msdUSBTransferThd = chThdCreateStatic(msdp->waMassStorageUSBTransfer, + sizeof(msdp->waMassStorageUSBTransfer), + NORMALPRIO, MassStorageUSBTransferThd, + msdp); + } + + return(msdp->driver_state); +} + +usb_msd_driver_state_t msdStop(USBMassStorageDriver *msdp) { + usb_msd_driver_state_t final_state = USB_MSD_DRIVER_STOPPED; + + if (msdThd != NULL) { + chThdTerminate(msdThd); + int i; + for(i = 0; i < 20 && msdThd->p_state != THD_STATE_FINAL; i++ ) { + chThdSleepMilliseconds(20); + } + + if( msdThd->p_state == THD_STATE_FINAL ) { + final_state = USB_MSD_DRIVER_ERROR; + } + msdThd = NULL; + } + + + if (msdUSBTransferThd == NULL) { + chThdTerminate(msdUSBTransferThd); + int i; + for(i = 0; i < 20 && msdUSBTransferThd->p_state != THD_STATE_FINAL; i++ ) { + chThdSleepMilliseconds(20); + } + + if( msdUSBTransferThd->p_state == THD_STATE_FINAL ) { + final_state = USB_MSD_DRIVER_ERROR; + } + msdUSBTransferThd = NULL; + } + + + msdp->driver_state = final_state; + return(msdp->driver_state); +} + + +/** + * @brief Default requests hook. + * + * @param[in] usbp pointer to the @p USBDriver object + * @return The hook status. + * @retval TRUE Message handled internally. + * @retval FALSE Message not handled. + * + * @api + */ +bool_t msdRequestsHook(USBDriver *usbp) { + return(msdRequestsHook2(usbp, NULL)); +} + +/** + * @brief Alternate request hook, useful for USB composite devices + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] msdp pointer to the @p USBMassStorageDriver object + * @return The hook status. + * @retval TRUE Message handled internally. + * @retval FALSE Message not handled. + * + * @api + */ +bool_t msdRequestsHook2(USBDriver *usbp, USBMassStorageDriver *msdp) { + if (((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) + && ((usbp->setup[0] & USB_RTYPE_RECIPIENT_MASK) + == USB_RTYPE_RECIPIENT_INTERFACE)) { + /* check that the request is for the MSD interface number.*/ + if( msdp != NULL ) { + if (MSD_SETUP_INDEX(usbp->setup) != msdp->msd_interface_number) + return FALSE; + } else if (MSD_SETUP_INDEX(usbp->setup) != 0 ) { + return FALSE; + } + + /* act depending on bRequest = setup[1] */ + switch (usbp->setup[1]) { + case MSD_REQ_RESET: + /* check that it is a HOST2DEV request */ + if (((usbp->setup[0] & USB_RTYPE_DIR_MASK) != USB_RTYPE_DIR_HOST2DEV) + || (MSD_SETUP_LENGTH(usbp->setup) != 0) + || (MSD_SETUP_VALUE(usbp->setup) != 0)) + return FALSE; + + /* reset all endpoints */ + /* TODO!*/ + /* The device shall NAK the status stage of the device request until + * the Bulk-Only Mass Storage Reset is complete. + */ + return TRUE; + case MSD_GET_MAX_LUN: + /* check that it is a DEV2HOST request */ + if (((usbp->setup[0] & USB_RTYPE_DIR_MASK) != USB_RTYPE_DIR_DEV2HOST) + || (MSD_SETUP_LENGTH(usbp->setup) != 1) + || (MSD_SETUP_VALUE(usbp->setup) != 0)) + return FALSE; + + //static uint8_t len_buf[1] = {0}; + msdp->data.max_lun_len_buf[0] = 0; + usbSetupTransfer(usbp, msdp->data.max_lun_len_buf, 1, NULL); + return TRUE; + default: + return FALSE; + break; + } + } + return FALSE; +} + + +const char* usb_msd_driver_state_t_to_str(const usb_msd_driver_state_t driver_state) { + switch (driver_state) { + case USB_MSD_DRIVER_UNINITIALIZED: + return ("USB_MSD_DRIVER_UNINITIALIZED"); + case USB_MSD_DRIVER_ERROR: + return ("USB_MSD_DRIVER_ERROR"); + case USB_MSD_DRIVER_OK: + return ("USB_MSD_DRIVER_OK"); + case USB_MSD_DRIVER_STOPPED: + return("USB_MSD_DRIVER_STOPPED"); + case USB_MSD_DRIVER_ERROR_BLK_DEV_NOT_READY: + return ("USB_MSD_DRIVER_ERROR_BLK_DEV_NOT_READY"); + } + return ("USB_MSD_DRIVER_UNKNOWN"); +} + + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + + +/* Event Flow Functions */ + + +static uint8_t msdWaitForISR(USBMassStorageDriver *msdp, const bool_t check_reset, const msd_wait_mode_t wait_mode) { + uint8_t ret = WAIT_ISR_SUCCESS; + /* sleep until the ISR completes */ + chSysLock(); + msd_debug_print(msdp->chp, "WaitISR(mode=%d)\r\n", wait_mode); + for (;;) { + const msg_t m = chBSemWaitTimeoutS(&msdp->bsem, 1); + if (m == RDY_OK && wait_mode == MSD_WAIT_MODE_NONE ) { + break; + } + + if( wait_mode == MSD_WAIT_MODE_BULK_IN && msdp->bulk_in_interupt_flag ) { + break; + } else if( wait_mode == MSD_WAIT_MODE_BULK_OUT && msdp->bulk_out_interupt_flag ) { + break; + } + + if (check_reset && msdp->reconfigured_or_reset_event) { + ret = WAIT_ISR_BUSS_RESET_OR_RECONNECT; + break; + } + + if( chThdShouldTerminate() ) { + break; + } + } + chSysUnlock(); + return (ret); +} + + +static void WaitForUSBTransferComplete(USBMassStorageDriver *msdp, + const int ping_pong_buffer_index) { + msd_debug_nest_print(msdp->chp, "A"); + while (TRUE) { + chBSemWaitTimeout(&msdp->mass_sorage_thd_bsem, MS2ST(1)); + + if (rw_ping_pong_buffer[ping_pong_buffer_index].transfer_status != MSD_USB_TRANSFER_STATUS_RUNNING) { + break; + } else { + //chThdSleepMilliseconds(1); + } + } + msd_debug_nest_print(msdp->chp, "a"); +} + + + +/* SCSI Functions */ + +static inline void SCSISetSense(USBMassStorageDriver *msdp, uint8_t key, + uint8_t acode, uint8_t aqual) { + msdp->sense.byte[2] = key; + msdp->sense.byte[12] = acode; + msdp->sense.byte[13] = aqual; +} + + +static void msdSetDefaultSenseKey(USBMassStorageDriver *msdp) { + SCSISetSense(msdp, SCSI_SENSE_KEY_GOOD, + SCSI_ASENSE_NO_ADDITIONAL_INFORMATION, + SCSI_ASENSEQ_NO_QUALIFIER); +} + + +#ifndef USB_MASS_STORAGE_INQUIRY_RESPONSE_STRING_0 +# define USB_MASS_STORAGE_INQUIRY_RESPONSE_STRING_0 "Chibios" +#endif + +#ifndef USB_MASS_STORAGE_INQUIRY_RESPONSE_STRING_1 +# define USB_MASS_STORAGE_INQUIRY_RESPONSE_STRING_1 "Mass Storage" +#endif + +#ifndef USB_MASS_STORAGE_INQUIRY_RESPONSE_STRING_2 +# define USB_MASS_STORAGE_INQUIRY_RESPONSE_STRING_2 {'v', CH_KERNEL_MAJOR + '0', '.', CH_KERNEL_MINOR + '0'} +#endif + +static const scsi_inquiry_response_t default_scsi_inquiry_response = + {0x00, /* peripheral, direct access block device */ + 0x80, /* removable */ + 0x04, /* version, SPC-2 */ + 0x02, /* response data format */ + 0x20, /* additional_length, response has 0x20 + 4 bytes */ + 0x00, /* sccstp*/ + 0x00, /* bqueetc*/ + 0x00, /* cmdqueue*/ + USB_MASS_STORAGE_INQUIRY_RESPONSE_STRING_0, + USB_MASS_STORAGE_INQUIRY_RESPONSE_STRING_1, + USB_MASS_STORAGE_INQUIRY_RESPONSE_STRING_2, + }; + + +static msd_wait_mode_t SCSICommandInquiry(USBMassStorageDriver *msdp) { + msd_cbw_t *cbw = &(msdp->cbw); + msdp->data.scsi_inquiry_response = default_scsi_inquiry_response; + + if ((cbw->scsi_cmd_data[1] & ((1 << 0) | (1 << 1))) || cbw->scsi_cmd_data[2]) { + /* Optional but unsupported bits set - update the SENSE key and fail + * the request */ + msd_debug_err_print(msdp->chp, " INQ ERR 0x%X 0x%X\r\n", cbw->scsi_cmd_data[1], cbw->scsi_cmd_data[2]); + SCSISetSense(msdp, SCSI_SENSE_KEY_ILLEGAL_REQUEST, + SCSI_ASENSE_INVALID_FIELD_IN_CDB, SCSI_ASENSEQ_NO_QUALIFIER); + + //we should indicate that the command failed to the host, but still return data + msdp->command_succeeded_flag = false; + } + + usbPrepareTransmit(msdp->usbp, msdp->ms_ep_number, (uint8_t *)&msdp->data.scsi_inquiry_response, + sizeof(scsi_inquiry_response_t)); + + + MSD_START_TRANSMIT(msdp); + + /* wait for ISR */ + return MSD_WAIT_MODE_BULK_IN; +} + +static msd_wait_mode_t SCSICommandRequestSense(USBMassStorageDriver *msdp) { + //This command should not affect the sense key + usbPrepareTransmit(msdp->usbp, msdp->ms_ep_number, (uint8_t *)&msdp->sense, + sizeof(scsi_sense_response_t)); + + MSD_START_TRANSMIT(msdp); + + /* wait for ISR */ + return MSD_WAIT_MODE_BULK_IN; +} + +static msd_wait_mode_t SCSICommandReadFormatCapacity(USBMassStorageDriver *msdp) { + msdSetDefaultSenseKey(msdp); + + const uint32_t formated_capactiy_descriptor_code = 0x02;//see usbmass-ufi10.doc for codes + + msdp->data.format_capacity_response.payload_byte_length[3] = 8; + msdp->data.format_capacity_response.last_block_addr = swap_uint32(msdp->block_dev_info.blk_num - 1); + msdp->data.format_capacity_response.block_size = swap_uint32(msdp->block_dev_info.blk_size) | formated_capactiy_descriptor_code; + + usbPrepareTransmit(msdp->usbp, msdp->ms_ep_number, (uint8_t *)&msdp->data.format_capacity_response, + sizeof(msdp->data.format_capacity_response)); + + MSD_START_TRANSMIT(msdp); + + /* wait for ISR */ + return MSD_WAIT_MODE_BULK_IN; +} + +static msd_wait_mode_t SCSICommandReadCapacity10(USBMassStorageDriver *msdp) { + msdSetDefaultSenseKey(msdp); + + msdp->data.read_capacity10_response.block_size = swap_uint32(msdp->block_dev_info.blk_size); + msdp->data.read_capacity10_response.last_block_addr = swap_uint32(msdp->block_dev_info.blk_num - 1); + + usbPrepareTransmit(msdp->usbp, msdp->ms_ep_number, (uint8_t *)&msdp->data.read_capacity10_response, + sizeof(msdp->data.read_capacity10_response)); + + MSD_START_TRANSMIT(msdp); + + /* wait for ISR */ + return MSD_WAIT_MODE_BULK_IN; +} + +static msd_wait_mode_t SCSICommandSendDiagnostic(USBMassStorageDriver *msdp) { + msd_cbw_t *cbw = &(msdp->cbw); + + if ((!cbw->scsi_cmd_data[1]) & (1 << 2)) { + /* Only self-test supported - update SENSE key and fail the command */ + SCSISetSense(msdp, SCSI_SENSE_KEY_ILLEGAL_REQUEST, + SCSI_ASENSE_INVALID_FIELD_IN_CDB, SCSI_ASENSEQ_NO_QUALIFIER); + + msdp->command_succeeded_flag = false; + return MSD_WAIT_MODE_NONE; + } + + /* TODO: actually perform the test */ + + /* don't wait for ISR */ + return MSD_WAIT_MODE_NONE; +} + +static void SCSIWriteTransferPingPong(USBMassStorageDriver *msdp, + volatile rw_usb_sd_buffer_t *dest_buffer) { + msd_debug_nest_print(msdp->chp, "B"); + int cnt; + dest_buffer->transfer_status = MSD_USB_TRANSFER_STATUS_RUNNING; + dest_buffer->num_blocks_to_write = 0; + +#if MSD_ENABLE_PERF_DEBUG_GPIOS + palSetPad(GPIOH, GPIOH_LED2); +#endif + + for (cnt = 0; + cnt < BLOCK_WRITE_ITTERATION_COUNT + && cnt < dest_buffer->max_blocks_to_read; cnt++) { + + msdp->transfer_thread_state = "RX-Prep"; + usbPrepareReceive(msdp->usbp, msdp->ms_ep_number, + (uint8_t*)&dest_buffer->buf[cnt * BLOCK_SIZE_INCREMENT], + (msdp->block_dev_info.blk_size)); + + msdp->transfer_thread_state = "RX"; + MSD_START_RECEIVED(msdp); + + msdp->transfer_thread_state = "RX-Wait"; + msdWaitForISR(msdp, FALSE, MSD_WAIT_MODE_BULK_OUT); + + dest_buffer->num_blocks_to_write++; + } + dest_buffer->transfer_status = MSD_USB_TRANSFER_STATUS_DONE_SUCCESSFUL; + msdp->transfer_thread_state = "RX-Done"; + //FIXME we need to handle setting the status to error if something failed, like a usb reset or something + +#if MSD_ENABLE_PERF_DEBUG_GPIOS + palClearPad(GPIOH, GPIOH_LED2); +#endif + + msd_debug_nest_print(msdp->chp, "b"); +} + + +static msd_wait_mode_t SCSICommandStartReadWrite10(USBMassStorageDriver *msdp) { + msd_cbw_t *cbw = &(msdp->cbw); + int read_success; + int retry_count; + + msdSetDefaultSenseKey(msdp); + + if ((cbw->scsi_cmd_data[0] == SCSI_CMD_WRITE_10) && blkIsWriteProtected(msdp->bbdp)) { + msd_debug_err_print(msdp->chp, "\r\nWrite Protected!\r\n"); + /* device is write protected and a write has been issued */ + /* Block address is invalid, update SENSE key and return command fail */ + SCSISetSense(msdp, SCSI_SENSE_KEY_NOT_READY, SCSI_ASENSE_WRITE_PROTECTED, + SCSI_ASENSEQ_NO_QUALIFIER); + + msdp->command_succeeded_flag = false; + msdp->stall_in_endpoint = true; + return MSD_WAIT_MODE_NONE; + } + + //FIXME, which of these? + //uint32_t rw_block_address = swap_4byte_buffer(&cbw->scsi_cmd_data[2]); + //const uint16_t total_blocks = swap_2byte_buffer(&cbw->scsi_cmd_data[7]); + uint32_t rw_block_address = swap_uint32(*(uint32_t *)&cbw->scsi_cmd_data[2]); + const uint16_t total_blocks = swap_uint16(*(uint16_t *)&cbw->scsi_cmd_data[7]); + const uint32_t rw_block_address_origional = rw_block_address; + uint16_t i = 0; + + if (rw_block_address >= msdp->block_dev_info.blk_num) { + msd_debug_err_print(msdp->chp, "\r\nBlock Address too large %u > %u\r\n", rw_block_address, msdp->block_dev_info.blk_num); + /* Block address is invalid, update SENSE key and return command fail */ + SCSISetSense(msdp, SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_ASENSE_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE, + SCSI_ASENSEQ_NO_QUALIFIER); + + msdp->command_succeeded_flag = false; + msdp->stall_in_endpoint = true; + + /* don't wait for ISR */ + return MSD_WAIT_MODE_NONE; + } + + /*initialized ping pong buffer*/ + for (i = 0; i < 2; i++) { + rw_ping_pong_buffer[i].max_blocks_to_read = 0; + rw_ping_pong_buffer[i].num_blocks_to_write = 0; + rw_ping_pong_buffer[i].transfer_status = MSD_USB_TRANSFER_STATUS_RUNNING; + } + + msd_debug_nest_print(msdp->chp, "\r\nG"); + if (cbw->scsi_cmd_data[0] == SCSI_CMD_WRITE_10) { + /* loop over each block */ + + uint32_t ping_pong_buffer_index = 0; + /*initiate a transfer*/ + rw_ping_pong_buffer[ping_pong_buffer_index].transfer_status = MSD_USB_TRANSFER_STATUS_RUNNING; + rw_ping_pong_buffer[ping_pong_buffer_index].max_blocks_to_read = total_blocks; + + /*Trigger the transfer in the other thread*/ + msdp->trigger_transfer_index = ping_pong_buffer_index; + + /*wake other thread on semaphore to trigger the transfer*/ + chBSemSignal(&msdp->usb_transfer_thread_bsem); + + WaitForUSBTransferComplete(msdp, ping_pong_buffer_index); + + for (i = 0; i < total_blocks;) { + const int done_buffer_index = ping_pong_buffer_index; + const int empty_buffer_index = ((ping_pong_buffer_index + 1) % 2); + + /*initiate another transfer in the other ping pong buffer*/ + //const bool_t queue_another_transfer = ((i + BLOCK_WRITE_ITTERATION_COUNT) < total_blocks); + const bool_t queue_another_transfer = ((i + rw_ping_pong_buffer[done_buffer_index].num_blocks_to_write) < total_blocks); + + msd_debug_nest_print(msdp->chp, "D"); + if (queue_another_transfer) { + while (TRUE) { + if (msdp->trigger_transfer_index == UINT32_MAX) { + rw_ping_pong_buffer[empty_buffer_index].max_blocks_to_read = + total_blocks - i - rw_ping_pong_buffer[done_buffer_index].num_blocks_to_write; + + msdp->trigger_transfer_index = empty_buffer_index; + + /*wake other thread on semaphore to trigger the transfer*/ + chBSemSignal(&msdp->usb_transfer_thread_bsem); + break; + } else { + chThdSleepMilliseconds(1); + } + } + } + msd_debug_nest_print(msdp->chp, "d"); + + if (rw_ping_pong_buffer[done_buffer_index].num_blocks_to_write <= 0) { + /*This should never happen!!! Something is seriously wrong!*/ + msd_debug_err_print( + msdp->chp, "\r\nCant write 0 blocks, this should not happen, halting\r\n"); + chThdSleepMilliseconds(50); + chSysHalt(); + } + + /* now write the block to the block device */ + MSD_W_LED_ON(); + msd_debug_nest_print(msdp->chp, "E"); + if (blkWrite(msdp->bbdp, rw_block_address, + (uint8_t*)rw_ping_pong_buffer[done_buffer_index].buf, + rw_ping_pong_buffer[done_buffer_index].num_blocks_to_write) + == CH_FAILED) { + msd_debug_err_print(msdp->chp, "\r\nSD Block Write Error\r\n"); + chThdSleepMilliseconds(50); + msdp->write_error_count++; + + msdp->command_succeeded_flag = false; + + SCSISetSense(msdp, SCSI_SENSE_KEY_MEDIUM_ERROR, + SCSI_ASENSE_PEREPHERIAL_DEVICE_WRITE_FAULT, + SCSI_ASENSEQ_PEREPHERIAL_DEVICE_WRITE_FAULT); + + +#define OLD_WRITE_ERROR_HANDLING FALSE +#if OLD_WRITE_ERROR_HANDLING + /* + * I think that this mode of error handling is incorrect, and was causing ACM0 usb device resets in the event of EMMC write errors. + * I confirmed using the Beagle USB 480 analyzer that the host gets a failed staus, and retries the write to the given error block, at which point the block write succeeds for thist test case. + * This code should be purged at some point + */ + msdp->stall_out_endpoint = true; + + if (queue_another_transfer) { + /*Let the previous queued transfer finish and ignore it.*/ + WaitForUSBTransferComplete(msdp, empty_buffer_index); + } + + MSD_W_LED_OFF(); + return (MSD_WAIT_MODE_NONE); +#endif + } else { + msdp->write_success_count++; + } + msd_debug_nest_print(msdp->chp, "e"); + MSD_W_LED_OFF(); + + rw_block_address += rw_ping_pong_buffer[done_buffer_index].num_blocks_to_write; + i += rw_ping_pong_buffer[done_buffer_index].num_blocks_to_write; + rw_ping_pong_buffer[done_buffer_index].transfer_status = MSD_USB_TRANSFER_STATUS_RUNNING; + rw_ping_pong_buffer[done_buffer_index].num_blocks_to_write = 0; + + msd_debug_nest_print(msdp->chp, "F"); + if (queue_another_transfer) { + if( !( i< total_blocks) ) { + msd_debug_err_print(msdp->chp, "\r\nERROR: Queued another transfer but not going to rx the data\r\n"); + } + WaitForUSBTransferComplete(msdp, empty_buffer_index); + } + msd_debug_nest_print(msdp->chp, "f"); + + /*Swap the ping pong buffers*/ + ping_pong_buffer_index = empty_buffer_index; + } + + if( i != total_blocks ) { + msd_debug_err_print(msdp->chp, "\r\ni!=total_blocks, %u, %u\r\n", i, total_blocks); + } + + msd_debug_nest_print(msdp->chp, "(%u,%u,%u)", rw_block_address_origional, total_blocks, i); + + } else { + /* FIXME: For some reason, when doing a blkRead on a SanDisk 8g sd card, it takes 2.5ms to read a block, limiting + * max read performance to about 200k/s. However, when using an 8g transcend SD card, we can get up to 2.0 megabytes/sec + * read through put. What's the difference? configuration for the SD driver? + */ + + i = 0; + /* read the first block from block device */ + read_success = FALSE; + for (retry_count = 0; retry_count < 3; retry_count++) { + if (blkRead(msdp->bbdp, rw_block_address, read_buffer[i % 2], 1) + == CH_FAILED) { + msd_debug_err_print(msdp->chp, "\r\nSD Block Read Error: block # %u\r\n", rw_block_address); + msdp->read_error_count++; + } else { + msdp->read_success_count++; + if( retry_count > 0 ) { + msd_debug_err_print(msdp->chp, "Successful Block Read Retry\r\n"); + } + read_success = TRUE; + break; + } + } + + + if ((!read_success) ) { + msd_debug_err_print(msdp->chp, "\r\nSD Block Read Error 1, breaking read sequence, block # %u\r\n", rw_block_address); + + /*wait for printing to finish*/ + chThdSleepMilliseconds(10); + + msdp->command_succeeded_flag = false; + msdp->stall_in_endpoint = true; + + msd_debug_err_print( + msdp->chp, "\r\nSetting sense code %u\r\n", SCSI_SENSE_KEY_MEDIUM_ERROR); + + SCSISetSense(msdp, SCSI_SENSE_KEY_MEDIUM_ERROR, + SCSI_ASENSE_UNRECOVERED_READ_ERROR, + SCSI_ASENSEQ_NO_QUALIFIER); + + return MSD_WAIT_MODE_NONE; + } + + rw_block_address++; + + /* loop over each block */ + for (i = 0; i < total_blocks; i++) { + + /* transmit the block */ + //while (usbGetTransmitStatusI(msdp->usbp, msdp->ms_ep_number)) { + //wait for the prior transmit to complete + //} + usbPrepareTransmit(msdp->usbp, msdp->ms_ep_number, read_buffer[i % 2], + msdp->block_dev_info.blk_size); + + MSD_START_TRANSMIT(msdp); + + if (i < (total_blocks - 1)) { + /* there is at least one more block to be read from device */ + /* so read that while the USB transfer takes place */ + read_success = FALSE; + MSD_R_LED_ON(); + for (retry_count = 0; retry_count < 3; retry_count++) { + if (blkRead(msdp->bbdp, rw_block_address, read_buffer[(i+1) % 2], 1) + == CH_FAILED) { + msd_debug_err_print(msdp->chp, "\r\nSD Block Read Error 2: block # %u\r\n", rw_block_address); + + msdp->read_error_count++; + } else { + if( retry_count > 0 ) { + msd_debug_err_print(msdp->chp, "Successful Block Read Retry: block # %u\r\n", rw_block_address); + } + read_success = TRUE; + msdp->read_success_count++; + break; + } + } + MSD_R_LED_OFF(); + + if( !read_success ) { + msd_debug_err_print( + msdp->chp, "\r\nSD Block Read Error 22, addr=%d, halting\r\n", rw_block_address); + + /*wait for printing to finish*/ + chThdSleepMilliseconds(70); + + msdp->command_succeeded_flag = false; + msdp->stall_in_endpoint = true; + + msd_debug_err_print( + msdp->chp, "\r\nSetting sense code %u\r\n", SCSI_SENSE_KEY_MEDIUM_ERROR); + + SCSISetSense(msdp, SCSI_SENSE_KEY_MEDIUM_ERROR, + SCSI_ASENSE_UNRECOVERED_READ_ERROR, + SCSI_ASENSEQ_NO_QUALIFIER); + return MSD_WAIT_MODE_NONE; + } + + rw_block_address++; + } + + /*FIXME In the event that the USB connection is unplugged while we're waiting for a bulk + * endpoint ISR, this will never return, and when re-plugged into the host, the drive will + * not show back up on the host. We need a way to break out of this loop when disconnected from the bus. + */ + + if (msdWaitForISR(msdp, TRUE, MSD_WAIT_MODE_BULK_IN) == WAIT_ISR_BUSS_RESET_OR_RECONNECT) { + //fixme are we handling the reset case properly + return MSD_WAIT_MODE_NONE; + } + } + } + msd_debug_nest_print(msdp->chp, "g"); + + /* don't wait for ISR */ + return MSD_WAIT_MODE_NONE; +} + +static msd_wait_mode_t SCSICommandStartStopUnit(USBMassStorageDriver *msdp) { + scsi_start_stop_unit_request_t *ssu = + (scsi_start_stop_unit_request_t *)&(msdp->cbw.scsi_cmd_data); + + if ((ssu->loej_start & 0b00000011) == 0b00000010) { + /* device has been ejected */ + if (!msdp->disable_usb_bus_disconnect_on_eject) { + chEvtBroadcast(&msdp->evt_ejected); + msdp->state = MSD_STATE_EJECTED; + } + } + + /* don't wait for ISR */ + return MSD_WAIT_MODE_NONE; +} + +static msd_wait_mode_t SCSICommandPreventAllowMediumRemovial(USBMassStorageDriver *msdp) { + msd_cbw_t *cbw = &(msdp->cbw); + + if( (cbw->scsi_cmd_data[4] & 0x01) ) { + //prohibit media removal + if( msdp->enable_media_removial ) { + //this can have positive performance + msdp->command_succeeded_flag = false; + SCSISetSense(msdp, SCSI_SENSE_KEY_ILLEGAL_REQUEST, + SCSI_ASENSE_INVALID_COMMAND, SCSI_ASENSEQ_NO_QUALIFIER); + } + } + + /* don't wait for ISR */ + return MSD_WAIT_MODE_NONE; +} + + + +static msd_wait_mode_t SCSICommandModeSense6(USBMassStorageDriver *msdp) { + //FIXME check for unsupported mode page set sense code, see page 161(144) of USB Mass storage book + memset(&msdp->data.mode_sense6_response, 0, sizeof(msdp->data.mode_sense6_response)); + msdp->data.mode_sense6_response.mode_data_length = 3; + if( blkIsWriteProtected(msdp->bbdp) ) { + msdp->data.mode_sense6_response.device_specifc_paramters |= (1<<7); + } + + usbPrepareTransmit(msdp->usbp, msdp->ms_ep_number, (uint8_t*)&msdp->data.mode_sense6_response, 4); + + MSD_START_TRANSMIT(msdp); + + /* wait for ISR */ + return MSD_WAIT_MODE_BULK_IN; +} + +static msd_wait_mode_t msdWaitForCommandBlock(USBMassStorageDriver *msdp) { + usbPrepareReceive(msdp->usbp, msdp->ms_ep_number, (uint8_t *)&msdp->cbw, + sizeof(msd_cbw_t)); + + MSD_START_RECEIVED(msdp); + + msdp->state = MSD_STATE_READ_CMD_BLOCK; + + return(MSD_WAIT_MODE_BULK_OUT);/* wait for ISR */ +} + +/* */ +/** + * @brief A command block has been received + + * + * @param[in] p1 description of parameter one + * @param[out] p2 description of parameter two + * @param[in,out] p3 description of parameter three + * @return Description of the returned value, must be omitted if + * a function returns void. + * @retval TRUE On success + * @retval FALSE On failure + * + */ +static msd_wait_mode_t msdProcessCommandBlock(USBMassStorageDriver *msdp) { + msd_cbw_t *cbw = &(msdp->cbw); + + /* by default transition back to the idle state */ + msdp->state = MSD_STATE_IDLE; + + msd_debug_print(msdp->chp, " CMD 0x%X\r\n", cbw->scsi_cmd_data[0]); + msdp->command_succeeded_flag = true; + msdp->stall_in_endpoint = false; + msdp->stall_out_endpoint = false; + msd_wait_mode_t wait_mode = MSD_WAIT_MODE_NONE; + + + /* check the command */ + if ((cbw->signature != MSD_CBW_SIGNATURE) || (cbw->lun > 0) + || ((cbw->data_len > 0) && (cbw->flags & 0x1F)) + || (cbw->scsi_cmd_len == 0) || (cbw->scsi_cmd_len > 16)) + { + + msdp->scsi_command_state = "Bad CBW"; + msd_debug_err_print(msdp->chp, "Bad CBW, sig=0x%X, lun=0x%X, data_len=0x%X, flags=0x%X, scsi_cmd_len=0x%X\r\n", cbw->signature, cbw->lun, cbw->data_len, cbw->flags, cbw->scsi_cmd_len); + /* stall both IN and OUT endpoints */ + msdp->stall_out_endpoint = true; + msdp->stall_in_endpoint = true; + msdp->command_succeeded_flag = false; + SCSISetSense(msdp, SCSI_SENSE_KEY_ILLEGAL_REQUEST, + SCSI_ASENSE_INVALID_FIELD_IN_CDB, SCSI_ASENSEQ_NO_QUALIFIER); + +#if 0 + chSysLock(); + msdp->bulk_out_interupt_flag = false; + usbStallReceiveI(msdp->usbp, msdp->ms_ep_number); + //usbStallTransmitI(msdp->usbp, msdp->ms_ep_number); + chSysUnlock(); + + //wait for the host to clear the stall + msdWaitForISR(msdp, TRUE, MSD_WAIT_MODE_BULK_OUT); + + /* don't wait for ISR */ + return MSD_WAIT_MODE_NONE; +#endif + } + + + if( msdp->command_succeeded_flag ) { + switch ( (msd_scsi_command_t) cbw->scsi_cmd_data[0] ) { + case SCSI_CMD_INQUIRY: + msdp->scsi_command_state = "CMD_INQ"; + msd_debug_print(msdp->chp, "CMD_INQ\r\n"); + wait_mode = SCSICommandInquiry(msdp); + msdp->scsi_command_state = "CMD_INQ-Done"; + break; + case SCSI_CMD_REQUEST_SENSE_6: + msdp->scsi_command_state = "CMD_RS"; + msd_debug_print(msdp->chp, "\r\nCMD_RS\r\n"); + wait_mode = SCSICommandRequestSense(msdp); + msdp->scsi_command_state = "CMD_RS-Done"; + break; + case SCSI_CMD_READ_FORMAT_CAPACITY: + msdp->scsi_command_state = "CMD_RFC"; + msd_debug_print(msdp->chp, "CMD_RFC\r\n"); + wait_mode = SCSICommandReadFormatCapacity(msdp); + msdp->scsi_command_state = "CMD_RFC-Done"; + break; + case SCSI_CMD_READ_CAPACITY_10: + msdp->scsi_command_state = "CMD_RC10"; + msd_debug_print(msdp->chp, "CMD_RC10\r\n"); + wait_mode = SCSICommandReadCapacity10(msdp); + msdp->scsi_command_state = "CMD_RC10-Done"; + break; + case SCSI_CMD_READ_10: + case SCSI_CMD_WRITE_10: + msdp->scsi_command_state = "CMD_RW"; + msd_debug_print(msdp->chp, "CMD_RW\r\n"); + MSD_RW_LED_ON(); + wait_mode = SCSICommandStartReadWrite10(msdp); + MSD_RW_LED_OFF(); + msdp->scsi_command_state = "CMD_RW-Done"; + break; + case SCSI_CMD_SEND_DIAGNOSTIC: + msdp->scsi_command_state = "CMD_DIA"; + msd_debug_print(msdp->chp, "CMD_DIA\r\n"); + wait_mode = SCSICommandSendDiagnostic(msdp); + msdp->scsi_command_state = "CMD_DIA-Done"; + break; + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + msdp->scsi_command_state = "CMD_PAMR"; + msd_debug_print(msdp->chp, "CMD_PAMR\r\n"); + wait_mode = SCSICommandPreventAllowMediumRemovial(msdp); + msdp->scsi_command_state = "CMD_PAMR-Done"; + break; + case SCSI_CMD_TEST_UNIT_READY: + case SCSI_CMD_VERIFY_10: + msdp->scsi_command_state = "CMD_00_1E_2F"; + msd_debug_print(msdp->chp, "CMD_00_1E_2F\r\n"); + msdp->scsi_command_state = "CMD_00_1E_2F-Done"; + /* don't handle */ + break; + case SCSI_CMD_MODE_SENSE_6: + msdp->scsi_command_state = "CMD_S6"; + msd_debug_print(msdp->chp, "\r\nCMD_S6\r\n"); + wait_mode = SCSICommandModeSense6(msdp); + msdp->scsi_command_state = "CMD_S6-Done"; + break; + case SCSI_CMD_START_STOP_UNIT: + msdp->scsi_command_state = "CMD_STOP"; + msd_debug_print(msdp->chp, "CMD_STOP\r\n"); + wait_mode = SCSICommandStartStopUnit(msdp); + msdp->scsi_command_state = "CMD_STOP-Done"; + break; + case SCSI_CMD_SYNCHRONIZE_CACHE_10: + msdp->scsi_command_state = "SYNC_10"; + msd_debug_print(msdp->chp, "SYNC_10\r\n"); + //FIXME impliment this and flush data to the MMC card, Linux sends this command. We are implicitly synchronized + //in our writes since we never leave data in RAM + + SCSISetSense(msdp, SCSI_SENSE_KEY_GOOD, + SCSI_ASENSE_NO_ADDITIONAL_INFORMATION, SCSI_ASENSEQ_NO_QUALIFIER); + wait_mode = MSD_WAIT_MODE_NONE; + + msdp->scsi_command_state = "SYNC_10-Done"; + break; + default: + msdp->last_bad_scsi_command = cbw->scsi_cmd_data[0]; + + msdp->scsi_command_state = "CMD_Def"; + msd_debug_err_print(msdp->chp, "CMD Unknown: 0x%X, using default CMD handler\r\n", cbw->scsi_cmd_data[0]); + msdp->command_succeeded_flag = false; + SCSISetSense(msdp, SCSI_SENSE_KEY_ILLEGAL_REQUEST, + SCSI_ASENSE_INVALID_COMMAND, SCSI_ASENSEQ_NO_QUALIFIER); + +#if 0 + /* stall IN endpoint */ + chSysLock() + msdp->bulk_in_interupt_flag = false; + usbStallTransmitI(msdp->usbp, msdp->ms_ep_number); + chSysUnlock() + + msdWaitForISR(msdp, TRUE, MSD_WAIT_MODE_BULK_IN); +#else + //msdp->stall_out_endpoint = true; + //msdp->stall_in_endpoint = true; +#endif + + msdp->scsi_command_state = "CMD_Def-Done"; + cbw->data_len = 0; +#if 0 + return MSD_WAIT_MODE_NONE; +#endif + } + } + + cbw->data_len = 0; + + + if( msdp->stall_in_endpoint || msdp->stall_out_endpoint ) { + msdp->scsi_command_state = "Stall"; + /* stall IN endpoint */ + chSysLock(); + if( msdp->stall_in_endpoint ) { + msd_debug_err_print(msdp->chp, "stalling IN endpoint\r\n"); + msdp->bulk_in_interupt_flag = false; + usbStallTransmitI(msdp->usbp, msdp->ms_ep_number); + } + if( msdp->stall_out_endpoint ) { + msd_debug_err_print(msdp->chp, "stalling OUT endpoint\r\n"); + msdp->bulk_out_interupt_flag = false; + usbStallReceiveI(msdp->usbp, msdp->ms_ep_number); + } + chSysUnlock(); + + if( msdp->stall_in_endpoint ) { + msdWaitForISR(msdp, TRUE, MSD_WAIT_MODE_BULK_IN); + } + + if( msdp->stall_out_endpoint ) { + msdWaitForISR(msdp, TRUE, MSD_WAIT_MODE_BULK_OUT); + } + } + + msdp->scsi_command_state = "Wait"; + + if (wait_mode != MSD_WAIT_MODE_NONE ) { + msd_debug_nest_print(msdp->chp, "H"); + if (msdWaitForISR(msdp, TRUE, wait_mode) == WAIT_ISR_BUSS_RESET_OR_RECONNECT) { + msd_debug_nest_print(msdp->chp, "h"); + return (MSD_WAIT_MODE_NONE); + } + msd_debug_nest_print(msdp->chp, "h"); + } + + msdp->scsi_command_state = "Wait-Done"; + + msd_csw_t *csw = &(msdp->csw); + + + csw->status = (msdp->command_succeeded_flag) ? MSD_COMMAND_PASSED : MSD_COMMAND_FAILED; + csw->signature = MSD_CSW_SIGNATURE; + csw->data_residue = cbw->data_len; + csw->tag = cbw->tag; + + msdp->scsi_command_state = "TX"; + msd_debug_nest_print(msdp->chp, "I"); + usbPrepareTransmit(msdp->usbp, msdp->ms_ep_number, (uint8_t *)csw, + sizeof(msd_csw_t)); + + chSysLock(); + msdp->bulk_out_interupt_flag = false; + msdWaitForCommandBlock(msdp); + + msdp->bulk_in_interupt_flag = false; + usbStartTransmitI(msdp->usbp, msdp->ms_ep_number); + chSysUnlock(); + msd_debug_nest_print(msdp->chp, "i"); + + msdWaitForISR(msdp, TRUE, MSD_WAIT_MODE_BULK_IN);//wait for our status to be sent back + + msdp->scsi_command_state = "Done All"; + + /* wait on ISR */ + //return MSD_WAIT_MODE_BULK_IN; + return MSD_WAIT_MODE_BULK_OUT; +} + + + + + +/*===========================================================================*/ +/* Threads */ +/*===========================================================================*/ + +/** + * @brief This thread is responsible for triggering a USB write of date + * from the MCU to the host. It is run as a separate thread to allow + * for concurrent RXing of data and writing of data to the SD card to + * thus significantly improve performance. + * + * @param[in] arg pointer to the @p USBMassStorageDriver object + * + * @special + */ +static msg_t MassStorageUSBTransferThd(void *arg) { + USBMassStorageDriver *msdp = (USBMassStorageDriver *)arg; + + chRegSetThreadName("MSD-Transfer"); + + while ( !chThdShouldTerminate() ) { + if (msdp->suspend_threads_callback != NULL && msdp->suspend_threads_callback()) { + /* Suspend the thread for power savings mode */ + chSysLock(); + chSchGoSleepS(THD_STATE_SUSPENDED); + chSysUnlock(); + } + + if (msdp->trigger_transfer_index != UINT32_MAX) { + msdp->transfer_thread_state = "PP"; + SCSIWriteTransferPingPong( + msdp, &rw_ping_pong_buffer[msdp->trigger_transfer_index]); + msdp->trigger_transfer_index = UINT32_MAX; + /*notify other thread*/ + chBSemSignal(&msdp->mass_sorage_thd_bsem); + } + + chBSemWaitTimeout(&msdp->usb_transfer_thread_bsem, MS2ST(1)); + } + + return (0); +} + + + +static msg_t MassStorageThd(void *arg) { + USBMassStorageDriver *msdp = (USBMassStorageDriver *)arg; + chRegSetThreadName("MSD"); + + msd_wait_mode_t wait_for_isr = MSD_WAIT_MODE_NONE; + + /* wait for the usb to be initialized */ + msd_debug_print(msdp->chp, "Y"); + msdWaitForISR(msdp, FALSE, MSD_WAIT_MODE_NONE); + msd_debug_print(msdp->chp, "y"); + + while ( !chThdShouldTerminate() ) { + +#if 0 + if( msdp->suspend_threads_callback != NULL && msdp->suspend_threads_callback() ) { + /* Suspend the thread for power savings mode */ + chSysLock(); + chSchGoSleepS(THD_STATE_SUSPENDED); + chSysUnlock(); + } +#endif + + + wait_for_isr = MSD_WAIT_MODE_NONE; + + if (msdp->reconfigured_or_reset_event) { + /*If the devices is unplugged and re-plugged but did not have a CPU reset, + * we must set the state back to idle.*/ + msdp->reconfigured_or_reset_event = FALSE; + msdp->state = MSD_STATE_IDLE; + + msdSetDefaultSenseKey(msdp); + } + + bool_t enable_msd = true; + if (msdp->enable_msd_callback != NULL) { + enable_msd = msdp->enable_msd_callback(); + } + + if( msdp->driver_state != USB_MSD_DRIVER_OK ) { + enable_msd = false; + } + msdp->debug_enable_msd = enable_msd; + + if ( enable_msd ) { + + if( ! msdp->block_dev_info_valid_flag ) { + if( blkGetDriverState(msdp->bbdp) == BLK_READY ) { + blkGetInfo(msdp->bbdp, &msdp->block_dev_info); + msdp->block_dev_info_valid_flag = true; + } + } + + msd_debug_print(msdp->chp, "state=%d\r\n", msdp->state); + /* wait on data depending on the current state */ + switch (msdp->state) { + case MSD_STATE_IDLE: + msdp->msd_thread_state = "IDL"; + msd_debug_print(msdp->chp, "IDL"); + wait_for_isr = msdWaitForCommandBlock(msdp); + msd_debug_print(msdp->chp, "x\r\n"); + break; + case MSD_STATE_READ_CMD_BLOCK: + msdp->msd_thread_state = "RCB"; + msd_debug_print(msdp->chp, "RCB"); + wait_for_isr = msdProcessCommandBlock(msdp); + msd_debug_print(msdp->chp, "x\r\n"); + break; + case MSD_STATE_EJECTED: + /* disconnect usb device */ + msd_debug_print(msdp->chp, "ejected\r\n"); + if (!msdp->disable_usb_bus_disconnect_on_eject) { + msdp->msd_thread_state = "Ejected"; + chThdSleepMilliseconds(70); + usbDisconnectBus(msdp->usbp); + usbStop(msdp->usbp); + chThdExit(0); + } + return 0; + } + } + + msdp->debug_wait_for_isr = wait_for_isr; + + if( enable_msd ) { + msd_debug_nest_print(msdp->chp, "L"); + } else { + msd_debug_nest_print(msdp->chp, "M"); + } + + if (wait_for_isr) { + msd_debug_nest_print(msdp->chp, "J"); + } else { + msd_debug_nest_print(msdp->chp, "K"); + } + + if (wait_for_isr && (!msdp->reconfigured_or_reset_event)) { + /* wait until the ISR wakes thread */ + msd_debug_print(msdp->chp, "W%d,%d", wait_for_isr, msdp->state); + msdWaitForISR(msdp, TRUE, wait_for_isr); + msd_debug_print(msdp->chp, "w\r\n"); + } else if( ! enable_msd ) { + chThdSleepMilliseconds(5); + } + + + if (wait_for_isr) { + msd_debug_nest_print(msdp->chp, "j"); + } else { + msd_debug_nest_print(msdp->chp, "k"); + } + } + + return 0; +} + + + + + +#endif /* HAL_USE_MASS_STORAGE_USB */ diff --git a/firmware/hw_layer/mass_storage/usb_msd.h b/firmware/hw_layer/mass_storage/usb_msd.h new file mode 100644 index 0000000000..4fa2c47e2b --- /dev/null +++ b/firmware/hw_layer/mass_storage/usb_msd.h @@ -0,0 +1,300 @@ +/* + ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010, + 2011,2012,2013 Giovanni Di Sirio. + + This file is part of ChibiOS/RT. + + ChibiOS/RT is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS/RT is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef _USB_MSD_H_ +#define _USB_MSD_H_ + +#include "hal.h" + +/* Default to disabled for USB Mass Storage */ +#if !defined(HAL_USE_MASS_STORAGE_USB) +# define HAL_USE_MASS_STORAGE_USB FALSE +#endif + +#if HAL_USE_MASS_STORAGE_USB || defined(__DOXYGEN__) + + +#if 0 +#define MSD_RW_LED_ON() palSetPad(GPIOI, GPIOI_TRI_LED_BLUE) +#define MSD_RW_LED_OFF() palClearPad(GPIOI, GPIOI_TRI_LED_BLUE) +#define MSD_R_LED_ON() palSetPad(GPIOI, GPIOI_TRI_LED_BLUE) +#define MSD_R_LED_OFF() palClearPad(GPIOI, GPIOI_TRI_LED_BLUE) +#define MSD_W_LED_ON() palSetPad(GPIOH, GPIOI_TRI_LED_BLUE) +#define MSD_W_LED_OFF() palClearPad(GPIOH, GPIOI_TRI_LED_BLUE) +#else +#define MSD_RW_LED_ON() +#define MSD_RW_LED_OFF() +#define MSD_R_LED_ON() +#define MSD_R_LED_OFF() +#define MSD_W_LED_ON() +#define MSD_W_LED_OFF() +#endif + + +#if STM32_USB_USE_OTG2 && STM32_USE_USB_OTG2_HS +# define USB_MS_EP_SIZE 512 +#else +# define USB_MS_EP_SIZE 64 +#endif + +#define MSD_THREAD_STACK_SIZE 1024 + +#define MSD_REQ_RESET 0xFF +#define MSD_GET_MAX_LUN 0xFE +#define MSD_CBW_SIGNATURE 0x43425355 +#define MSD_CSW_SIGNATURE 0x53425355 +#define MSD_COMMAND_DIR_DATA_OUT (0 << 7) +#define MSD_COMMAND_DIR_DATA_IN (1 << 7) + +#define MSD_SETUP_WORD(setup, index) (uint16_t)(((uint16_t)setup[index+1] << 8) | (setup[index] & 0x00FF)) + +#define MSD_SETUP_VALUE(setup) MSD_SETUP_WORD(setup, 2) +#define MSD_SETUP_INDEX(setup) MSD_SETUP_WORD(setup, 4) +#define MSD_SETUP_LENGTH(setup) MSD_SETUP_WORD(setup, 6) + +typedef enum { + SCSI_CMD_TEST_UNIT_READY = 0x00, + SCSI_CMD_REQUEST_SENSE_6 = 0x03, + SCSI_CMD_INQUIRY = 0x12, + SCSI_CMD_MODE_SENSE_6 = 0x1A, + SCSI_CMD_START_STOP_UNIT = 0x1B, + SCSI_CMD_SEND_DIAGNOSTIC = 0x1D, + SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E, + SCSI_CMD_READ_FORMAT_CAPACITY = 0x23, + SCSI_CMD_READ_CAPACITY_10 = 0x25, + SCSI_CMD_READ_10 = 0x28, + SCSI_CMD_WRITE_10 = 0x2A, + SCSI_CMD_VERIFY_10 = 0x2F, + SCSI_CMD_SYNCHRONIZE_CACHE_10 = 0x35, +} msd_scsi_command_t; + + + +#define MSD_COMMAND_PASSED 0x00 +#define MSD_COMMAND_FAILED 0x01 +#define MSD_COMMAND_PHASE_ERROR 0x02 + +#define SCSI_SENSE_KEY_GOOD 0x00 +#define SCSI_SENSE_KEY_RECOVERED_ERROR 0x01 +#define SCSI_SENSE_KEY_NOT_READY 0x02 +#define SCSI_SENSE_KEY_MEDIUM_ERROR 0x03 +#define SCSI_SENSE_KEY_HARDWARE_ERROR 0x04 +#define SCSI_SENSE_KEY_ILLEGAL_REQUEST 0x05 +#define SCSI_SENSE_KEY_UNIT_ATTENTION 0x06 +#define SCSI_SENSE_KEY_DATA_PROTECT 0x07 +#define SCSI_SENSE_KEY_BLANK_CHECK 0x08 +#define SCSI_SENSE_KEY_VENDOR_SPECIFIC 0x09 +#define SCSI_SENSE_KEY_COPY_ABORTED 0x0A +#define SCSI_SENSE_KEY_ABORTED_COMMAND 0x0B +#define SCSI_SENSE_KEY_VOLUME_OVERFLOW 0x0D +#define SCSI_SENSE_KEY_MISCOMPARE 0x0E + +#define SCSI_ASENSE_NO_ADDITIONAL_INFORMATION 0x00 +#define SCSI_ASENSE_PEREPHERIAL_DEVICE_WRITE_FAULT 0x03 +#define SCSI_ASENSE_LOGICAL_UNIT_NOT_READY 0x04 +#define SCSI_ASENSE_UNRECOVERED_READ_ERROR 0x11 +#define SCSI_ASENSE_INVALID_COMMAND 0x20 +#define SCSI_ASENSE_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x21 +#define SCSI_ASENSE_INVALID_FIELD_IN_CDB 0x24 +#define SCSI_ASENSE_WRITE_PROTECTED 0x27 +#define SCSI_ASENSE_NOT_READY_TO_READY_CHANGE 0x28 +#define SCSI_ASENSE_FORMAT_ERROR 0x31 +#define SCSI_ASENSE_MEDIUM_NOT_PRESENT 0x3A + +#define SCSI_ASENSEQ_NO_QUALIFIER 0x00 +#define SCSI_ASENSEQ_PEREPHERIAL_DEVICE_WRITE_FAULT 0x00 +#define SCSI_ASENSEQ_FORMAT_COMMAND_FAILED 0x01 +#define SCSI_ASENSEQ_INITIALIZING_COMMAND_REQUIRED 0x02 +#define SCSI_ASENSEQ_OPERATION_IN_PROGRESS 0x07 + + +PACK_STRUCT_BEGIN typedef struct { + uint32_t signature; + uint32_t tag; + uint32_t data_len; + uint8_t flags; + uint8_t lun; + uint8_t scsi_cmd_len; + uint8_t scsi_cmd_data[16]; +} PACK_STRUCT_STRUCT msd_cbw_t PACK_STRUCT_END; + +PACK_STRUCT_BEGIN typedef struct { + uint32_t signature; + uint32_t tag; + uint32_t data_residue; + uint8_t status; +} PACK_STRUCT_STRUCT msd_csw_t PACK_STRUCT_END; + +typedef struct { + uint8_t byte[18]; +} PACK_STRUCT_STRUCT scsi_sense_response_t; + +typedef struct { + uint8_t mode_data_length; //Number of bytes that follow + uint8_t medium_type; //0x00 for SBC devices + uint8_t device_specifc_paramters; //bit 7 is the write protect bit + uint8_t block_descriptor_length; //Length in bytes of all block descriptors in the mode parameter list. +} PACK_STRUCT_STRUCT scsi_mode_sense6_response_t; + +PACK_STRUCT_BEGIN typedef struct +{ + uint8_t peripheral; + uint8_t removable; + uint8_t version; + uint8_t response_data_format; + uint8_t additional_length; + uint8_t sccstp; + uint8_t bqueetc; + uint8_t cmdque; + uint8_t vendorID[8]; + uint8_t productID[16]; + uint8_t productRev[4]; +} PACK_STRUCT_STRUCT scsi_inquiry_response_t PACK_STRUCT_END; + +PACK_STRUCT_BEGIN typedef struct { + uint8_t payload_byte_length[4]; + uint32_t last_block_addr; + uint32_t block_size; +} PACK_STRUCT_STRUCT scsi_read_format_capacity_response_t PACK_STRUCT_END; + +PACK_STRUCT_BEGIN typedef struct { + uint32_t last_block_addr; + uint32_t block_size; +} PACK_STRUCT_STRUCT scsi_read_capacity10_response_t PACK_STRUCT_END; + +PACK_STRUCT_BEGIN typedef struct { + uint8_t op_code; + uint8_t lun_immed; + uint8_t res1; + uint8_t res2; + uint8_t loej_start; + uint8_t control; +} PACK_STRUCT_STRUCT scsi_start_stop_unit_request_t; + + + +typedef enum { + MSD_WAIT_MODE_NONE = 0, + MSD_WAIT_MODE_BULK_IN, + MSD_WAIT_MODE_BULK_OUT +} msd_wait_mode_t; + +typedef enum { + MSD_STATE_IDLE = 0, + MSD_STATE_READ_CMD_BLOCK, + MSD_STATE_EJECTED +} msd_state_t; + +typedef enum { + USB_MSD_DRIVER_UNINITIALIZED = 0, + USB_MSD_DRIVER_ERROR, + USB_MSD_DRIVER_OK, + USB_MSD_DRIVER_STOPPED, + USB_MSD_DRIVER_ERROR_BLK_DEV_NOT_READY, +} usb_msd_driver_state_t; + +typedef struct USBMassStorageDriver USBMassStorageDriver; + +struct USBMassStorageDriver { + /* Driver Setup Data */ + USBDriver *usbp; + BaseBlockDevice *bbdp; + EventSource evt_connected; + EventSource evt_ejected; + BlockDeviceInfo block_dev_info; + bool block_dev_info_valid_flag; + usb_msd_driver_state_t driver_state; + usbep_t ms_ep_number; + uint16_t msd_interface_number; + bool_t (*enable_msd_callback)(void); + bool_t (*suspend_threads_callback)(void); + + /* Externally modifiable settings */ + bool_t enable_media_removial; + bool_t disable_usb_bus_disconnect_on_eject; + BaseSequentialStream *chp; /*For debug logging*/ + + /*Internal data for operation of the driver */ + BinarySemaphore bsem; + BinarySemaphore usb_transfer_thread_bsem; + BinarySemaphore mass_sorage_thd_bsem; + volatile uint32_t trigger_transfer_index; + + volatile bool_t bulk_in_interupt_flag; + volatile bool_t bulk_out_interupt_flag; + + struct { + scsi_read_format_capacity_response_t format_capacity_response; + scsi_read_capacity10_response_t read_capacity10_response; + scsi_mode_sense6_response_t mode_sense6_response; + uint8_t max_lun_len_buf[1]; + scsi_inquiry_response_t scsi_inquiry_response; + } data; + + msd_state_t state; + msd_cbw_t cbw; + msd_csw_t csw; + scsi_sense_response_t sense; + + volatile bool_t reconfigured_or_reset_event; + + bool_t command_succeeded_flag; + bool_t stall_in_endpoint; + bool_t stall_out_endpoint; + + + /*Debugging Information*/ + volatile uint32_t read_error_count; + volatile uint32_t write_error_count; + volatile uint32_t read_success_count; + volatile uint32_t write_success_count; + char *msd_thread_state; + char *transfer_thread_state; + char *scsi_command_state; + volatile uint8_t last_bad_scsi_command; + + /* Externally readable values */ + volatile bool_t debug_enable_msd; + volatile msd_wait_mode_t debug_wait_for_isr; + + WORKING_AREA(waMassStorage, MSD_THREAD_STACK_SIZE); + WORKING_AREA(waMassStorageUSBTransfer, MSD_THREAD_STACK_SIZE); +}; + + +#ifdef __cplusplus +extern "C" { +#endif +usb_msd_driver_state_t msdInit(USBDriver *usbp, BaseBlockDevice *bbdp, USBMassStorageDriver *msdp, const usbep_t ms_ep_number, const uint16_t msd_interface_number); +usb_msd_driver_state_t msdStart(USBMassStorageDriver *msdp); +usb_msd_driver_state_t msdStop(USBMassStorageDriver *msdp); +void msdBulkInCallbackComplete(USBDriver *usbp, usbep_t ep); +void msdBulkOutCallbackComplete(USBDriver *usbp, usbep_t ep); +bool_t msdRequestsHook(USBDriver *usbp); +bool_t msdRequestsHook2(USBDriver *usbp, USBMassStorageDriver *msdp); +const char* usb_msd_driver_state_t_to_str(const usb_msd_driver_state_t driver_state); +#ifdef __cplusplus +} +#endif + + +#endif /* HAL_USE_MASS_STORAGE_USB */ + +#endif /* _USB_MSD_H_ */