d499ac9d58
Signed-off-by: Petro Karashchenko <petro.karashchenko@gmail.com>
3072 lines
94 KiB
C
3072 lines
94 KiB
C
/****************************************************************************
|
|
* drivers/usbdev/usbmsc_scsi.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/* Mass storage class device. Bulk-only with SCSI subclass. */
|
|
|
|
/* References:
|
|
* "Universal Serial Bus Mass Storage Class, Specification Overview,"
|
|
* Revision 1.2, USB Implementer's Forum, June 23, 2003.
|
|
*
|
|
* "Universal Serial Bus Mass Storage Class, Bulk-Only Transport,"
|
|
* Revision 1.0, USB Implementer's Forum, September 31, 1999.
|
|
*
|
|
* "SCSI Primary Commands - 3 (SPC-3)," American National Standard
|
|
* for Information Technology, May 4, 2005
|
|
*
|
|
* "SCSI Primary Commands - 4 (SPC-4)," American National Standard
|
|
* for Information Technology, July 19, 2008
|
|
*
|
|
* "SCSI Block Commands -2 (SBC-2)," American National Standard
|
|
* for Information Technology, November 13, 2004
|
|
*/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/kthread.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/queue.h>
|
|
#include <nuttx/signal.h>
|
|
#include <nuttx/scsi.h>
|
|
#include <nuttx/usb/storage.h>
|
|
#include <nuttx/usb/usbdev.h>
|
|
#include <nuttx/usb/usbdev_trace.h>
|
|
|
|
#include "usbmsc.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* Configuration ************************************************************/
|
|
|
|
/* Race condition workaround found by David Hewson. This race condition:
|
|
*
|
|
* "seems to relate to stalling the endpoint when a short response is
|
|
* generated which causes a residue to exist when the CSW would be returned.
|
|
* I think there's two issues here. The first being if the transfer which
|
|
* had just been queued before the stall had not completed then it wouldn't
|
|
* then complete once the endpoint was stalled? The second is that the
|
|
* subsequent transfer for the CSW would be dropped on the floor (by the
|
|
* epsubmit() function) if the end point was still stalled as the control
|
|
* transfer to resume it hadn't occurred."
|
|
*
|
|
* If queuing of stall requests is supported by DCD then this workaround is
|
|
* not required. In this case, (1) the stall is not sent until all write
|
|
* requests preceding the stall request are sent, (2) the stall is sent,
|
|
* and then after the stall is cleared, (3) all write requests queued after
|
|
* the stall are sent.
|
|
*/
|
|
|
|
#ifndef CONFIG_ARCH_USBDEV_STALLQUEUE
|
|
# define USBMSC_STALL_RACEWAR 1
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Debug ********************************************************************/
|
|
|
|
#if defined(CONFIG_DEBUG_INFO) && defined (CONFIG_DEBUG_USB)
|
|
static void usbmsc_dumpdata(FAR const char *msg, const uint8_t *buf,
|
|
int buflen);
|
|
#else
|
|
# define usbmsc_dumpdata(msg, buf, len)
|
|
#endif
|
|
|
|
/* Utility Support Functions ************************************************/
|
|
|
|
static uint16_t usbmsc_getbe16(FAR uint8_t *buf);
|
|
static uint32_t usbmsc_getbe32(FAR uint8_t *buf);
|
|
static void usbmsc_putbe16(FAR uint8_t * buf, uint16_t val);
|
|
static void usbmsc_putbe24(FAR uint8_t *buf, uint32_t val);
|
|
static void usbmsc_putbe32(FAR uint8_t *buf, uint32_t val);
|
|
#if 0 /* not used */
|
|
static uint16_t usbmsc_getle16(FAR uint8_t *buf);
|
|
#endif
|
|
static uint32_t usbmsc_getle32(FAR uint8_t *buf);
|
|
#if 0 /* not used */
|
|
static void usbmsc_putle16(FAR uint8_t * buf, uint16_t val);
|
|
#endif
|
|
static void usbmsc_putle32(FAR uint8_t *buf, uint32_t val);
|
|
|
|
/* SCSI Command Processing **************************************************/
|
|
|
|
static inline int usbmsc_cmdtestunitready(FAR struct usbmsc_dev_s *priv);
|
|
static inline int usbmsc_cmdrequestsense(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf);
|
|
static inline int usbmsc_cmdread6(FAR struct usbmsc_dev_s *priv);
|
|
static inline int usbmsc_cmdwrite6(FAR struct usbmsc_dev_s *priv);
|
|
static inline int usbmsc_cmdinquiry(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf);
|
|
static inline int usbmsc_cmdmodeselect6(FAR struct usbmsc_dev_s *priv);
|
|
static int usbmsc_modepage(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf, uint8_t pcpgcode,
|
|
FAR int *mdlen);
|
|
static inline int usbmsc_cmdmodesense6(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf);
|
|
static inline int usbmsc_cmdstartstopunit(FAR struct usbmsc_dev_s *priv);
|
|
static inline int usbmsc_cmdpreventmediumremoval(
|
|
FAR struct usbmsc_dev_s *priv);
|
|
static inline int usbmsc_cmdreadformatcapacity(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf);
|
|
static inline int usbmsc_cmdreadcapacity10(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf);
|
|
static inline int usbmsc_cmdread10(FAR struct usbmsc_dev_s *priv);
|
|
static inline int usbmsc_cmdwrite10(FAR struct usbmsc_dev_s *priv);
|
|
static inline int usbmsc_cmdverify10(FAR struct usbmsc_dev_s *priv);
|
|
static inline int usbmsc_cmdsynchronizecache10(
|
|
FAR struct usbmsc_dev_s *priv);
|
|
static inline int usbmsc_cmdmodeselect10(FAR struct usbmsc_dev_s *priv);
|
|
static inline int usbmsc_cmdmodesense10(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf);
|
|
static inline int usbmsc_cmdread12(FAR struct usbmsc_dev_s *priv);
|
|
static inline int usbmsc_cmdwrite12(FAR struct usbmsc_dev_s *priv);
|
|
static inline int usbmsc_setupcmd(FAR struct usbmsc_dev_s *priv,
|
|
uint8_t cdblen, uint8_t flags);
|
|
|
|
/* SCSI Worker Thread *******************************************************/
|
|
|
|
static int usbmsc_idlestate(FAR struct usbmsc_dev_s *priv);
|
|
static int usbmsc_cmdparsestate(FAR struct usbmsc_dev_s *priv);
|
|
static int usbmsc_cmdreadstate(FAR struct usbmsc_dev_s *priv);
|
|
static int usbmsc_cmdwritestate(FAR struct usbmsc_dev_s *priv);
|
|
static int usbmsc_cmdfinishstate(FAR struct usbmsc_dev_s *priv);
|
|
static int usbmsc_cmdstatusstate(FAR struct usbmsc_dev_s *priv);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static FAR const char *g_productrevision = "0101";
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Debug
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_dumpdata
|
|
****************************************************************************/
|
|
|
|
#if defined(CONFIG_DEBUG_INFO) && defined (CONFIG_DEBUG_USB)
|
|
static void usbmsc_dumpdata(FAR const char *msg, FAR const uint8_t *buf,
|
|
int buflen)
|
|
{
|
|
int i;
|
|
|
|
syslog(LOG_DEBUG, "%s:", msg);
|
|
for (i = 0; i < buflen; i++)
|
|
{
|
|
syslog(LOG_DEBUG, " %02x", buf[i]);
|
|
}
|
|
|
|
syslog(LOG_DEBUG, "\n");
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Utility Support Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_getbe16
|
|
*
|
|
* Description:
|
|
* Get a 16-bit big-endian value reference by the byte pointer
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint16_t usbmsc_getbe16(FAR uint8_t *buf)
|
|
{
|
|
return ((uint16_t)buf[0] << 8) | ((uint16_t)buf[1]);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_getbe32
|
|
*
|
|
* Description:
|
|
* Get a 32-bit big-endian value reference by the byte pointer
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t usbmsc_getbe32(FAR uint8_t *buf)
|
|
{
|
|
return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) |
|
|
((uint32_t)buf[2] << 8) | ((uint32_t)buf[3]);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_putbe16
|
|
*
|
|
* Description:
|
|
* Store a 16-bit value in big-endian order to the location specified by
|
|
* a byte pointer
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void usbmsc_putbe16(FAR uint8_t * buf, uint16_t val)
|
|
{
|
|
buf[0] = val >> 8;
|
|
buf[1] = val;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_putbe24
|
|
*
|
|
* Description:
|
|
* Store a 32-bit value in big-endian order to the location specified by
|
|
* a byte pointer
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void usbmsc_putbe24(FAR uint8_t *buf, uint32_t val)
|
|
{
|
|
buf[0] = val >> 16;
|
|
buf[1] = val >> 8;
|
|
buf[2] = val;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_putbe32
|
|
*
|
|
* Description:
|
|
* Store a 32-bit value in big-endian order to the location specified by
|
|
* a byte pointer
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void usbmsc_putbe32(FAR uint8_t *buf, uint32_t val)
|
|
{
|
|
buf[0] = val >> 24;
|
|
buf[1] = val >> 16;
|
|
buf[2] = val >> 8;
|
|
buf[3] = val;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_getle16
|
|
*
|
|
* Description:
|
|
* Get a 16-bit little-endian value reference by the byte pointer
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if 0 /* not used */
|
|
static uint16_t usbmsc_getle16(FAR uint8_t *buf)
|
|
{
|
|
return ((uint16_t)buf[1] << 8) | ((uint16_t)buf[0]);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_getle32
|
|
*
|
|
* Description:
|
|
* Get a 32-bit little-endian value reference by the byte pointer
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t usbmsc_getle32(FAR uint8_t *buf)
|
|
{
|
|
return ((uint32_t)buf[3] << 24) | ((uint32_t)buf[2] << 16) |
|
|
((uint32_t)buf[1] << 8) | ((uint32_t)buf[0]);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_putle16
|
|
*
|
|
* Description:
|
|
* Store a 16-bit value in little-endian order to the location specified by
|
|
* a byte pointer
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if 0 /* not used */
|
|
static void usbmsc_putle16(FAR uint8_t *buf, uint16_t val)
|
|
{
|
|
buf[0] = val;
|
|
buf[1] = val >> 8;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_putle32
|
|
*
|
|
* Description:
|
|
* Store a 32-bit value in little-endian order to the location specified by
|
|
* a byte pointer
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void usbmsc_putle32(FAR uint8_t *buf, uint32_t val)
|
|
{
|
|
buf[0] = val;
|
|
buf[1] = val >> 8;
|
|
buf[2] = val >> 16;
|
|
buf[3] = val >> 24;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* SCSI Worker Thread
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_scsi_wait
|
|
*
|
|
* Description:
|
|
* Wait for a SCSI worker thread event.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int usbmsc_scsi_wait(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
irqstate_t flags;
|
|
int ret;
|
|
int ret2;
|
|
|
|
/* We must hold the SCSI lock to call this function */
|
|
|
|
DEBUGASSERT(nxmutex_is_locked(&priv->thlock));
|
|
|
|
/* A flag is used to prevent driving up the semaphore count. This function
|
|
* is called (primarily) from the SCSI work thread so we must disable
|
|
* interrupts momentarily to assure that test of the flag and the wait fo
|
|
* the semaphore count are atomic. Interrupts will, of course, be re-
|
|
* enabled while we wait for the event.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
priv->thwaiting = true;
|
|
|
|
/* Relinquish our lock on the SCSI state data */
|
|
|
|
nxmutex_unlock(&priv->thlock);
|
|
|
|
/* Now wait for a SCSI event to be signaled */
|
|
|
|
do
|
|
{
|
|
ret = nxsem_wait_uninterruptible(&priv->thwaitsem);
|
|
}
|
|
while (priv->thwaiting && ret >= 0);
|
|
|
|
/* Re-acquire our lock on the SCSI state data */
|
|
|
|
ret2 = nxmutex_lock(&priv->thlock);
|
|
leave_critical_section(flags);
|
|
return ret >= 0 ? ret2 : ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdtestunitready
|
|
*
|
|
* Description:
|
|
* Handle the SCSI_CMD_TESTUNITREADY command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdtestunitready(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
int ret;
|
|
|
|
priv->u.alloclen = 0;
|
|
ret = usbmsc_setupcmd(priv, 6, USBMSC_FLAGS_DIRNONE);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdrequestsense
|
|
*
|
|
* Description:
|
|
* Handle the SCSI_CMD_REQUESTSENSE command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdrequestsense(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf)
|
|
{
|
|
FAR struct scsicmd_requestsense_s *request =
|
|
(FAR struct scsicmd_requestsense_s *)priv->cdb;
|
|
FAR struct scsiresp_fixedsensedata_s *response =
|
|
(FAR struct scsiresp_fixedsensedata_s *)buf;
|
|
FAR struct usbmsc_lun_s *lun;
|
|
uint32_t sd;
|
|
uint32_t sdinfo;
|
|
uint8_t cdblen;
|
|
int ret;
|
|
|
|
/* Extract the host allocation length */
|
|
|
|
priv->u.alloclen = request->alloclen;
|
|
|
|
/* Get the expected length of the command (with hack for MS-Windows 12-byte
|
|
* REQUEST SENSE command.
|
|
*/
|
|
|
|
cdblen = SCSICMD_REQUESTSENSE_SIZEOF;
|
|
if (cdblen != priv->cdblen)
|
|
{
|
|
/* Try MS-Windows REQUEST SENSE with cbw->cdblen == 12 */
|
|
|
|
cdblen = SCSICMD_REQUESTSENSE_MSSIZEOF;
|
|
}
|
|
|
|
ret = usbmsc_setupcmd(priv, cdblen,
|
|
USBMSC_FLAGS_DIRDEVICE2HOST |
|
|
USBMSC_FLAGS_LUNNOTNEEDED |
|
|
USBMSC_FLAGS_UACOKAY |
|
|
USBMSC_FLAGS_RETAINSENSEDATA);
|
|
if (ret == OK)
|
|
{
|
|
lun = priv->lun;
|
|
if (!lun)
|
|
{
|
|
sd = SCSI_KCQIR_INVALIDLUN;
|
|
sdinfo = 0;
|
|
}
|
|
else
|
|
{
|
|
/* Get the saved sense data from the LUN structure */
|
|
|
|
sd = lun->sd;
|
|
sdinfo = lun->sdinfo;
|
|
|
|
/* Discard the sense data */
|
|
|
|
lun->sd = SCSI_KCQ_NOSENSE;
|
|
lun->sdinfo = 0;
|
|
}
|
|
|
|
/* Create the fixed sense data response */
|
|
|
|
memset(response, 0, SCSIRESP_FIXEDSENSEDATA_SIZEOF);
|
|
|
|
response->code = SCSIRESP_SENSEDATA_RESPVALID |
|
|
SCSIRESP_SENSEDATA_CURRENTFIXED;
|
|
response->flags = (uint8_t)(sd >> 16);
|
|
usbmsc_putbe32(response->info, sdinfo);
|
|
response->len = SCSIRESP_FIXEDSENSEDATA_SIZEOF - 7;
|
|
response->code2 = (uint8_t)(sd >> 8);
|
|
response->qual2 = (uint8_t)sd;
|
|
|
|
priv->nreqbytes = SCSIRESP_FIXEDSENSEDATA_SIZEOF;
|
|
ret = OK;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdread6
|
|
*
|
|
* Description:
|
|
* Handle the SCSI_CMD_READ6 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdread6(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct scsicmd_read6_s *read6 =
|
|
(FAR struct scsicmd_read6_s *)priv->cdb;
|
|
FAR struct usbmsc_lun_s *lun;
|
|
int ret;
|
|
|
|
priv->u.xfrlen = (uint16_t)read6->xfrlen;
|
|
if (priv->u.xfrlen == 0)
|
|
{
|
|
priv->u.xfrlen = 256;
|
|
}
|
|
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_READ6_SIZEOF,
|
|
USBMSC_FLAGS_DIRDEVICE2HOST | USBMSC_FLAGS_BLOCKXFR);
|
|
if (ret == OK)
|
|
{
|
|
lun = priv->lun;
|
|
|
|
/* Get the Logical Block Address (LBA) from cdb[] as the starting
|
|
* sector
|
|
*/
|
|
|
|
priv->sector =
|
|
(uint32_t)(read6->mslba & SCSICMD_READ6_MSLBAMASK) << 16 |
|
|
(uint32_t)usbmsc_getbe16(read6->lslba);
|
|
|
|
/* Verify that a block driver has been bound to the LUN */
|
|
|
|
if (!lun->inode)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_READ6MEDIANOTPRESENT), 0);
|
|
lun->sd = SCSI_KCQNR_MEDIANOTPRESENT;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Verify that sector lies in the range supported by the block driver */
|
|
|
|
else if (priv->sector >= lun->nsectors)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_READ6LBARANGE), 0);
|
|
lun->sd = SCSI_KCQIR_LBAOUTOFRANGE;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Looks like we are good to go */
|
|
|
|
else
|
|
{
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDPARSECMDREAD6),
|
|
priv->cdb[0]);
|
|
priv->thstate = USBMSC_STATE_CMDREAD;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdwrite6
|
|
*
|
|
* Description:
|
|
* Handle the SCSI_CMD_WRITE6 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdwrite6(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct scsicmd_write6_s *write6 =
|
|
(FAR struct scsicmd_write6_s *)priv->cdb;
|
|
FAR struct usbmsc_lun_s *lun;
|
|
int ret;
|
|
|
|
priv->u.xfrlen = (uint16_t)write6->xfrlen;
|
|
if (priv->u.xfrlen == 0)
|
|
{
|
|
priv->u.xfrlen = 256;
|
|
}
|
|
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_WRITE6_SIZEOF,
|
|
USBMSC_FLAGS_DIRHOST2DEVICE | USBMSC_FLAGS_BLOCKXFR);
|
|
if (ret == OK)
|
|
{
|
|
lun = priv->lun;
|
|
|
|
/* Get the Logical Block Address (LBA) from cdb[] as the starting
|
|
* sector
|
|
*/
|
|
|
|
priv->sector =
|
|
(uint32_t)(write6->mslba & SCSICMD_WRITE6_MSLBAMASK) << 16 |
|
|
(uint32_t)usbmsc_getbe16(write6->lslba);
|
|
|
|
/* Verify that a block driver has been bound to the LUN */
|
|
|
|
if (!lun->inode)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_WRITE6MEDIANOTPRESENT), 0);
|
|
lun->sd = SCSI_KCQNR_MEDIANOTPRESENT;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Check for attempts to write to a read-only device */
|
|
|
|
else if (lun->readonly)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_WRITE6READONLY), 0);
|
|
lun->sd = SCSI_KCQWP_COMMANDNOTALLOWED;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Verify that sector lies in the range supported by the block driver */
|
|
|
|
else if (priv->sector >= lun->nsectors)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_WRITE6LBARANGE), 0);
|
|
lun->sd = SCSI_KCQIR_LBAOUTOFRANGE;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Looks like we are good to go */
|
|
|
|
else
|
|
{
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDPARSECMDWRITE6),
|
|
priv->cdb[0]);
|
|
priv->thstate = USBMSC_STATE_CMDWRITE;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdinquiry
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_INQUIRY command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdinquiry(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf)
|
|
{
|
|
FAR struct scscicmd_inquiry_s *inquiry =
|
|
(FAR struct scscicmd_inquiry_s *)priv->cdb;
|
|
FAR struct scsiresp_inquiry_s *response =
|
|
(FAR struct scsiresp_inquiry_s *)buf;
|
|
int len;
|
|
int ret;
|
|
|
|
priv->u.alloclen = usbmsc_getbe16(inquiry->alloclen);
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_INQUIRY_SIZEOF,
|
|
USBMSC_FLAGS_DIRDEVICE2HOST |
|
|
USBMSC_FLAGS_LUNNOTNEEDED |
|
|
USBMSC_FLAGS_UACOKAY);
|
|
if (ret == OK)
|
|
{
|
|
if (!priv->lun)
|
|
{
|
|
response->qualtype = SCSIRESP_INQUIRYPQ_NOTCAPABLE |
|
|
SCSIRESP_INQUIRYPD_UNKNOWN;
|
|
}
|
|
#ifndef CONFIG_USBMSC_NOT_STALL_BULKEP
|
|
else if ((inquiry->flags != 0) || (inquiry->pagecode != 0))
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_INQUIRYFLAGS), 0);
|
|
priv->lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
|
ret = -EINVAL;
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
memset(response, 0, SCSIRESP_INQUIRY_SIZEOF);
|
|
priv->nreqbytes = SCSIRESP_INQUIRY_SIZEOF;
|
|
|
|
#ifdef CONFIG_USBMSC_REMOVABLE
|
|
response->flags1 = SCSIRESP_INQUIRYFLAGS1_RMB;
|
|
#endif
|
|
response->version = 2; /* SCSI-2 */
|
|
response->flags2 = 2; /* SCSI-2 INQUIRY response data format */
|
|
response->len = SCSIRESP_INQUIRY_SIZEOF - 5;
|
|
|
|
/* Strings */
|
|
|
|
memset(response->vendorid, ' ', 8 + 16 + 4);
|
|
|
|
len = strlen(g_mscvendorstr);
|
|
if (len > 8)
|
|
{
|
|
len = 8;
|
|
}
|
|
|
|
memcpy(response->vendorid, g_mscvendorstr, len);
|
|
|
|
len = strlen(g_mscproductstr);
|
|
if (len > 16)
|
|
{
|
|
len = 16;
|
|
}
|
|
|
|
memcpy(response->productid, g_mscproductstr, len);
|
|
|
|
len = strlen(g_productrevision);
|
|
if (len > 4)
|
|
{
|
|
len = 4;
|
|
}
|
|
|
|
memcpy(response->revision, g_productrevision, len);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdmodeselect6
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_MODESELECT6 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdmodeselect6(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct scsicmd_modeselect6_s *modeselect =
|
|
(FAR struct scsicmd_modeselect6_s *)priv->cdb;
|
|
|
|
priv->u.alloclen = modeselect->plen;
|
|
usbmsc_setupcmd(priv, SCSICMD_MODESELECT6_SIZEOF,
|
|
USBMSC_FLAGS_DIRHOST2DEVICE);
|
|
|
|
/* Not supported */
|
|
|
|
priv->lun->sd = SCSI_KCQIR_INVALIDCOMMAND;
|
|
return -EINVAL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_modepage
|
|
*
|
|
* Description:
|
|
* Common logic for usbmsc_cmdmodesense6() and usbmsc_cmdmodesense10()
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int usbmsc_modepage(FAR struct usbmsc_dev_s *priv, FAR uint8_t *buf,
|
|
uint8_t pcpgcode, FAR int *mdlen)
|
|
{
|
|
FAR struct scsiresp_cachingmodepage_s *cmp =
|
|
(FAR struct scsiresp_cachingmodepage_s *)buf;
|
|
|
|
/* Saving parms not supported */
|
|
|
|
if ((pcpgcode & SCSICMD_MODESENSE_PCMASK) == SCSICMD_MODESENSE_PCSAVED)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_PCSAVED), 0);
|
|
priv->lun->sd = SCSI_KCQIR_SAVINGPARMSNOTSUPPORTED;
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Only the caching mode page is supported: */
|
|
|
|
if ((pcpgcode & SCSICMD_MODESENSE_PGCODEMASK) ==
|
|
SCSIRESP_MODESENSE_PGCCODE_CACHING ||
|
|
(pcpgcode & SCSICMD_MODESENSE_PGCODEMASK) ==
|
|
SCSIRESP_MODESENSE_PGCCODE_RETURNALL)
|
|
{
|
|
memset(cmp, 0, 12);
|
|
cmp->pgcode = SCSIRESP_MODESENSE_PGCCODE_CACHING;
|
|
cmp->len = 10; /* n-2 */
|
|
|
|
/* None of the fields are changeable */
|
|
|
|
if (((pcpgcode & SCSICMD_MODESENSE_PCMASK) !=
|
|
SCSICMD_MODESENSE_PCCHANGEABLE))
|
|
{
|
|
cmp->flags1 = SCSIRESP_CACHINGMODEPG_WCE; /* Write cache enable */
|
|
|
|
/* Disable prefetch transfer length = 0xffffffff */
|
|
|
|
cmp->dpflen[0] = 0xff;
|
|
cmp->dpflen[1] = 0xff;
|
|
|
|
/* Maximum pre-fetch = 0xffffffff */
|
|
|
|
cmp->maxpf[0] = 0xff;
|
|
cmp->maxpf[1] = 0xff;
|
|
|
|
/* Maximum pref-fetch ceiling = 0xffffffff */
|
|
|
|
cmp->maxpfc[0] = 0xff;
|
|
cmp->maxpfc[1] = 0xff;
|
|
}
|
|
|
|
/* Return the mode data length */
|
|
|
|
*mdlen = 12; /* Only the first 12-bytes of caching mode page sent */
|
|
return OK;
|
|
}
|
|
else
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_MODEPAGEFLAGS), pcpgcode);
|
|
priv->lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdmodesense6
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_MODESENSE6 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int inline usbmsc_cmdmodesense6(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf)
|
|
{
|
|
FAR struct scsicmd_modesense6_s *modesense =
|
|
(FAR struct scsicmd_modesense6_s *)priv->cdb;
|
|
FAR struct scsiresp_modeparameterhdr6_s *mph =
|
|
(FAR struct scsiresp_modeparameterhdr6_s *)buf;
|
|
#ifndef CONFIG_USBMSC_NOT_STALL_BULKEP
|
|
int mdlen;
|
|
#endif
|
|
int ret;
|
|
|
|
priv->u.alloclen = modesense->alloclen;
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_MODESENSE6_SIZEOF,
|
|
USBMSC_FLAGS_DIRDEVICE2HOST);
|
|
if (ret == OK)
|
|
{
|
|
#ifdef CONFIG_USBMSC_NOT_STALL_BULKEP
|
|
priv->residue = priv->cbwlen = priv->nreqbytes =
|
|
SCSIRESP_MODEPARAMETERHDR6_SIZEOF;
|
|
#endif
|
|
|
|
if ((modesense->flags & ~SCSICMD_MODESENSE6_DBD) != 0 ||
|
|
modesense->subpgcode != 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_MODESENSE6FLAGS), 0);
|
|
priv->lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
|
ret = -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
/* The response consists of:
|
|
*
|
|
* (1) A MODESENSE6-specific mode parameter header,
|
|
* (2) A variable length list of block descriptors, and
|
|
* (3) A variable length list of mode page formats
|
|
*/
|
|
|
|
mph->type = 0; /* Medium type */
|
|
mph->param =
|
|
(priv->lun->readonly ? SCSIRESP_MODEPARMHDR_DAPARM_WP : 0x00);
|
|
mph->bdlen = 0; /* Block descriptor length */
|
|
|
|
#ifndef CONFIG_USBMSC_NOT_STALL_BULKEP
|
|
/* There are no block descriptors, only the following mode page: */
|
|
|
|
ret = usbmsc_modepage(priv,
|
|
&buf[SCSIRESP_MODEPARAMETERHDR6_SIZEOF],
|
|
modesense->pcpgcode, &mdlen);
|
|
if (ret == OK)
|
|
{
|
|
/* Store the mode data length and return the total message
|
|
* size
|
|
*/
|
|
|
|
mph->mdlen =
|
|
mdlen + SCSIRESP_MODEPARAMETERHDR6_SIZEOF - 1;
|
|
priv->nreqbytes =
|
|
mdlen + SCSIRESP_MODEPARAMETERHDR6_SIZEOF;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdstartstopunit
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_STARTSTOPUNIT command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdstartstopunit(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
int ret;
|
|
|
|
priv->u.alloclen = 0;
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_STARTSTOPUNIT_SIZEOF,
|
|
USBMSC_FLAGS_DIRNONE);
|
|
if (ret == OK)
|
|
{
|
|
#ifndef CONFIG_USBMSC_REMOVABLE
|
|
FAR struct usbmsc_lun_s *lun = priv->lun;
|
|
|
|
/* This command is not valid if the media is not removable */
|
|
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_NOTREMOVABLE), 0);
|
|
lun->sd = SCSI_KCQIR_INVALIDCOMMAND;
|
|
ret = -EINVAL;
|
|
#endif
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdpreventmediumremoval
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_PREVENTMEDIAREMOVAL command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int
|
|
usbmsc_cmdpreventmediumremoval(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
#ifdef CONFIG_USBMSC_REMOVABLE
|
|
FAR struct scsicmd_preventmediumremoval_s *pmr =
|
|
(FAR struct scsicmd_preventmediumremoval_s *)priv->cdb;
|
|
#endif
|
|
FAR struct usbmsc_lun_s *lun;
|
|
int ret;
|
|
|
|
priv->u.alloclen = 0;
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_PREVENTMEDIUMREMOVAL_SIZEOF,
|
|
USBMSC_FLAGS_DIRNONE);
|
|
if (ret == OK)
|
|
{
|
|
lun = priv->lun;
|
|
|
|
#ifndef CONFIG_USBMSC_REMOVABLE
|
|
lun->sd = SCSI_KCQIR_INVALIDCOMMAND;
|
|
ret = -EINVAL;
|
|
#else
|
|
if ((pmr->prevent & ~SCSICMD_PREVENTMEDIUMREMOVAL_TRANSPORT) != 0)
|
|
{
|
|
usbtrace(
|
|
TRACE_CLSERROR(USBMSC_TRACEERR_PREVENTMEDIUMREMOVALPREVENT),
|
|
0);
|
|
|
|
lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
lun->locked = pmr->prevent & SCSICMD_PREVENTMEDIUMREMOVAL_TRANSPORT;
|
|
#endif
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdreadformatcapacity
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_READFORMATCAPACITIES command (MMC)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdreadformatcapacity(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf)
|
|
{
|
|
FAR struct scsicmd_readformatcapcacities_s *rfc =
|
|
(FAR struct scsicmd_readformatcapcacities_s *)priv->cdb;
|
|
FAR struct scsiresp_readformatcapacities_s *hdr;
|
|
FAR struct usbmsc_lun_s *lun;
|
|
int ret;
|
|
|
|
priv->u.alloclen = usbmsc_getbe16(rfc->alloclen);
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_READFORMATCAPACITIES_SIZEOF,
|
|
USBMSC_FLAGS_DIRDEVICE2HOST);
|
|
if (ret == OK)
|
|
{
|
|
lun = priv->lun;
|
|
|
|
hdr = (FAR struct scsiresp_readformatcapacities_s *)buf;
|
|
memset(hdr, 0, SCSIRESP_READFORMATCAPACITIES_SIZEOF);
|
|
hdr->listlen = SCSIRESP_CURRCAPACITYDESC_SIZEOF;
|
|
|
|
/* Only the Current/Maximum Capacity Descriptor follows the header */
|
|
|
|
usbmsc_putbe32(hdr->nblocks, lun->nsectors);
|
|
hdr->type = SCIRESP_RDFMTCAPACITIES_FORMATED;
|
|
usbmsc_putbe24(hdr->blocklen, lun->sectorsize);
|
|
priv->nreqbytes = SCSIRESP_READFORMATCAPACITIES_SIZEOF;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdreadcapacity10
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_READCAPACITY10 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int inline usbmsc_cmdreadcapacity10(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf)
|
|
{
|
|
FAR struct scsicmd_readcapacity10_s *rcc =
|
|
(FAR struct scsicmd_readcapacity10_s *)priv->cdb;
|
|
FAR struct scsiresp_readcapacity10_s *rcr =
|
|
(FAR struct scsiresp_readcapacity10_s *)buf;
|
|
FAR struct usbmsc_lun_s *lun;
|
|
uint32_t lba;
|
|
int ret;
|
|
|
|
priv->u.alloclen = SCSIRESP_READCAPACITY10_SIZEOF; /* Fake the allocation length */
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_READCAPACITY10_SIZEOF,
|
|
USBMSC_FLAGS_DIRDEVICE2HOST);
|
|
if (ret == OK)
|
|
{
|
|
lun = priv->lun;
|
|
|
|
/* Check the PMI and LBA fields */
|
|
|
|
lba = usbmsc_getbe32(rcc->lba);
|
|
|
|
if (rcc->pmi > 1 || (rcc->pmi == 0 && lba != 0))
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_READCAPACITYFLAGS), 0);
|
|
lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
|
ret = -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
usbmsc_putbe32(rcr->lba, lun->nsectors - 1);
|
|
usbmsc_putbe32(&buf[4], lun->sectorsize);
|
|
priv->nreqbytes = SCSIRESP_READCAPACITY10_SIZEOF;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdread10
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_READ10 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdread10(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct scsicmd_read10_s *read10 =
|
|
(FAR struct scsicmd_read10_s *)priv->cdb;
|
|
FAR struct usbmsc_lun_s *lun;
|
|
int ret;
|
|
|
|
priv->u.xfrlen = usbmsc_getbe16(read10->xfrlen);
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_READ10_SIZEOF,
|
|
USBMSC_FLAGS_DIRDEVICE2HOST | USBMSC_FLAGS_BLOCKXFR);
|
|
if (ret == OK)
|
|
{
|
|
lun = priv->lun;
|
|
|
|
/* Get the Logical Block Address (LBA) from cdb[] as the starting
|
|
* sector
|
|
*/
|
|
|
|
priv->sector = usbmsc_getbe32(read10->lba);
|
|
|
|
/* Verify that we can support this read command */
|
|
|
|
if ((read10->flags & ~(SCSICMD_READ10FLAGS_DPO |
|
|
SCSICMD_READ10FLAGS_FUA)) != 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_READ10FLAGS), 0);
|
|
lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Verify that a block driver has been bound to the LUN */
|
|
|
|
else if (!lun->inode)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_READ10MEDIANOTPRESENT), 0);
|
|
lun->sd = SCSI_KCQNR_MEDIANOTPRESENT;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Verify that LBA lies in the range supported by the block driver */
|
|
|
|
else if (priv->sector >= lun->nsectors)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_READ10LBARANGE), 0);
|
|
lun->sd = SCSI_KCQIR_LBAOUTOFRANGE;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Looks like we are good to go */
|
|
|
|
else
|
|
{
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDPARSECMDREAD10),
|
|
priv->cdb[0]);
|
|
priv->thstate = USBMSC_STATE_CMDREAD;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdwrite10
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_WRITE10 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdwrite10(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct scsicmd_write10_s *write10 =
|
|
(FAR struct scsicmd_write10_s *)priv->cdb;
|
|
FAR struct usbmsc_lun_s *lun;
|
|
int ret;
|
|
|
|
priv->u.xfrlen = usbmsc_getbe16(write10->xfrlen);
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_WRITE10_SIZEOF,
|
|
USBMSC_FLAGS_DIRHOST2DEVICE | USBMSC_FLAGS_BLOCKXFR);
|
|
if (ret == OK)
|
|
{
|
|
lun = priv->lun;
|
|
|
|
/* Get the Logical Block Address (LBA) from cdb[] as the starting
|
|
* sector
|
|
*/
|
|
|
|
priv->sector = usbmsc_getbe32(write10->lba);
|
|
|
|
/* Verify that we can support this write command */
|
|
|
|
if ((write10->flags & ~(SCSICMD_WRITE10FLAGS_DPO |
|
|
SCSICMD_WRITE10FLAGS_FUA)) != 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_WRITE10FLAGS), 0);
|
|
lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Verify that a block driver has been bound to the LUN */
|
|
|
|
else if (!lun->inode)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_WRITE10MEDIANOTPRESENT),
|
|
0);
|
|
lun->sd = SCSI_KCQNR_MEDIANOTPRESENT;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Check for attempts to write to a read-only device */
|
|
|
|
else if (lun->readonly)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_WRITE10READONLY), 0);
|
|
lun->sd = SCSI_KCQWP_COMMANDNOTALLOWED;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Verify that LBA lies in the range supported by the block driver */
|
|
|
|
else if (priv->sector >= lun->nsectors)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_WRITE10LBARANGE), 0);
|
|
lun->sd = SCSI_KCQIR_LBAOUTOFRANGE;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Looks like we are good to go */
|
|
|
|
else
|
|
{
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDPARSECMDWRITE10),
|
|
priv->cdb[0]);
|
|
priv->thstate = USBMSC_STATE_CMDWRITE;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdverify10
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_VERIFY10 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdverify10(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct scsicmd_verify10_s *verf =
|
|
(FAR struct scsicmd_verify10_s *)priv->cdb;
|
|
FAR struct usbmsc_lun_s *lun;
|
|
uint32_t lba;
|
|
uint16_t blocks;
|
|
size_t sector;
|
|
ssize_t nread;
|
|
int ret;
|
|
int i;
|
|
|
|
priv->u.alloclen = 0;
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_VERIFY10_SIZEOF, USBMSC_FLAGS_DIRNONE);
|
|
if (ret == OK)
|
|
{
|
|
lun = priv->lun;
|
|
|
|
/* Verify the starting and ending LBA */
|
|
|
|
lba = usbmsc_getbe32(verf->lba);
|
|
blocks = usbmsc_getbe16(verf->len);
|
|
|
|
if ((verf->flags & ~SCSICMD_VERIFY10_DPO) != 0 || verf->groupno != 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_VERIFY10FLAGS), 0);
|
|
lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
|
ret = -EINVAL;
|
|
}
|
|
else if (blocks == 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_VERIFY10NOBLOCKS), 0);
|
|
ret = -EIO; /* No reply */
|
|
}
|
|
|
|
/* Verify that a block driver has been bound to the LUN */
|
|
|
|
else if (!lun->inode)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_VERIFY10MEDIANOTPRESENT),
|
|
0);
|
|
lun->sd = SCSI_KCQNR_MEDIANOTPRESENT;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Verify that LBA lies in the range supported by the block driver */
|
|
|
|
else if (lba >= lun->nsectors || lba + blocks > lun->nsectors)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_VERIFY10LBARANGE), 0);
|
|
lun->sd = SCSI_KCQIR_LBAOUTOFRANGE;
|
|
ret = -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
/* Try to read the requested blocks */
|
|
|
|
for (i = 0, sector = lba + lun->startsector;
|
|
i < blocks;
|
|
i++, sector++)
|
|
{
|
|
nread = USBMSC_DRVR_READ(lun, priv->iobuffer, sector, 1);
|
|
if (nread < 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_VERIFY10READFAIL),
|
|
i);
|
|
lun->sd = SCSI_KCQME_UNRRE1;
|
|
lun->sdinfo = sector;
|
|
ret = -EIO;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdsynchronizecache10
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_SYNCHCACHE10 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdsynchronizecache10(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
int ret;
|
|
|
|
priv->u.alloclen = 0;
|
|
|
|
/* Verify that we have the LUN structure and the block driver has been
|
|
* bound
|
|
*/
|
|
|
|
if (!priv->lun->inode)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_SYNCCACHEMEDIANOTPRESENT), 0);
|
|
priv->lun->sd = SCSI_KCQNR_MEDIANOTPRESENT;
|
|
ret = -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_SYNCHRONIZECACHE10_SIZEOF,
|
|
USBMSC_FLAGS_DIRNONE);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdmodeselect10
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_MODESELECT10 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdmodeselect10(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct scsicmd_modeselect10_s *modeselect =
|
|
(FAR struct scsicmd_modeselect10_s *)priv->cdb;
|
|
|
|
priv->u.alloclen = usbmsc_getbe16(modeselect->parmlen);
|
|
usbmsc_setupcmd(priv, SCSICMD_MODESELECT10_SIZEOF,
|
|
USBMSC_FLAGS_DIRHOST2DEVICE);
|
|
|
|
/* Not supported */
|
|
|
|
priv->lun->sd = SCSI_KCQIR_INVALIDCOMMAND;
|
|
return -EINVAL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdmodesense10
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_MODESENSE10 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int inline usbmsc_cmdmodesense10(FAR struct usbmsc_dev_s *priv,
|
|
FAR uint8_t *buf)
|
|
{
|
|
FAR struct scsicmd_modesense10_s *modesense =
|
|
(FAR struct scsicmd_modesense10_s *)priv->cdb;
|
|
FAR struct scsiresp_modeparameterhdr10_s *mph =
|
|
(FAR struct scsiresp_modeparameterhdr10_s *)buf;
|
|
int mdlen;
|
|
int ret;
|
|
|
|
priv->u.alloclen = usbmsc_getbe16(modesense->alloclen);
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_MODESENSE10_SIZEOF,
|
|
USBMSC_FLAGS_DIRDEVICE2HOST);
|
|
if (ret == OK)
|
|
{
|
|
if ((modesense->flags & ~SCSICMD_MODESENSE10_DBD) != 0 ||
|
|
modesense->subpgcode != 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_MODESENSE10FLAGS), 0);
|
|
priv->lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
|
ret = -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
/* The response consists of:
|
|
*
|
|
* (1) A MODESENSE6-specific mode parameter header,
|
|
* (2) A variable length list of block descriptors, and
|
|
* (3) A variable lengtth list of mode page formats
|
|
*/
|
|
|
|
memset(mph, 0, SCSIRESP_MODEPARAMETERHDR10_SIZEOF);
|
|
mph->param =
|
|
(priv->lun->readonly ? SCSIRESP_MODEPARMHDR_DAPARM_WP : 0x00);
|
|
|
|
/* There are no block descriptors, only the following mode page: */
|
|
|
|
ret = usbmsc_modepage(priv,
|
|
&buf[SCSIRESP_MODEPARAMETERHDR10_SIZEOF],
|
|
modesense->pcpgcode, &mdlen);
|
|
if (ret == OK)
|
|
{
|
|
/* Store the mode data length and return the total message
|
|
* size
|
|
*/
|
|
|
|
usbmsc_putbe16(mph->mdlen, mdlen - 2);
|
|
priv->nreqbytes = mdlen + SCSIRESP_MODEPARAMETERHDR10_SIZEOF;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdread12
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_READ12 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdread12(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct scsicmd_read12_s *read12 =
|
|
(FAR struct scsicmd_read12_s *)priv->cdb;
|
|
FAR struct usbmsc_lun_s *lun;
|
|
int ret;
|
|
|
|
priv->u.xfrlen = usbmsc_getbe32(read12->xfrlen);
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_READ12_SIZEOF,
|
|
USBMSC_FLAGS_DIRDEVICE2HOST | USBMSC_FLAGS_BLOCKXFR);
|
|
if (ret == OK)
|
|
{
|
|
lun = priv->lun;
|
|
|
|
/* Get the Logical Block Address (LBA) from cdb[] as the starting
|
|
* sector
|
|
*/
|
|
|
|
priv->sector = usbmsc_getbe32(read12->lba);
|
|
|
|
/* Verify that we can support this read command */
|
|
|
|
if ((read12->flags & ~(SCSICMD_READ12FLAGS_DPO |
|
|
SCSICMD_READ12FLAGS_FUA)) != 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_READ12FLAGS), 0);
|
|
lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Verify that a block driver has been bound to the LUN */
|
|
|
|
else if (!lun->inode)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_READ12MEDIANOTPRESENT), 0);
|
|
lun->sd = SCSI_KCQNR_MEDIANOTPRESENT;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Verify that LBA lies in the range supported by the block driver */
|
|
|
|
else if (priv->sector >= lun->nsectors)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_READ12LBARANGE), 0);
|
|
lun->sd = SCSI_KCQIR_LBAOUTOFRANGE;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Looks like we are good to go */
|
|
|
|
else
|
|
{
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDPARSECMDREAD12),
|
|
priv->cdb[0]);
|
|
priv->thstate = USBMSC_STATE_CMDREAD;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdwrite12
|
|
*
|
|
* Description:
|
|
* Handle SCSI_CMD_WRITE12 command
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbmsc_cmdwrite12(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct scsicmd_write12_s *write12 =
|
|
(FAR struct scsicmd_write12_s *)priv->cdb;
|
|
FAR struct usbmsc_lun_s *lun;
|
|
int ret;
|
|
|
|
priv->u.xfrlen = usbmsc_getbe32(write12->xfrlen);
|
|
ret = usbmsc_setupcmd(priv, SCSICMD_WRITE12_SIZEOF,
|
|
USBMSC_FLAGS_DIRHOST2DEVICE | USBMSC_FLAGS_BLOCKXFR);
|
|
if (ret == OK)
|
|
{
|
|
lun = priv->lun;
|
|
|
|
/* Get the Logical Block Address (LBA) from cdb[] as the starting
|
|
* sector
|
|
*/
|
|
|
|
priv->sector = usbmsc_getbe32(write12->lba);
|
|
|
|
/* Verify that we can support this write command */
|
|
|
|
if ((write12->flags & ~(SCSICMD_WRITE12FLAGS_DPO |
|
|
SCSICMD_WRITE12FLAGS_FUA)) != 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_WRITE12FLAGS), 0);
|
|
lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
|
}
|
|
|
|
/* Verify that a block driver has been bound to the LUN */
|
|
|
|
else if (!lun->inode)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_WRITE12MEDIANOTPRESENT),
|
|
0);
|
|
lun->sd = SCSI_KCQNR_MEDIANOTPRESENT;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Check for attempts to write to a read-only device */
|
|
|
|
else if (lun->readonly)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_WRITE12READONLY), 0);
|
|
lun->sd = SCSI_KCQWP_COMMANDNOTALLOWED;
|
|
}
|
|
|
|
/* Verify that LBA lies in the range supported by the block driver */
|
|
|
|
else if (priv->sector >= lun->nsectors)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_WRITE12LBARANGE), 0);
|
|
lun->sd = SCSI_KCQIR_LBAOUTOFRANGE;
|
|
}
|
|
|
|
/* Looks like we are good to go */
|
|
|
|
else
|
|
{
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDPARSECMDWRITE12),
|
|
priv->cdb[0]);
|
|
priv->thstate = USBMSC_STATE_CMDWRITE;
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_setupcmd
|
|
*
|
|
* Description:
|
|
* Called after each SCSI command is identified in order to perform setup
|
|
* and verification operations that are common to all SCSI commands. This
|
|
* function performs the following common setup operations:
|
|
*
|
|
* 1. Determine the direction of the response
|
|
* 2. Verify lengths
|
|
* 3. Setup and verify the LUN
|
|
*
|
|
* Includes special logic for INQUIRY and REQUESTSENSE commands
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int inline usbmsc_setupcmd(FAR struct usbmsc_dev_s *priv,
|
|
uint8_t cdblen, uint8_t flags)
|
|
{
|
|
FAR struct usbmsc_lun_s *lun = NULL;
|
|
uint32_t datlen;
|
|
uint8_t dir;
|
|
int ret = OK;
|
|
|
|
/* Verify the LUN and set up the current LUN reference in the
|
|
* device structure
|
|
*/
|
|
|
|
if (priv->cbwlun < priv->nluns)
|
|
{
|
|
/* LUN number is valid in a valid range, but the LUN is not necessarily
|
|
* bound to a block driver. That will be checked as necessary in each
|
|
* individual command.
|
|
*/
|
|
|
|
lun = &priv->luntab[priv->cbwlun];
|
|
priv->lun = lun;
|
|
}
|
|
|
|
/* Only a few commands may specify unsupported LUNs */
|
|
|
|
else if ((flags & USBMSC_FLAGS_LUNNOTNEEDED) == 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDBADLUN), priv->cbwlun);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Extract the direction and data transfer length */
|
|
|
|
dir = flags & USBMSC_FLAGS_DIRMASK; /* Expected data direction */
|
|
datlen = 0;
|
|
if ((flags & USBMSC_FLAGS_BLOCKXFR) == 0)
|
|
{
|
|
/* Non-block transfer. Data length: Host allocation to receive data
|
|
* (only for device-to-host transfers. At present, alloclen is set
|
|
* to zero for all host-to-device, non-block transfers.
|
|
*/
|
|
|
|
datlen = priv->u.alloclen;
|
|
}
|
|
else if (lun)
|
|
{
|
|
/* Block transfer: Calculate the total size of all sectors to be
|
|
* transferred
|
|
*/
|
|
|
|
datlen = priv->u.alloclen * lun->sectorsize;
|
|
}
|
|
|
|
/* Check the data direction. This was set up at the end of the
|
|
* IDLE state when the CBW was parsed, but if there is no data,
|
|
* then the direction is none.
|
|
*/
|
|
|
|
if (datlen == 0)
|
|
{
|
|
/* No data.. then direction is none */
|
|
|
|
dir = USBMSC_FLAGS_DIRNONE;
|
|
}
|
|
|
|
/* Compare the expected data length in the command to the data length
|
|
* in the CBW.
|
|
*/
|
|
|
|
else if (priv->cbwlen < datlen)
|
|
{
|
|
/* Clip to the length in the CBW and declare a phase error */
|
|
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_PHASEERROR1), priv->cdb[0]);
|
|
if ((flags & USBMSC_FLAGS_BLOCKXFR) == 0)
|
|
{
|
|
priv->u.alloclen = priv->cbwlen;
|
|
}
|
|
else if (lun)
|
|
{
|
|
priv->u.xfrlen = priv->cbwlen / lun->sectorsize;
|
|
}
|
|
|
|
priv->phaseerror = 1;
|
|
}
|
|
|
|
/* Initialize the residue */
|
|
|
|
priv->residue = priv->cbwlen;
|
|
|
|
/* Conflicting data directions is a phase error */
|
|
|
|
if (priv->cbwdir != dir && datlen > 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_PHASEERROR2), priv->cdb[0]);
|
|
priv->phaseerror = 1;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Compare the length of data in the cdb[] with the expected length
|
|
* of the command. These sizes should match exactly.
|
|
*/
|
|
|
|
if (cdblen != priv->cdblen)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_PHASEERROR3), priv->cdb[0]);
|
|
priv->phaseerror = 1;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Was a valid LUN provided? */
|
|
|
|
if (lun)
|
|
{
|
|
/* Retain the sense data for the REQUEST SENSE command */
|
|
|
|
if ((flags & USBMSC_FLAGS_RETAINSENSEDATA) == 0)
|
|
{
|
|
/* Discard the sense data */
|
|
|
|
lun->sd = SCSI_KCQ_NOSENSE;
|
|
lun->sdinfo = 0;
|
|
}
|
|
|
|
/* If a unit attention condition exists, then only a restricted set of
|
|
* commands is permitted.
|
|
*/
|
|
|
|
if (lun->uad != SCSI_KCQ_NOSENSE &&
|
|
(flags & USBMSC_FLAGS_UACOKAY) != 0)
|
|
{
|
|
/* Command not permitted */
|
|
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDUNEVIOLATION),
|
|
priv->cbwlun);
|
|
lun->sd = lun->uad;
|
|
lun->uad = SCSI_KCQ_NOSENSE;
|
|
ret = -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* The final, 1-byte field of every SCSI command is the Control field which
|
|
* must be zero
|
|
*/
|
|
|
|
if (priv->cdb[cdblen - 1] != 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_SCSICMDCONTROL), 0);
|
|
if (lun)
|
|
{
|
|
lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
|
}
|
|
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_idlestate
|
|
*
|
|
* Description:
|
|
* Called from the worker thread in the USBMSC_STATE_IDLE state. Checks
|
|
* for the receipt of a bulk CBW.
|
|
*
|
|
* Returned Value:
|
|
* If no new, valid CBW is available, this function returns a negated
|
|
* errno value. Otherwise, when a new CBW is successfully parsed, this
|
|
* function sets priv->thstate to USBMSC_STATE_CMDPARSE and returns OK.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int usbmsc_idlestate(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct usbmsc_req_s *privreq;
|
|
FAR struct usbdev_req_s *req;
|
|
FAR struct usbmsc_cbw_s *cbw;
|
|
irqstate_t flags;
|
|
int ret = -EINVAL;
|
|
|
|
/* Take a request from the rdreqlist */
|
|
|
|
flags = enter_critical_section();
|
|
privreq = (FAR struct usbmsc_req_s *)sq_remfirst(&priv->rdreqlist);
|
|
leave_critical_section(flags);
|
|
|
|
/* Has anything been received? If not, just return an error.
|
|
* This will cause us to remain in the IDLE state. When a USB request is
|
|
* received, the worker thread will be awakened in the USBMSC_STATE_IDLE
|
|
* and we will be called again.
|
|
*/
|
|
|
|
if (!privreq)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_IDLERDREQLISTEMPTY), 0);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
req = privreq->req;
|
|
cbw = (FAR struct usbmsc_cbw_s *)req->buf;
|
|
|
|
/* Handle the CBW */
|
|
|
|
usbmsc_dumpdata("SCSCI CBW", (FAR uint8_t *)cbw, USBMSC_CBW_SIZEOF -
|
|
USBMSC_MAXCDBLEN);
|
|
usbmsc_dumpdata(" CDB", cbw->cdb, MIN(cbw->cdblen, USBMSC_MAXCDBLEN));
|
|
|
|
/* Check for properly formatted CBW? */
|
|
|
|
if (req->xfrd != USBMSC_CBW_SIZEOF ||
|
|
cbw->signature[0] != 'U' ||
|
|
cbw->signature[1] != 'S' ||
|
|
cbw->signature[2] != 'B' ||
|
|
cbw->signature[3] != 'C')
|
|
{
|
|
/* CBS BAD: Stall the bulk endpoints. If the CBW is bad we must stall
|
|
* the bulk IN endpoint and either (1) stall the bulk OUT endpoint, or
|
|
* (2) discard data from the endpoint.
|
|
*/
|
|
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_INVALIDCBWSIGNATURE), 0);
|
|
EP_STALL(priv->epbulkout);
|
|
EP_STALL(priv->epbulkin);
|
|
}
|
|
|
|
/* Is the CBW meaningful? */
|
|
|
|
else if (cbw->lun >= priv->nluns ||
|
|
(cbw->flags & ~USBMSC_CBWFLAG_IN) != 0 ||
|
|
cbw->cdblen < 6 ||
|
|
cbw->cdblen > USBMSC_MAXCDBLEN)
|
|
{
|
|
/* CBS BAD: Stall the bulk endpoints. If the CBW is bad we must stall
|
|
* the bulk IN endpoint and either (1) stall the bulk OUT endpoint, or
|
|
* (2) discard data from the endpoint.
|
|
*/
|
|
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_INVALIDCBWCONTENT), 0);
|
|
EP_STALL(priv->epbulkout);
|
|
EP_STALL(priv->epbulkin);
|
|
}
|
|
|
|
/* Save the information from the CBW */
|
|
|
|
else
|
|
{
|
|
/* Extract the CDB and copy the whole CBD[] for later use */
|
|
|
|
priv->cdblen = cbw->cdblen;
|
|
memcpy(priv->cdb, cbw->cdb, priv->cdblen);
|
|
|
|
/* Extract the data direction */
|
|
|
|
if ((cbw->flags & USBMSC_CBWFLAG_IN) != 0)
|
|
{
|
|
/* IN: Device-to-host */
|
|
|
|
priv->cbwdir = USBMSC_FLAGS_DIRDEVICE2HOST;
|
|
}
|
|
else
|
|
{
|
|
/* OUT: Host-to-device */
|
|
|
|
priv->cbwdir = USBMSC_FLAGS_DIRHOST2DEVICE;
|
|
}
|
|
|
|
/* Get the max size of the data response */
|
|
|
|
priv->cbwlen = usbmsc_getle32(cbw->datlen);
|
|
if (priv->cbwlen == 0)
|
|
{
|
|
/* No length? Then no direction either */
|
|
|
|
priv->cbwdir = USBMSC_FLAGS_DIRNONE;
|
|
}
|
|
|
|
/* Extract and save the LUN index and TAG value */
|
|
|
|
priv->cbwlun = cbw->lun;
|
|
priv->cbwtag = usbmsc_getle32(cbw->tag);
|
|
|
|
/* Return the read request to the bulk out endpoint for re-filling */
|
|
|
|
req = privreq->req;
|
|
req->len = priv->epbulkout->maxpacket;
|
|
req->priv = privreq;
|
|
req->callback = usbmsc_rdcomplete;
|
|
|
|
/* Change to the CMDPARSE state and return success */
|
|
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_IDLECMDPARSE),
|
|
priv->cdb[0]);
|
|
priv->thstate = USBMSC_STATE_CMDPARSE;
|
|
ret = OK;
|
|
}
|
|
|
|
/* In any event, return the request to be refilled */
|
|
|
|
if (EP_SUBMIT(priv->epbulkout, req) != OK)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_IDLERDSUBMIT), (uint16_t)-ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdparsestate
|
|
*
|
|
* Description:
|
|
* Called from the worker thread in the USBMSC_STATE_CMDPARSE state.
|
|
* This state is entered when usbmsc_idlestate obtains a valid CBW
|
|
* containing SCSI commands. This function processes those SCSI commands.
|
|
*
|
|
* Returned Value:
|
|
* If no write request is available or certain other errors occur, this
|
|
* function returns a negated errno and stays in the USBMSC_STATE_CMDPARSE
|
|
* state. Otherwise, when the new CBW is successfully process, this
|
|
* function sets priv->thstate to one of USBMSC_STATE_CMDREAD,
|
|
* USBMSC_STATE_CMDWRITE, or USBMSC_STATE_CMDFINISH and returns OK.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int usbmsc_cmdparsestate(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct usbmsc_req_s *privreq;
|
|
FAR uint8_t *buf;
|
|
int ret;
|
|
|
|
usbmsc_dumpdata("SCSCI CDB", priv->cdb, priv->cdblen);
|
|
|
|
/* Check if there is a request in the wrreqlist that we will be able to
|
|
* use for data or status.
|
|
*/
|
|
|
|
privreq = (FAR struct usbmsc_req_s *)sq_peek(&priv->wrreqlist);
|
|
|
|
/* If there no request structures available, then just return an error.
|
|
* This will cause us to remain in the CMDPARSE state. When a request is
|
|
* returned, the worker thread will be awakened in the
|
|
* USBMSC_STATE_CMDPARSE and we will be called again.
|
|
*/
|
|
|
|
if (!privreq)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDPARSEWRREQLISTEMPTY), 0);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
DEBUGASSERT(privreq->req && privreq->req->buf);
|
|
buf = privreq->req->buf;
|
|
|
|
/* Assume that no errors will be encountered */
|
|
|
|
priv->phaseerror = 0;
|
|
priv->shortpacket = 0;
|
|
|
|
/* No data is buffered */
|
|
|
|
priv->nsectbytes = 0;
|
|
priv->nreqbytes = 0;
|
|
|
|
/* Get exclusive access to the block driver */
|
|
|
|
ret = nxmutex_lock(&priv->thlock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = -EINVAL;
|
|
|
|
switch (priv->cdb[0])
|
|
{
|
|
case SCSI_CMD_TESTUNITREADY: /* 0x00 Mandatory */
|
|
ret = usbmsc_cmdtestunitready(priv);
|
|
break;
|
|
|
|
/* case SCSI_CMD_REZEROUNIT: 0x01 Obsolete
|
|
* 0x02 Vendor-specific
|
|
*/
|
|
|
|
case SCSI_CMD_REQUESTSENSE: /* 0x03 Mandatory */
|
|
ret = usbmsc_cmdrequestsense(priv, buf);
|
|
break;
|
|
|
|
/* case SCSI_CMD_FORMAT_UNIT: 0x04 Mandatory, but not impl.
|
|
* 0x05 Vendor specific
|
|
* 0x06 Vendor specific
|
|
* case SCSI_CMD_REASSIGNBLOCKS: 0x07 Optional
|
|
*/
|
|
|
|
case SCSI_CMD_READ6: /* 0x08 Mandatory */
|
|
ret = usbmsc_cmdread6(priv);
|
|
break;
|
|
|
|
/* 0x09 Vendor specific */
|
|
|
|
case SCSI_CMD_WRITE6: /* 0x0a Optional */
|
|
ret = usbmsc_cmdwrite6(priv);
|
|
break;
|
|
|
|
/* case SCSI_CMD_SEEK6: 0x0b Obsolete
|
|
* 0x0c-0x10 Vendor specific
|
|
* case SCSI_CMD_SPACE6: 0x11 Vendor specific
|
|
*/
|
|
|
|
case SCSI_CMD_INQUIRY: /* 0x12 Mandatory */
|
|
ret = usbmsc_cmdinquiry(priv, buf);
|
|
break;
|
|
|
|
/* 0x13-0x14 Vendor specific */
|
|
|
|
case SCSI_CMD_MODESELECT6: /* 0x15 Optional */
|
|
ret = usbmsc_cmdmodeselect6(priv);
|
|
break;
|
|
|
|
/* case SCSI_CMD_RESERVE6: 0x16 Obsolete
|
|
* case SCSI_CMD_RELEASE6: 0x17 Obsolete
|
|
* case SCSI_CMD_COPY: 0x18 Obsolete
|
|
* 0x19 Vendor specific
|
|
*/
|
|
|
|
case SCSI_CMD_MODESENSE6: /* 0x1a Optional */
|
|
ret = usbmsc_cmdmodesense6(priv, buf);
|
|
break;
|
|
|
|
case SCSI_CMD_STARTSTOPUNIT: /* 0x1b Optional */
|
|
ret = usbmsc_cmdstartstopunit(priv);
|
|
break;
|
|
|
|
/* case SCSI_CMD_RECEIVEDIAGNOSTICRESULTS: 0x1c Optional
|
|
* case SCSI_CMD_SENDDIAGNOSTIC: 0x1d Mandatory, but not impl.
|
|
*/
|
|
|
|
case SCSI_CMD_PREVENTMEDIAREMOVAL: /* 0x1e Optional */
|
|
ret = usbmsc_cmdpreventmediumremoval(priv);
|
|
break;
|
|
|
|
/* 0x20-22 Vendor specific */
|
|
|
|
case SCSI_CMD_READFORMATCAPACITIES: /* 0x23 Vend-spec (def. MMC spec) */
|
|
ret = usbmsc_cmdreadformatcapacity(priv, buf);
|
|
break;
|
|
|
|
/* 0x24 Vendor specific */
|
|
|
|
case SCSI_CMD_READCAPACITY10: /* 0x25 Mandatory */
|
|
ret = usbmsc_cmdreadcapacity10(priv, buf);
|
|
break;
|
|
|
|
/* 0x26-27 Vendor specific */
|
|
|
|
case SCSI_CMD_READ10: /* 0x28 Mandatory */
|
|
ret = usbmsc_cmdread10(priv);
|
|
break;
|
|
|
|
/* 0x29 Vendor specific */
|
|
|
|
case SCSI_CMD_WRITE10: /* 0x2a Optional */
|
|
ret = usbmsc_cmdwrite10(priv);
|
|
break;
|
|
|
|
/* case SCSI_CMD_SEEK10: 0x2b Obsolete
|
|
* 0x2c-2d Vendor specific
|
|
* case SCSI_CMD_WRITEANDVERIFY: 0x2e Optional
|
|
*/
|
|
|
|
case SCSI_CMD_VERIFY10: /* 0x2f Opt, excpt Windows */
|
|
ret = usbmsc_cmdverify10(priv);
|
|
break;
|
|
|
|
/* case SCSI_CMD_SEARCHDATAHIGH: 0x30 Obsolete
|
|
* case SCSI_CMD_SEARCHDATAEQUAL: 0x31 Obsolete
|
|
* case SCSI_CMD_SEARCHDATALOW: 0x32 Obsolete
|
|
* case SCSI_CMD_SETLIMITS10: 0x33 Obsolete
|
|
* case SCSI_CMD_PREFETCH10: 0x34 Optional
|
|
*/
|
|
|
|
case SCSI_CMD_SYNCHCACHE10: /* 0x35 Optional */
|
|
ret = usbmsc_cmdsynchronizecache10(priv);
|
|
break;
|
|
|
|
/* case SCSI_CMD_LOCKCACHE: 0x36 Obsolete
|
|
* case SCSI_CMD_READDEFECTDATA10: 0x37 Optional
|
|
* case SCSI_CMD_COMPARE: 0x39 Obsolete
|
|
* case SCSI_CMD_COPYANDVERIFY: 0x3a Obsolete
|
|
* case SCSI_CMD_WRITEBUFFER: 0x3b Optional
|
|
* case SCSI_CMD_READBUFFER: 0x3c Optional
|
|
* case SCSI_CMD_READLONG10: 0x3e Optional
|
|
* case SCSI_CMD_WRITELONG10: 0x3f Optional
|
|
* case SCSI_CMD_CHANGEDEFINITION: 0x40 Obsolete
|
|
* case SCSI_CMD_WRITESAME10: 0x41 Optional
|
|
* case SCSI_CMD_LOGSELECT: 0x4c Optional
|
|
* case SCSI_CMD_LOGSENSE: 0x4d Optional
|
|
* case SCSI_CMD_XDWRITE10: 0x50 Optional
|
|
* case SCSI_CMD_XPWRITE10: 0x51 Optional
|
|
* case SCSI_CMD_XDREAD10: 0x52 Optional
|
|
*/
|
|
|
|
case SCSI_CMD_MODESELECT10: /* 0x55 Optional */
|
|
ret = usbmsc_cmdmodeselect10(priv);
|
|
break;
|
|
|
|
/* case SCSI_CMD_RESERVE10: 0x56 Obsolete
|
|
* case SCSI_CMD_RELEASE10: 0x57 Obsolete
|
|
*/
|
|
|
|
case SCSI_CMD_MODESENSE10: /* 0x5a Optional */
|
|
ret = usbmsc_cmdmodesense10(priv, buf);
|
|
break;
|
|
|
|
/* case SCSI_CMD_PERSISTENTRESERVEIN: 0x5e Optional
|
|
* case SCSI_CMD_PERSISTENTRESERVEOUT: 0x5f Optional
|
|
* case SCSI_CMD_32: 0x7f Optional
|
|
* case SCSI_CMD_XDWRITEEXTENDED: 0x80 Obsolete
|
|
* case SCSI_CMD_REBUILD: 0x81 Obsolete
|
|
* case SCSI_CMD_REGENERATE: 0x82 Obsolete
|
|
* case SCSI_CMD_EXTENDEDCOPY: 0x83 Optional
|
|
* case SCSI_CMD_COPYRESULTS: 0x84 Optional
|
|
* case SCSI_CMD_ACCESSCONTROLIN: 0x86 Optional
|
|
* case SCSI_CMD_ACCESSCONTROLOUT: 0x87 Optional
|
|
* case SCSI_CMD_READ16: 0x88 Optional
|
|
* case SCSI_CMD_WRITE16: 0x8a Optional
|
|
* case SCSI_CMD_READATTRIBUTE: 0x8c Optional
|
|
* case SCSI_CMD_WRITEATTRIBUTE: 0x8d Optional
|
|
* case SCSI_CMD_WRITEANDVERIFY16: 0x8e Optional
|
|
* case SCSI_CMD_SYNCHCACHE16: 0x91 Optional
|
|
* case SCSI_CMD_LOCKUNLOCKACACHE: 0x92 Optional
|
|
* case SCSI_CMD_WRITESAME16: 0x93 Optional
|
|
* case SCSI_CMD_READCAPACITY16: 0x9e Optional
|
|
* case SCSI_CMD_READLONG16: 0x9e Optional
|
|
* case SCSI_CMD_WRITELONG16 0x9f Optional
|
|
* case SCSI_CMD_REPORTLUNS: 0xa0 Mandatory, but no-impl
|
|
* case SCSI_CMD_MAINTENANCEIN: 0xa3 Optional (SCCS==0)
|
|
* case SCSI_CMD_MAINTENANCEOUT: 0xa4 Optional (SCCS==0)
|
|
* case SCSI_CMD_MOVEMEDIUM: 0xa5 ?
|
|
* case SCSI_CMD_MOVEMEDIUMATTACHED: 0xa7 Optional (MCHNGR==0)
|
|
*/
|
|
|
|
case SCSI_CMD_READ12: /* 0xa8 Optional */
|
|
ret = usbmsc_cmdread12(priv);
|
|
break;
|
|
|
|
case SCSI_CMD_WRITE12: /* 0xaa Optional */
|
|
ret = usbmsc_cmdwrite12(priv);
|
|
break;
|
|
|
|
/* case SCSI_CMD_READMEDIASERIALNUMBER: 0xab Optional
|
|
* case SCSI_CMD_WRITEANDVERIFY12: 0xae Optional
|
|
* case SCSI_CMD_VERIFY12: 0xaf Optional
|
|
* case SCSI_CMD_SETLIMITS12 0xb3 Obsolete
|
|
* case SCSI_CMD_READELEMENTSTATUS: 0xb4 Optional (MCHNGR==0)
|
|
* case SCSI_CMD_READDEFECTDATA12: 0xb7 Optional
|
|
* case SCSI_CMD_REDUNDANCYGROUPIN: 0xba Optional
|
|
* case SCSI_CMD_REDUNDANCYGROUPOUT: 0xbb Optional
|
|
* case SCSI_CMD_SPAREIN: 0xbc Optional (SCCS==0)
|
|
* case SCSI_CMD_SPAREOUT: 0xbd Optional (SCCS==0)
|
|
* case SCSI_CMD_VOLUMESETIN: 0xbe Optional (SCCS==0)
|
|
* case SCSI_CMD_VOLUMESETOUT: 0xbe Optional (SCCS==0)
|
|
* 0xc0-0xff Vendor specific
|
|
*/
|
|
|
|
default:
|
|
priv->u.alloclen = 0;
|
|
priv->residue = priv->cbwlen;
|
|
priv->lun->sd = SCSI_KCQIR_INVALIDCOMMAND;
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
nxmutex_unlock(&priv->thlock);
|
|
|
|
/* Is a response required? (Not for read6/10/12 and write6/10/12). */
|
|
|
|
if (priv->thstate == USBMSC_STATE_CMDPARSE)
|
|
{
|
|
/* All commands come through this path (EXCEPT read6/10/12 and
|
|
* write6/10/12). For all other commands, the following setup is
|
|
* expected for the response based on data direction:
|
|
*
|
|
* For direction NONE:
|
|
* 1. priv->u.alloclen == 0
|
|
* 2. priv->nreqbytes == 0
|
|
*
|
|
* For direction = device-to-host:
|
|
* 1. priv->u.alloclen == allocation length; space set aside by the
|
|
* host to receive the device data. The size of the response
|
|
* cannot exceed this value.
|
|
* 2. response data is in the request currently at the head of
|
|
* the priv->wrreqlist queue. priv->nreqbytes is set to the
|
|
* length of data in the response.
|
|
*
|
|
* For direction host-to-device
|
|
* At present, there are no supported commands that should have
|
|
* host-to-device transfers (except write6/10/12 and that command
|
|
* logic does not take this path. The 'residue' is left at the full
|
|
* host-to-device data size.
|
|
*
|
|
* For all:
|
|
* ret set to <0 if an error occurred in parsing the commands.
|
|
*/
|
|
|
|
/* For from device-to-hose operations, the residue is the expected size
|
|
* (the initial value of 'residue') minus the amount actually returned
|
|
* in the response:
|
|
*/
|
|
|
|
if (priv->cbwdir == USBMSC_FLAGS_DIRDEVICE2HOST)
|
|
{
|
|
/* The number of bytes in the response cannot exceed the host
|
|
* 'allocation length' in the command.
|
|
*/
|
|
|
|
if (priv->nreqbytes > priv->u.alloclen)
|
|
{
|
|
priv->nreqbytes = priv->u.alloclen;
|
|
}
|
|
|
|
/* The residue is then the number of bytes that were not sent */
|
|
|
|
priv->residue -= priv->nreqbytes;
|
|
}
|
|
|
|
/* On return, we need the following:
|
|
*
|
|
* 1. Setup for CMDFINISH state if appropriate
|
|
* 2. priv->thstate set to either CMDPARSE if no buffer was
|
|
* available or CMDFINISH to send the response
|
|
* 3. Return OK to continue; <0 to wait for the next event
|
|
*/
|
|
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDPARSECMDFINISH),
|
|
priv->cdb[0]);
|
|
priv->thstate = USBMSC_STATE_CMDFINISH;
|
|
ret = OK;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdreadstate
|
|
*
|
|
* Description:
|
|
* Called from the worker thread in the USBMSC_STATE_CMDREAD state.
|
|
* The USBMSC_STATE_CMDREAD state is entered when usbmsc_cmdparsestate
|
|
* obtains a valid SCSI read command. This state is really a continuation
|
|
* of the USBMSC_STATE_CMDPARSE state that handles extended SCSI read
|
|
* command handling.
|
|
*
|
|
* Returned Value:
|
|
* If no USBDEV write request is available or certain other errors occur,
|
|
* this function returns a negated errno and stays in the
|
|
* USBMSC_STATE_CMDREAD state. Otherwise, when the new SCSI read command
|
|
* is fully processed, this function sets priv->thstate to
|
|
* USBMSC_STATE_CMDFINISH and returns OK.
|
|
*
|
|
* State variables:
|
|
* xfrlen - holds the number of sectors read to be read.
|
|
* sector - holds the sector number of the next sector to be read
|
|
* nsectbytes - holds the number of bytes buffered for the current sector
|
|
* nreqbytes - holds the number of bytes currently buffered in the request
|
|
* at the head of the wrreqlist.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int usbmsc_cmdreadstate(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct usbmsc_lun_s *lun = priv->lun;
|
|
FAR struct usbmsc_req_s *privreq;
|
|
FAR struct usbdev_req_s *req;
|
|
irqstate_t flags;
|
|
ssize_t nread;
|
|
FAR uint8_t *src;
|
|
FAR uint8_t *dest;
|
|
int nbytes;
|
|
int ret;
|
|
|
|
/* Loop transferring data until either (1) all of the data has been
|
|
* transferred, or (2) we have used up all of the write requests that we
|
|
* have available.
|
|
*/
|
|
|
|
while (priv->u.xfrlen > 0 || priv->nsectbytes > 0)
|
|
{
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDREAD), priv->u.xfrlen);
|
|
|
|
/* Is the I/O buffer empty? */
|
|
|
|
if (priv->nsectbytes <= 0)
|
|
{
|
|
/* Yes.. read the next sector */
|
|
|
|
nread = USBMSC_DRVR_READ(lun, priv->iobuffer, priv->sector, 1);
|
|
if (nread < 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDREADREADFAIL),
|
|
-nread);
|
|
lun->sd = SCSI_KCQME_UNRRE1;
|
|
lun->sdinfo = priv->sector;
|
|
break;
|
|
}
|
|
|
|
priv->nsectbytes = lun->sectorsize;
|
|
priv->u.xfrlen--;
|
|
priv->sector++;
|
|
}
|
|
|
|
/* Check if there is a request in the wrreqlist that we will be able to
|
|
* use for data transfer.
|
|
*/
|
|
|
|
privreq = (FAR struct usbmsc_req_s *)sq_peek(&priv->wrreqlist);
|
|
|
|
/* If there no request structures available, then just return an
|
|
* error. This will cause us to remain in the CMDREAD state. When a
|
|
* request is returned, the worker thread will be awakened in the
|
|
* USBMSC_STATE_CMDREAD and we will be called again.
|
|
*/
|
|
|
|
if (!privreq)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDREADWRRQEMPTY), 0);
|
|
priv->nreqbytes = 0;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
req = privreq->req;
|
|
|
|
/* Transfer all of the data that will (1) fit into the request buffer,
|
|
* OR (2) all of the data available in the sector buffer.
|
|
*/
|
|
|
|
src = &priv->iobuffer[lun->sectorsize - priv->nsectbytes];
|
|
dest = &req->buf[priv->nreqbytes];
|
|
|
|
nbytes = MIN(priv->epbulkin->maxpacket - priv->nreqbytes,
|
|
priv->nsectbytes);
|
|
|
|
/* Copy the data from the sector buffer to the USB request and update
|
|
* counts
|
|
*/
|
|
|
|
memcpy(dest, src, nbytes);
|
|
priv->nreqbytes += nbytes;
|
|
priv->nsectbytes -= nbytes;
|
|
|
|
/* If (1) the request buffer is full OR (2) this is the final request
|
|
* full of data,
|
|
* then submit the request
|
|
*/
|
|
|
|
if (priv->nreqbytes >= priv->epbulkin->maxpacket ||
|
|
(priv->u.xfrlen <= 0 && priv->nsectbytes <= 0))
|
|
{
|
|
/* Remove the request that we just filled from wrreqlist (we've
|
|
* already checked that is it not NULL
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
privreq = (FAR struct usbmsc_req_s *)sq_remfirst(&priv->wrreqlist);
|
|
leave_critical_section(flags);
|
|
|
|
/* And submit the request to the bulk IN endpoint */
|
|
|
|
req->len = priv->nreqbytes;
|
|
req->priv = privreq;
|
|
req->callback = usbmsc_wrcomplete;
|
|
req->flags = 0;
|
|
|
|
ret = EP_SUBMIT(priv->epbulkin, req);
|
|
if (ret != OK)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDREADSUBMIT),
|
|
(uint16_t)-ret);
|
|
lun->sd = SCSI_KCQME_UNRRE1;
|
|
lun->sdinfo = priv->sector;
|
|
break;
|
|
}
|
|
|
|
/* Assume success... residue should probably really be decremented
|
|
* in wrcomplete when we know that the transfer completed
|
|
* successfully.
|
|
*/
|
|
|
|
priv->residue -= priv->nreqbytes;
|
|
priv->nreqbytes = 0;
|
|
}
|
|
}
|
|
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDREADCMDFINISH),
|
|
priv->u.xfrlen);
|
|
priv->thstate = USBMSC_STATE_CMDFINISH;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdwritestate
|
|
*
|
|
* Description:
|
|
* Called from the worker thread in the USBMSC_STATE_CMDWRITE state.
|
|
* The USBMSC_STATE_CMDWRITE state is entered when usbmsc_cmdparsestate
|
|
* obtains a valid SCSI write command. This state is really a continuation
|
|
* of the USBMSC_STATE_CMDPARSE state that handles extended SCSI write
|
|
* command handling.
|
|
*
|
|
* Returned Value:
|
|
* If no USBDEV write request is available or certain other errors occur,
|
|
* this function returns a negated errno and stays in the
|
|
* USBMSC_STATE_CMDWRITE state. Otherwise, when the new SCSI write
|
|
* command is fully processed, this function sets priv->thstate to
|
|
* USBMSC_STATE_CMDFINISH and returns OK.
|
|
*
|
|
* State variables:
|
|
* xfrlen - holds the number of sectors read to be written.
|
|
* sector - holds the sector number of the next sector to write
|
|
* nsectbytes - holds the number of bytes buffered for the current sector
|
|
* nreqbytes - holds the number of untransferred bytes currently in the
|
|
* request at the head of the rdreqlist.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int usbmsc_cmdwritestate(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct usbmsc_lun_s *lun = priv->lun;
|
|
FAR struct usbmsc_req_s *privreq;
|
|
FAR struct usbdev_req_s *req;
|
|
ssize_t nwritten;
|
|
uint16_t xfrd;
|
|
FAR uint8_t *src;
|
|
FAR uint8_t *dest;
|
|
int nbytes;
|
|
int ret;
|
|
|
|
/* Loop transferring data until either (1) all of the data has been
|
|
* transferred, or (2) we have written all of the data in the available
|
|
* read requests.
|
|
*/
|
|
|
|
while (priv->u.xfrlen > 0)
|
|
{
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDWRITE), priv->u.xfrlen);
|
|
|
|
/* Check if there is a request in the rdreqlist containing additional
|
|
* data to be written.
|
|
*/
|
|
|
|
irqstate_t flags = enter_critical_section();
|
|
privreq = (FAR struct usbmsc_req_s *)sq_remfirst(&priv->rdreqlist);
|
|
leave_critical_section(flags);
|
|
|
|
/* If there no request data available, then just return an error.
|
|
* This will cause us to remain in the CMDWRITE state. When a filled
|
|
* request is received, the worker thread will be awakened in the
|
|
* USBMSC_STATE_CMDWRITE and we will be called again.
|
|
*/
|
|
|
|
if (!privreq)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDWRITERDRQEMPTY), 0);
|
|
priv->nreqbytes = 0;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
req = privreq->req;
|
|
xfrd = req->xfrd;
|
|
priv->nreqbytes = xfrd;
|
|
|
|
/* Now loop until all of the data in the read request has been
|
|
* transferred to the block driver OR all of the request data has been
|
|
* transferred.
|
|
*/
|
|
|
|
while (priv->nreqbytes > 0 && priv->u.xfrlen > 0)
|
|
{
|
|
/* Copy the data received in the read request into the sector I/O
|
|
* buffer
|
|
*/
|
|
|
|
src = &req->buf[xfrd - priv->nreqbytes];
|
|
dest = &priv->iobuffer[priv->nsectbytes];
|
|
|
|
#ifdef CONFIG_USBMSC_WRMULTIPLE
|
|
/* nbytes may end up being zero, after which the loop no longer
|
|
* proceeds but will be stuck forever. Make sure nbytes isn't
|
|
* zero.
|
|
*/
|
|
|
|
if (lun->sectorsize > priv->nsectbytes)
|
|
{
|
|
nbytes = MIN(lun->sectorsize - priv->nsectbytes,
|
|
priv->nreqbytes);
|
|
}
|
|
else
|
|
{
|
|
nbytes = priv->nreqbytes;
|
|
}
|
|
#else
|
|
nbytes = MIN(lun->sectorsize - priv->nsectbytes, priv->nreqbytes);
|
|
#endif
|
|
/* Copy the data from the sector buffer to the USB request and
|
|
* update counts
|
|
*/
|
|
|
|
memcpy(dest, src, nbytes);
|
|
priv->nsectbytes += nbytes;
|
|
priv->nreqbytes -= nbytes;
|
|
|
|
#ifdef CONFIG_USBMSC_WRMULTIPLE
|
|
uint32_t nrbufs = MIN(priv->u.xfrlen, CONFIG_USBMSC_NWRREQS);
|
|
|
|
/* Is the I/O buffer full? */
|
|
|
|
if ((priv->nsectbytes >= lun->sectorsize * priv->u.xfrlen) ||
|
|
(priv->nsectbytes >= lun->sectorsize * CONFIG_USBMSC_NWRREQS))
|
|
{
|
|
/* Yes.. Write next sectors */
|
|
|
|
nwritten = USBMSC_DRVR_WRITE(lun, priv->iobuffer,
|
|
priv->sector, nrbufs);
|
|
if (nwritten < 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDWRITEWRITEFAIL),
|
|
-nwritten);
|
|
lun->sd = SCSI_KCQME_WRITEFAULTAUTOREALLOCFAILED;
|
|
lun->sdinfo = priv->sector;
|
|
goto errout;
|
|
}
|
|
|
|
priv->nsectbytes = 0;
|
|
priv->residue -= lun->sectorsize * nrbufs;
|
|
priv->u.xfrlen -= nrbufs;
|
|
priv->sector += nrbufs;
|
|
}
|
|
#else
|
|
if ((priv->nsectbytes >= lun->sectorsize))
|
|
{
|
|
/* Yes.. Write the next sector */
|
|
|
|
nwritten = USBMSC_DRVR_WRITE(lun, priv->iobuffer,
|
|
priv->sector, 1);
|
|
if (nwritten < 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDWRITEWRITEFAIL),
|
|
-nwritten);
|
|
lun->sd = SCSI_KCQME_WRITEFAULTAUTOREALLOCFAILED;
|
|
lun->sdinfo = priv->sector;
|
|
goto errout;
|
|
}
|
|
|
|
priv->nsectbytes = 0;
|
|
priv->residue -= lun->sectorsize;
|
|
priv->u.xfrlen--;
|
|
priv->sector++;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* In either case, we are finished with this read request and can
|
|
* return it to the endpoint. Then we will go back to the top of the
|
|
* top and attempt to get the next read request.
|
|
*/
|
|
|
|
req->len = priv->epbulkout->maxpacket;
|
|
req->priv = privreq;
|
|
req->callback = usbmsc_rdcomplete;
|
|
|
|
ret = EP_SUBMIT(priv->epbulkout, req);
|
|
if (ret != OK)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDWRITERDSUBMIT),
|
|
(uint16_t)-ret);
|
|
}
|
|
|
|
/* Did the host decide to stop early? */
|
|
|
|
if (xfrd != priv->epbulkout->maxpacket)
|
|
{
|
|
priv->shortpacket = 1;
|
|
goto errout;
|
|
}
|
|
}
|
|
|
|
errout:
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDWRITECMDFINISH),
|
|
priv->u.xfrlen);
|
|
priv->thstate = USBMSC_STATE_CMDFINISH;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdfinishstate
|
|
*
|
|
* Description:
|
|
* Called from the worker thread in the USBMSC_STATE_CMDFINISH state.
|
|
* The USBMSC_STATE_CMDFINISH state is entered when processing of a
|
|
* command has finished but before status has been returned.
|
|
*
|
|
* Returned Value:
|
|
* If no USBDEV write request is available or certain other errors occur,
|
|
* this function returns a negated errno and stays in the
|
|
* USBMSC_STATE_CMDFINISH state. Otherwise, when the command is fully
|
|
* processed, this function sets priv->thstate to USBMSC_STATE_CMDSTATUS
|
|
* and returns OK.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int usbmsc_cmdfinishstate(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct usbmsc_req_s *privreq;
|
|
irqstate_t flags;
|
|
int ret = OK;
|
|
|
|
/* Check if there is a request in the wrreqlist that we will be able to
|
|
* use for data transfer.
|
|
*/
|
|
|
|
privreq = (FAR struct usbmsc_req_s *)sq_peek(&priv->wrreqlist);
|
|
|
|
/* If there no request structures available, then just return an error.
|
|
* This will cause us to remain in the CMDREAD state. When a request is
|
|
* returned, the worker thread will be awakened in the USBMSC_STATE_CMDREAD
|
|
* and we will be called again.
|
|
*/
|
|
|
|
if (!privreq)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDFINISHRQEMPTY), 0);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Finish the final stages of the reply */
|
|
|
|
switch (priv->cbwdir)
|
|
{
|
|
/* Device-to-host: All but the last data buffer has been sent */
|
|
|
|
case USBMSC_FLAGS_DIRDEVICE2HOST:
|
|
if (priv->cbwlen > 0)
|
|
{
|
|
/* On most commands (the exception is outgoing, write commands),
|
|
* the data has not yet been sent.
|
|
*/
|
|
|
|
if (priv->nreqbytes > 0)
|
|
{
|
|
struct usbdev_req_s *req;
|
|
|
|
/* Take a request from the wrreqlist (we've already checked
|
|
* that is it not NULL)
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
privreq = (FAR struct usbmsc_req_s *)
|
|
sq_remfirst(&priv->wrreqlist);
|
|
leave_critical_section(flags);
|
|
|
|
/* Send the write request */
|
|
|
|
req = privreq->req;
|
|
req->len = priv->nreqbytes;
|
|
req->callback = usbmsc_wrcomplete;
|
|
req->priv = privreq;
|
|
req->flags = USBDEV_REQFLAGS_NULLPKT;
|
|
|
|
ret = EP_SUBMIT(priv->epbulkin, privreq->req);
|
|
if (ret < 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDFINISHSUBMIT),
|
|
(uint16_t)-ret);
|
|
}
|
|
}
|
|
|
|
/* Stall the BULK In endpoint if there is a residue */
|
|
|
|
if (priv->residue > 0)
|
|
{
|
|
#ifndef CONFIG_USBMSC_NOT_STALL_BULKEP
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDFINISHRESIDUE),
|
|
(uint16_t)priv->residue);
|
|
|
|
#ifdef USBMSC_STALL_RACEWAR
|
|
/* (See description of the workaround at the top of the file).
|
|
* First, wait for the transfer to complete, then stall the
|
|
* endpoint
|
|
*/
|
|
|
|
nxsig_usleep (100000);
|
|
EP_STALL(priv->epbulkin);
|
|
|
|
/* now wait for stall to go away .... */
|
|
|
|
nxsig_usleep (100000);
|
|
#else
|
|
EP_STALL(priv->epbulkin);
|
|
#endif
|
|
#else
|
|
priv->residue = 0;
|
|
#endif
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* Host-to-device: We have processed all we want of the host data. */
|
|
|
|
case USBMSC_FLAGS_DIRHOST2DEVICE:
|
|
if (priv->residue > 0)
|
|
{
|
|
/* Did the host stop sending unexpectedly early? */
|
|
|
|
flags = enter_critical_section();
|
|
if (priv->shortpacket)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDFINISHSHORTPKT),
|
|
(uint16_t)priv->residue);
|
|
}
|
|
|
|
/* Unprocessed incoming data: STALL and cancel requests. */
|
|
|
|
else
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDFINSHSUBMIT),
|
|
(uint16_t)priv->residue);
|
|
EP_STALL(priv->epbulkout);
|
|
}
|
|
|
|
priv->theventset |= USBMSC_EVENT_ABORTBULKOUT;
|
|
leave_critical_section(flags);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDFINSHDIR), priv->cbwdir);
|
|
case USBMSC_FLAGS_DIRNONE: /* Nothing to send */
|
|
break;
|
|
}
|
|
|
|
/* Return to the IDLE state */
|
|
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDFINISHCMDSTATUS), 0);
|
|
priv->thstate = USBMSC_STATE_CMDSTATUS;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_cmdstatusstate
|
|
*
|
|
* Description:
|
|
* Called from the worker thread in the USBMSC_STATE_CMDSTATUS state.
|
|
* That state is after a CBW has been fully processed. This function sends
|
|
* the concluding CSW.
|
|
*
|
|
* Returned Value:
|
|
* If no write request is available or certain other errors occur, this
|
|
* function returns a negated errno and stays in the USBMSC_STATE_CMDSTATUS
|
|
* state. Otherwise, when the SCSI statis is successfully returned, this
|
|
* function sets priv->thstate to USBMSC_STATE_IDLE and returns OK.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int usbmsc_cmdstatusstate(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
FAR struct usbmsc_lun_s *lun = priv->lun;
|
|
FAR struct usbmsc_req_s *privreq;
|
|
FAR struct usbdev_req_s *req;
|
|
FAR struct usbmsc_csw_s *csw;
|
|
irqstate_t flags;
|
|
uint32_t sd;
|
|
uint8_t status = USBMSC_CSWSTATUS_PASS;
|
|
int ret;
|
|
|
|
/* Take a request from the wrreqlist */
|
|
|
|
flags = enter_critical_section();
|
|
privreq = (FAR struct usbmsc_req_s *)sq_remfirst(&priv->wrreqlist);
|
|
leave_critical_section(flags);
|
|
|
|
/* If there no request structures available, then just return an error.
|
|
* This will cause us to remain in the CMDSTATUS status. When a request
|
|
* is returned, the worker thread will be awakened in the
|
|
* USBMSC_STATE_CMDSTATUS and we will be called again.
|
|
*/
|
|
|
|
if (!privreq)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_CMDSTATUSWRREQLISTEMPTY), 0);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
req = privreq->req;
|
|
csw = (FAR struct usbmsc_csw_s *)req->buf;
|
|
|
|
/* Extract the sense data from the LUN structure */
|
|
|
|
if (lun)
|
|
{
|
|
sd = lun->sd;
|
|
}
|
|
else
|
|
{
|
|
sd = SCSI_KCQIR_INVALIDLUN;
|
|
}
|
|
|
|
/* Determine the CSW status: PASS, PHASEERROR, or FAIL */
|
|
|
|
if (priv->phaseerror)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_SNDPHERROR), 0);
|
|
status = USBMSC_CSWSTATUS_PHASEERROR;
|
|
sd = SCSI_KCQIR_INVALIDCOMMAND;
|
|
}
|
|
else if (sd != SCSI_KCQ_NOSENSE)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_SNDCSWFAIL), 0);
|
|
status = USBMSC_CSWSTATUS_FAIL;
|
|
}
|
|
|
|
/* Format and submit the CSW */
|
|
|
|
csw->signature[0] = 'U';
|
|
csw->signature[1] = 'S';
|
|
csw->signature[2] = 'B';
|
|
csw->signature[3] = 'S';
|
|
usbmsc_putle32(csw->tag, priv->cbwtag);
|
|
usbmsc_putle32(csw->residue, priv->residue);
|
|
csw->status = status;
|
|
|
|
usbmsc_dumpdata("SCSCI CSW", (FAR uint8_t *)csw, USBMSC_CSW_SIZEOF);
|
|
|
|
req->len = USBMSC_CSW_SIZEOF;
|
|
req->callback = usbmsc_wrcomplete;
|
|
req->priv = privreq;
|
|
req->flags = USBDEV_REQFLAGS_NULLPKT;
|
|
|
|
ret = EP_SUBMIT(priv->epbulkin, req);
|
|
if (ret < 0)
|
|
{
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_SNDSTATUSSUBMIT),
|
|
(uint16_t)-ret);
|
|
flags = enter_critical_section();
|
|
sq_addlast((FAR sq_entry_t *)privreq, &priv->wrreqlist);
|
|
leave_critical_section(flags);
|
|
}
|
|
|
|
/* Return to the IDLE state */
|
|
|
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDSTATUSIDLE), 0);
|
|
priv->thstate = USBMSC_STATE_IDLE;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_scsi_main
|
|
*
|
|
* Description:
|
|
* This is the main function of the USB storage worker thread. It loops
|
|
* until USB-related events occur, then processes those events accordingly
|
|
*
|
|
****************************************************************************/
|
|
|
|
int usbmsc_scsi_main(int argc, FAR char *argv[])
|
|
{
|
|
FAR struct usbmsc_dev_s *priv;
|
|
irqstate_t flags;
|
|
uint16_t eventset;
|
|
int ret;
|
|
|
|
uinfo("Started\n");
|
|
|
|
/* Get the SCSI state data handed off from the initialization logic */
|
|
|
|
priv = g_usbmsc_handoff;
|
|
DEBUGASSERT(priv);
|
|
|
|
g_usbmsc_handoff = NULL;
|
|
usbmsc_synch_signal(priv);
|
|
|
|
/* Get exclusive access to SCSI state data */
|
|
|
|
ret = nxmutex_lock(&priv->thlock);
|
|
if (ret < 0)
|
|
{
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* This thread is started before the USB storage class is fully
|
|
* initialized. Wait here until we are told to begin. Start in the
|
|
* NOTINITIALIZED state
|
|
*/
|
|
|
|
uinfo("Waiting to be signaled\n");
|
|
|
|
priv->thstate = USBMSC_STATE_STARTED;
|
|
while ((priv->theventset & USBMSC_EVENT_READY) == 0 &&
|
|
(priv->theventset & USBMSC_EVENT_TERMINATEREQUEST) == 0)
|
|
{
|
|
ret = usbmsc_scsi_wait(priv);
|
|
if (ret < 0)
|
|
{
|
|
/* The thread has been canceled */
|
|
|
|
nxmutex_unlock(&priv->thlock);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
uinfo("Running\n");
|
|
|
|
/* Transition to the INITIALIZED/IDLE state */
|
|
|
|
priv->thstate = USBMSC_STATE_IDLE;
|
|
eventset = priv->theventset;
|
|
priv->theventset = USBMSC_EVENT_NOEVENTS;
|
|
nxmutex_unlock(&priv->thlock);
|
|
|
|
/* Then loop until we are asked to terminate */
|
|
|
|
while ((eventset & USBMSC_EVENT_TERMINATEREQUEST) == 0)
|
|
{
|
|
/* Wait for some interesting event. Note that we must both take the
|
|
* lock (to eliminate race conditions with other threads) and disable
|
|
* interrupts (to eliminate race conditions with USB interrupt
|
|
* handling.
|
|
*/
|
|
|
|
ret = nxmutex_lock(&priv->thlock);
|
|
if (ret < 0)
|
|
{
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
flags = enter_critical_section();
|
|
if (priv->theventset == USBMSC_EVENT_NOEVENTS)
|
|
{
|
|
ret = usbmsc_scsi_wait(priv);
|
|
if (ret < 0)
|
|
{
|
|
/* The thread has been canceled */
|
|
|
|
leave_critical_section(flags);
|
|
nxmutex_unlock(&priv->thlock);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
/* Sample any events before re-enabling interrupts. Any events that
|
|
* occur after re-enabling interrupts will have to be handled in the
|
|
* next time through the loop.
|
|
*/
|
|
|
|
eventset = priv->theventset;
|
|
priv->theventset = USBMSC_EVENT_NOEVENTS;
|
|
nxmutex_unlock(&priv->thlock);
|
|
|
|
/* Were we awakened by some event that requires immediate action?
|
|
*
|
|
* - The USBMSC_EVENT_DISCONNECT is signaled from the disconnect
|
|
* method after all transfers have been stopped, when the host is
|
|
* disconnected.
|
|
*
|
|
* - The CUSBMSC_EVENT_RESET is signaled when the bulk-storage
|
|
* specific USBMSC_REQ_MSRESET EP0 setup received. We must stop the
|
|
* current operation and reinitialize state.
|
|
*
|
|
* - The USBMSC_EVENT_CFGCHANGE is signaled when the EP0 setup logic
|
|
* receives a valid USB_REQ_SETCONFIGURATION request
|
|
*
|
|
* - The USBMSC_EVENT_IFCHANGE is signaled when the EP0 setup logic
|
|
* receives a valid USB_REQ_SETINTERFACE request
|
|
*
|
|
* - The USBMSC_EVENT_ABORTBULKOUT event is signaled by the CMDFINISH
|
|
* logic when there is a residue after processing a host-to-device
|
|
* transfer. We need to discard all incoming request.
|
|
*
|
|
* All other events are just wakeup calls and are intended only
|
|
* drive the state machine.
|
|
*/
|
|
|
|
if ((eventset & (USBMSC_EVENT_DISCONNECT | USBMSC_EVENT_RESET |
|
|
USBMSC_EVENT_CFGCHANGE | USBMSC_EVENT_IFCHANGE |
|
|
USBMSC_EVENT_ABORTBULKOUT)) != 0)
|
|
{
|
|
/* These events require that a new configuration be established */
|
|
|
|
if ((eventset & (USBMSC_EVENT_CFGCHANGE)) != 0)
|
|
{
|
|
usbmsc_setconfig(priv, priv->thvalue);
|
|
}
|
|
|
|
/* These events required that we send a deferred EP0 setup
|
|
* response
|
|
*/
|
|
|
|
if ((eventset & (USBMSC_EVENT_RESET | USBMSC_EVENT_CFGCHANGE |
|
|
USBMSC_EVENT_IFCHANGE)) != 0)
|
|
{
|
|
usbmsc_deferredresponse(priv, false);
|
|
}
|
|
|
|
/* For all of these events... terminate any transactions in
|
|
* progress
|
|
*/
|
|
|
|
priv->thstate = USBMSC_STATE_IDLE;
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
|
|
/* Loop processing each SCSI command state. Each state handling
|
|
* function will do the following:
|
|
*
|
|
* - If it must block for an event, it will return a negated errno
|
|
* value
|
|
* - If it completes the processing for that state, it will (1) set
|
|
* the next appropriate state value and (2) return OK.
|
|
*
|
|
* So the following will loop until we must block for an event in
|
|
* a particular state. When we are awakened by an event (above) we
|
|
* will resume processing in the same state.
|
|
*/
|
|
|
|
do
|
|
{
|
|
switch (priv->thstate)
|
|
{
|
|
case USBMSC_STATE_IDLE: /* Started and waiting for commands */
|
|
ret = usbmsc_idlestate(priv);
|
|
break;
|
|
|
|
case USBMSC_STATE_CMDPARSE: /* Parsing the received a command */
|
|
ret = usbmsc_cmdparsestate(priv);
|
|
break;
|
|
|
|
case USBMSC_STATE_CMDREAD: /* Continuing to process a SCSI read command */
|
|
ret = usbmsc_cmdreadstate(priv);
|
|
break;
|
|
|
|
case USBMSC_STATE_CMDWRITE: /* Continuing to process a SCSI write command */
|
|
ret = usbmsc_cmdwritestate(priv);
|
|
break;
|
|
|
|
case USBMSC_STATE_CMDFINISH: /* Finish command processing */
|
|
ret = usbmsc_cmdfinishstate(priv);
|
|
break;
|
|
|
|
case USBMSC_STATE_CMDSTATUS: /* Processing the status phase of a command */
|
|
ret = usbmsc_cmdstatusstate(priv);
|
|
break;
|
|
|
|
case USBMSC_STATE_NOTSTARTED: /* Thread has not yet been started */
|
|
case USBMSC_STATE_STARTED: /* Started, but is not yet initialized */
|
|
case USBMSC_STATE_TERMINATED: /* Thread has exited */
|
|
default:
|
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_INVALIDSTATE),
|
|
priv->thstate);
|
|
priv->thstate = USBMSC_STATE_IDLE;
|
|
ret = OK;
|
|
break;
|
|
}
|
|
}
|
|
while (ret == OK);
|
|
}
|
|
|
|
/* Transition to the TERMINATED state and exit */
|
|
|
|
priv->thstate = USBMSC_STATE_TERMINATED;
|
|
usbmsc_synch_signal(priv);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbmsc_scsi_signal
|
|
*
|
|
* Description:
|
|
* Signal the SCSI worker thread that SCSI events need service.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void usbmsc_scsi_signal(FAR struct usbmsc_dev_s *priv)
|
|
{
|
|
irqstate_t flags;
|
|
|
|
/* A flag is used to prevent driving up the semaphore count. This function
|
|
* is called (primarily) from interrupt level logic so we must disable
|
|
* interrupts momentarily to assure that test of the flag and the increment
|
|
* of the semaphore count are atomic.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
if (priv->thwaiting)
|
|
{
|
|
priv->thwaiting = false;
|
|
nxsem_post(&priv->thwaitsem);
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
}
|