nuttx/drivers/usbdev/usbmsc_scsi.c
Wolfgang Reissnegger 4f83d58b24 USBMSC: Fix reversed logic on waiting for SCSI thread start.
The scsi thread was waiting for the wrong condition.

However, this was masked by the fact that the code creating the scsi thread
was also holding usbmsc_scsi_lock(priv) while initializing data, hence this
lock synchronized the scsi thread start with init completion.
2016-07-23 20:11:00 -07:00

2845 lines
92 KiB
C

/****************************************************************************
* drivers/usbdev/usbmsc_scsi.c
*
* Copyright (C) 2008-2010, 2012, 2016 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* 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
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* 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 <errno.h>
#include <queue.h>
#include <debug.h>
#include <nuttx/irq.h>
#include <nuttx/kthread.h>
#include <nuttx/arch.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
****************************************************************************/
/****************************************************************************
* 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 void usbmsc_scsi_wait(FAR struct usbmsc_dev_s *priv)
{
irqstate_t flags;
int ret;
/* We must hold the SCSI lock to call this function */
DEBUGASSERT(priv->thlock.semcount < 1);
/* 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 */
usbmsc_scsi_unlock(priv);
/* Now wait for a SCSI event to be signalled */
do
{
ret = sem_wait(&priv->thwaitsem);
DEBUGASSERT(ret == OK || errno == EINTR);
UNUSED(ret); /* Eliminate warnings when debug is off */
}
while (priv->thwaiting);
/* Re-acquire our lock on the SCSI state data */
usbmsc_scsi_lock(priv);
leave_critical_section(flags);
}
/****************************************************************************
* 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 = priv->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)
{
/* 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 = priv->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)
{
/* 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;
}
else if ((inquiry->flags != 0) || (inquiry->pagecode != 0))
{
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_INQUIRYFLAGS), 0);
priv->lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
ret = -EINVAL;
}
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_mscserialstr);
if (len > 4)
{
len = 4;
}
memcpy(response->revision, g_mscserialstr, 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;
(void)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 */
cmp->dpflen[0] = 0xff; /* Disable prefetch transfer length = 0xffffffff */
cmp->dpflen[1] = 0xff;
cmp->maxpf[0] = 0xff; /* Maximum pre-fetch = 0xffffffff */
cmp->maxpf[1] = 0xff;
cmp->maxpfc[0] = 0xff; /* Maximum pref-fetch ceiling = 0xffffffff */
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;
int mdlen;
int ret;
priv->u.alloclen = modesense->alloclen;
ret = usbmsc_setupcmd(priv, SCSICMD_MODESENSE6_SIZEOF,
USBMSC_FLAGS_DIRDEVICE2HOST);
if (ret == OK)
{
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 */
/* 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 - 1;
priv->nreqbytes = mdlen + SCSIRESP_MODEPARAMETERHDR6_SIZEOF;
}
}
}
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 = priv->lun;
int ret;
priv->u.alloclen = 0;
ret = usbmsc_setupcmd(priv, SCSICMD_PREVENTMEDIUMREMOVAL_SIZEOF,
USBMSC_FLAGS_DIRNONE);
if (ret == OK)
{
#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 = priv->lun;
int ret;
priv->u.alloclen = usbmsc_getbe16(rfc->alloclen);
ret = usbmsc_setupcmd(priv, SCSICMD_READFORMATCAPACITIES_SIZEOF,
USBMSC_FLAGS_DIRDEVICE2HOST);
if (ret == OK)
{
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 = priv->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)
{
/* 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 = priv->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)
{
/* 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 = priv->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)
{
/* 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 = priv->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)
{
/* 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);
(void)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 = priv->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)
{
/* 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 = priv->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)
{
/* 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.
* 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 = CONFIG_USBMSC_BULKOUTREQLEN;
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 = -EINVAL;
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 */
usbmsc_scsi_lock(priv);
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 implemented
* * 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 implemented */
case SCSI_CMD_PREVENTMEDIAREMOVAL: /* 0x1e Optional */
ret = usbmsc_cmdpreventmediumremoval(priv);
break;
/* * 0x20-22 Vendor specific */
case SCSI_CMD_READFORMATCAPACITIES: /* 0x23 Vendor-specific (defined in 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 Optional, but needed my MS 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 not implemented
* 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;
if (ret == OK)
{
priv->lun->sd = SCSI_KCQIR_INVALIDCOMMAND;
ret = -EINVAL;
}
break;
}
usbmsc_scsi_unlock(priv);
/* 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(CONFIG_USBMSC_BULKINREQLEN - 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 >= CONFIG_USBMSC_BULKINREQLEN ||
(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];
nbytes = MIN(lun->sectorsize - priv->nsectbytes, priv->nreqbytes);
/* Copy the data from the sector buffer to the USB request and update counts */
memcpy(dest, src, nbytes);
priv->nsectbytes += nbytes;
priv->nreqbytes -= nbytes;
/* Is the I/O buffer full? */
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++;
}
}
/* 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 = CONFIG_USBMSC_BULKOUTREQLEN;
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 != CONFIG_USBMSC_BULKOUTREQLEN)
{
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)
{
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
*/
usleep (100000);
(void)EP_STALL(priv->epbulkin);
/* now wait for stall to go away .... */
usleep (100000);
#else
(void)EP_STALL(priv->epbulkin);
#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();
(void)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);
/* 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 signalled\n");
usbmsc_scsi_lock(priv);
priv->thstate = USBMSC_STATE_STARTED;
while ((priv->theventset & USBMSC_EVENT_READY) == 0 &&
(priv->theventset & USBMSC_EVENT_TERMINATEREQUEST) == 0)
{
usbmsc_scsi_wait(priv);
}
uinfo("Running\n");
/* Transition to the INITIALIZED/IDLE state */
priv->thstate = USBMSC_STATE_IDLE;
eventset = priv->theventset;
priv->theventset = USBMSC_EVENT_NOEVENTS;
usbmsc_scsi_unlock(priv);
/* 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.
*/
usbmsc_scsi_lock(priv);
flags = enter_critical_section();
if (priv->theventset == USBMSC_EVENT_NOEVENTS)
{
usbmsc_scsi_wait(priv);
}
/* 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;
usbmsc_scsi_unlock(priv);
/* Were we awakened by some event that requires immediate action?
*
* - The USBMSC_EVENT_DISCONNECT is signalled from the disconnect method
* after all transfers have been stopped, when the host is disconnected.
*
* - The CUSBMSC_EVENT_RESET is signalled when the bulk-storage-specific
* USBMSC_REQ_MSRESET EP0 setup received. We must stop the current
* operation and reinialize 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 signalled 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 the current configuration be reset */
if ((eventset & USBMSC_EVENT_IFCHANGE) != 0)
{
usbmsc_resetconfig(priv);
}
/* 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 exitted */
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;
sem_post(&priv->thwaitsem);
}
leave_critical_section(flags);
}
/****************************************************************************
* Name: usbmsc_scsi_lock
*
* Description:
* Get exclusive access to SCSI state data.
*
****************************************************************************/
void usbmsc_scsi_lock(FAR struct usbmsc_dev_s *priv)
{
int ret;
do
{
ret = sem_wait(&priv->thlock);
DEBUGASSERT(ret == OK || errno == EINTR);
}
while (ret < 0);
}