Skip to content
Snippets Groups Projects
usbdev_scsi.c 84 KiB
Newer Older
          ret           = EP_SUBMIT(priv->epbulkin, req);
patacongo's avatar
patacongo committed
          if (ret != OK)
            {
              usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDREADSUBMIT), (uint16)-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(USBSTRG_CLASSSTATE_CMDREADCMDFINISH), priv->u.xfrlen);
  priv->thstate  = USBSTRG_STATE_CMDFINISH;
  return OK;
}

/****************************************************************************
 * Name: usbstrg_cmdwritestate
 *
 * Description:
 *   Called from the worker thread in the USBSTRG_STATE_CMDWRITE state.
 *   The USBSTRG_STATE_CMDWRITE state is entered when usbstrg_cmdparsestate
 *   obtains a valid SCSI write command. This state is really a continuation
 *   of the USBSTRG_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 USBSTRG_STATE_CMDWRITE
 *   state.  Otherwise, when the new SCSI write command is fully processed,
 *   this function sets priv->thstate to USBSTRG_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 usbstrg_cmdwritestate(FAR struct usbstrg_dev_s *priv)
{
  FAR struct usbstrg_lun_s *lun = priv->lun;
  FAR struct usbstrg_req_s *privreq;
  FAR struct usbdev_req_s *req;
  ssize_t nwritten;
  uint16 xfrd;
  ubyte *src;
  ubyte *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(USBSTRG_CLASSSTATE_CMDWRITE), priv->u.xfrlen);

      /* Check if there is a request in the rdreqlist containing additional
       * data to be written.
       */

      privreq = (FAR struct usbstrg_req_s *)sq_peek(&priv->rdreqlist);

      /* 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 USBSTRG_STATE_CMDWRITE
       * and we will be called again.
       */

      if (!privreq)
        {
          usbtrace(TRACE_CLSERROR(USBSTRG_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 tranferred
       * 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 = USBSTRG_DRVR_WRITE(lun, priv->iobuffer, priv->sector, 1);
              if (nwritten < 0)
                {
                  usbtrace(TRACE_CLSERROR(USBSTRG_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      = priv->epbulkout->maxpacket;
      req->private  = privreq;
      req->callback = usbstrg_rdcomplete;

      ret = EP_SUBMIT(priv->epbulkout, req);
      if (ret != OK)
        {
          usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDWRITERDSUBMIT), (uint16)-ret);
        }

      /* Did the host decide to stop early? */

      if (xfrd != priv->epbulkout->maxpacket)
        {
          priv->shortpacket = 1;
          goto errout;
        }
    }

errout:
  usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDWRITECMDFINISH), priv->u.xfrlen);
  priv->thstate  = USBSTRG_STATE_CMDFINISH;
  return OK;
}

/****************************************************************************
 * Name: usbstrg_cmdfinishstate
 *
 * Description:
 *   Called from the worker thread in the USBSTRG_STATE_CMDFINISH state.
 *   The USBSTRG_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 USBSTRG_STATE_CMDFINISH
 *   state.  Otherwise, when the command is fully processed, this function
 *   sets priv->thstate to USBSTRG_STATE_CMDSTATUS and returns OK.
 *
 ****************************************************************************/

static int usbstrg_cmdfinishstate(FAR struct usbstrg_dev_s *priv)
{
  FAR struct usbstrg_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 usbstrg_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 USBSTRG_STATE_CMDREAD
   * and we will be called again.
   */

  if (!privreq)
    {
      usbtrace(TRACE_CLSERROR(USBSTRG_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 USBSTRG_FLAGS_DIRDEVICE2HOST:
      if (priv->cbwlen > 0)
        {
          struct usbdev_req_s *req;

          /* Take a request from the wrreqlist (we've already checked
           * that is it not NULL)
           */

          flags = irqsave();
          privreq = (FAR struct usbstrg_req_s *)sq_remfirst(&priv->wrreqlist);
          irqrestore(flags);

          /* Send the write request */

          req           = privreq->req;
          req->len      = priv->nreqbytes;
          req->callback = usbstrg_wrcomplete;
          req->private  = privreq;
          req->flags    = USBDEV_REQFLAGS_NULLPKT;

patacongo's avatar
patacongo committed
          ret           = EP_SUBMIT(priv->epbulkin, privreq->req);
          if (ret < 0)
            {
              usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINISHSUBMIT), (uint16)-ret);
            }

          /* Stall the BULK In endpoint if there is a residue */

          if (priv->residue > 0)
            {
              usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINISHRESIDUE), (uint16)priv->residue);
              (void)EP_STALL(priv->epbulkin);
            }
        }
      break;

      /* Host-to-device: We have processed all we want of the host data. */

    case USBSTRG_FLAGS_DIRHOST2DEVICE:
      if (priv->residue > 0)
        {
          /* Did the host stop sending unexpectedly early? */

          flags = irqsave();
          if (priv->shortpacket)
            {
              usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINISHSHORTPKT), (uint16)priv->residue);
            }

          /* Unprocessed incoming data: STALL and cancel requests. */

          else 
            {
              usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINSHSUBMIT), (uint16)priv->residue);
              EP_STALL(priv->epbulkout);
           }

           priv->theventset |= USBSTRG_EVENT_ABORTBULKOUT;
           irqrestore(flags);
        }
      break;

    default:
      usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINSHDIR), priv->cbwdir);
    case USBSTRG_FLAGS_DIRNONE: /* Nothing to send */
      break;
    }

  /* Return to the IDLE state */

  usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDFINISHCMDSTATUS), 0);
  priv->thstate = USBSTRG_STATE_CMDSTATUS;
  return OK;
}

