/****************************************************************************
 * 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(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(uint8_t *buf);
static uint32_t usbmsc_getbe32(uint8_t *buf);
static void     usbmsc_putbe16(uint8_t * buf, uint16_t val);
static void     usbmsc_putbe24(uint8_t *buf, uint32_t val);
static void     usbmsc_putbe32(uint8_t *buf, uint32_t val);
#if 0 /* not used */
static uint16_t usbmsc_getle16(uint8_t *buf);
#endif
static uint32_t usbmsc_getle32(uint8_t *buf);
#if 0 /* not used */
static void     usbmsc_putle16(uint8_t * buf, uint16_t val);
#endif
static void     usbmsc_putle32(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, 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 const char *g_productrevision = "0101";

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Debug
 ****************************************************************************/

/****************************************************************************
 * Name: usbmsc_dumpdata
 ****************************************************************************/

#if defined(CONFIG_DEBUG_INFO) && defined (CONFIG_DEBUG_USB)
static void usbmsc_dumpdata(const char *msg, 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(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(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(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(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(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(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(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(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(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, 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)
{
  struct scsicmd_read10_s *read10 = (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)
{
  struct scsicmd_write10_s *write10 = (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)
{
  struct scsicmd_read12_s *read12 = (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)
{
  struct scsicmd_write12_s *write12 = (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;
  uint8_t *src;
  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;
  uint8_t *src;
  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, 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);
}