1413 lines
46 KiB
C
1413 lines
46 KiB
C
|
/*
|
||
|
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 <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @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 */
|