/****************************************************************************
 * Name: usbstrg_cmdstatusstate
 *
 * Description:
 *   Called from the worker thread in the USBSTRG_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 USBSTRG_STATE_CMDSTATUS
 *   state.  Otherwise, when the SCSI statis is successfully returned, this
 *   function sets priv->thstate to USBSTRG_STATE_IDLE and returns OK.
 *
 ****************************************************************************/

static int usbstrg_cmdstatusstate(FAR struct usbstrg_dev_s *priv)
{
  FAR struct usbstrg_lun_s *lun = priv->lun;
  FAR struct usbstrg_req_s *privreq;
  FAR struct usbdev_req_s *req;
  FAR struct usbstrg_csw_s *csw;
  irqstate_t flags;
  uint32 sd;
  ubyte status = USBSTRG_CSWSTATUS_PASS;
  int ret;

  /* Take a request from the wrreqlist */

  flags = irqsave();
  privreq = (FAR struct usbstrg_req_s *)sq_remfirst(&priv->wrreqlist);
  irqrestore(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 USBSTRG_STATE_CMDSTATUS
   * and we will be called again.
   */

  if (!privreq)
    {
      usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDSTATUSRDREQLISTEMPTY), 0);
      return -ENOMEM;
    }

  req = privreq->req;
  csw = (struct usbstrg_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(USBSTRG_TRACEERR_SNDPHERROR), 0);
      status = USBSTRG_CSWSTATUS_PHASEERROR;
      sd     = SCSI_KCQIR_INVALIDCOMMAND;
    }
  else if (sd != SCSI_KCQ_NOSENSE)
    {
      usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_SNDCSWFAIL), 0);
      status = USBSTRG_CSWSTATUS_FAIL;
    }

  /* Format and submit the CSW */

  csw->signature[0] = 'U';
  csw->signature[1] = 'S';
  csw->signature[2] = 'B';
  csw->signature[3] = 'S';
  usbstrg_putle32(csw->tag, priv->cbwtag);
  usbstrg_putle32(csw->residue, priv->residue);
  csw->status       = status;

  usbstrg_dumpdata("SCSCI CSW", (ubyte*)csw, USBSTRG_CSW_SIZEOF);

  req->len       = USBSTRG_CSW_SIZEOF;
  req->callback  = usbstrg_wrcomplete;
  req->private   = privreq;
  req->flags     = USBDEV_REQFLAGS_NULLPKT;

  ret            = EP_SUBMIT(priv->epbulkin, req);
patacongo's avatar
patacongo committed
  if (ret < 0)
    {
      usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_SNDSTATUSSUBMIT), (uint16)-ret);
      flags = irqsave();
      (void)sq_addlast((sq_entry_t*)privreq, &priv->wrreqlist);
      irqrestore(flags);
    }

  /* Return to the IDLE state */

  usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDSTATUSIDLE), 0);
  priv->thstate = USBSTRG_STATE_IDLE;
  return OK;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: usbstrg_workerthread
 *
 * Description:
 *   This is the main function of the USB storage worker thread.  It loops
 *   until USB-related events occur, then processes those events accordingly
 *
 ****************************************************************************/

