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