void *usbstrg_workerthread(void *arg)
{
  struct usbstrg_dev_s *priv = (struct usbstrg_dev_s *)arg;
  irqstate_t flags;
  uint16 eventset;
  int ret;

  /* 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
   */

  pthread_mutex_lock(&priv->mutex);
  priv->thstate = USBSTRG_STATE_STARTED;
  while ((priv->theventset & USBSTRG_EVENT_READY) != 0 &&
         (priv->theventset & USBSTRG_EVENT_TERMINATEREQUEST) != 0)
    {
      pthread_cond_wait(&priv->cond, &priv->mutex);
    }

  /* Transition to the INITIALIZED/IDLE state */

  priv->thstate    = USBSTRG_STATE_IDLE;
  eventset         = priv->theventset;
  priv->theventset = USBSTRG_EVENT_NOEVENTS;
  pthread_mutex_unlock(&priv->mutex);

  /* Then loop until we are asked to terminate */

  while (eventset != USBSTRG_EVENT_TERMINATEREQUEST)
    {
      /* 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.
       */

      pthread_mutex_lock(&priv->mutex);
      flags = irqsave();
      if (priv->theventset == USBSTRG_EVENT_NOEVENTS)
        {
          pthread_cond_wait(&priv->cond, &priv->mutex);
        }

      /* 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 = USBSTRG_EVENT_NOEVENTS;
      irqrestore(flags);

      /* Were we awakened by some event that requires immediate action? */

      /* The USBSTRG_EVENT_DISCONNECT is signalled from the disconnect method
       * after all transfers have been stopped, when the host is disconnected.
       */

      if ((eventset & USBSTRG_EVENT_DISCONNECT) != 0)
        {
#warning LOGIC needed
        }

      /* Called with the bulk-storage-specific USBSTRG_REQ_MSRESET EP0 setup
       * received.  We must stop the current operation and reinialize state.
       */

      if ((eventset & USBSTRG_EVENT_RESET) != 0)
        {
#warning LOGIC needed
          priv->thstate = USBSTRG_STATE_IDLE;
          usbstrg_deferredresponse(priv, FALSE);
        }

      /* The USBSTRG_EVENT_CFGCHANGE is signaled when the EP0 setup
       * logic receives a USB_REQ_SETCONFIGURATION request
       */

      if ((eventset & USBSTRG_EVENT_CFGCHANGE) != 0)
        {
#warning LOGIC needed
          usbstrg_setconfig(priv, priv->thvalue);
          priv->thstate = USBSTRG_STATE_IDLE;
          usbstrg_deferredresponse(priv, FALSE);
        }

      /* The USBSTRG_EVENT_IFCHANGE is signaled when the EP0 setup
       * logic receives a USB_REQ_SETINTERFACE request
       */
      if ((eventset & USBSTRG_EVENT_IFCHANGE) != 0)
        {
#warning LOGIC needed
          usbstrg_resetconfig(priv);
          usbstrg_setconfig(priv, priv->thvalue);
          priv->thstate = USBSTRG_STATE_IDLE;
          usbstrg_deferredresponse(priv, FALSE);
        }

      /* Set by the CMDFINISH logic when there is a residue after processing
       * a host-to-device transfer.  We need to discard all incoming request.
       */

      if ((eventset & USBSTRG_EVENT_ABORTBULKOUT) != 0)
        {
#warning LOGIC needed
          priv->thstate = USBSTRG_STATE_IDLE;
        }

      /* All other events are just wakeup calls and are intended only
       * drive the state maching.  Remember only the terminate request event...
       * we'll process that at the end of the loop.
       */

      eventset &= USBSTRG_EVENT_TERMINATEREQUEST;

      /* 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.
       */

      for (ret = OK; ret == OK; )
        {
          switch (priv->thstate)
            {
            case USBSTRG_STATE_IDLE:             /* Started and waiting for commands */
               ret = usbstrg_idlestate(priv);
               break;

            case USBSTRG_STATE_CMDPARSE:         /* Parsing the received a command */
               ret = usbstrg_cmdparsestate(priv);
               break;

            case USBSTRG_STATE_CMDREAD:          /* Continuing to process a SCSI read command */
               ret = usbstrg_cmdreadstate(priv);
               break;

            case USBSTRG_STATE_CMDWRITE:         /* Continuing to process a SCSI write command */
               ret = usbstrg_cmdwritestate(priv);
               break;

            case USBSTRG_STATE_CMDFINISH:        /* Finish command processing */
               ret = usbstrg_cmdfinishstate(priv);
               break;

            case USBSTRG_STATE_CMDSTATUS:        /* Processing the status phase of a command */
              ret = usbstrg_cmdstatusstate(priv);
              break;

            case USBSTRG_STATE_NOTSTARTED:       /* Thread has not yet been started */
            case USBSTRG_STATE_STARTED:          /* Started, but is not yet initialized */
            case USBSTRG_STATE_TERMINATED:       /* Thread has exitted */
            default:
              usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_INVALIDSTATE), priv->thstate);
              priv->thstate = USBSTRG_STATE_IDLE;
              break;
            }
        }
    }

  /* Transition to the TERMINATED state and exit */

  priv->thstate = USBSTRG_STATE_TERMINATED;
  return NULL;
}