This is an automated email from the ASF dual-hosted git repository.

jerpelea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new cfeca506f8c rp2040/rp23xx: implement stall queueing and GET STATUS 
responses
cfeca506f8c is described below

commit cfeca506f8c928e5ce1f53e6f8d934207aeb6952
Author: Ricard Rosson <[email protected]>
AuthorDate: Wed Jul 15 21:01:19 2026 +0100

    rp2040/rp23xx: implement stall queueing and GET STATUS responses
    
    Two device-controller defects that break standard host error recovery,
    found while root-causing why macOS never mounts a NuttX mass-storage
    function (full analysis and host traces in apache/nuttx#19435):
    
    1. epsubmit() aborted (-EBUSY) any IN request submitted while the
       endpoint was halted.  The mass storage class halts bulk-IN for a
       failed device-to-host command (BOT-legal) and then submits the CSW;
       the CSW was dropped on the floor, so after the host's Clear-Halt the
       endpoint NAKed forever and the host timed out (macOS: 30 s, then a
       Bulk-Only-reset/device-reset spiral until it disables the port).
       This is exactly the race documented for years in the usbmsc_scsi.c
       header (David Hewson's analysis); the USBMSC_STALL_RACEWAR sleep
       workaround only survives hosts that clear the halt within 100 ms --
       Linux does, macOS does not, which is why Linux testing never saw it.
    
       Implement the stall-queueing contract instead: requests submitted
       while an endpoint is halted are queued without arming the hardware
       (arming rewrites the buffer control word and would silently clear
       the STALL bit); a halt terminates any in-flight IN transfer (its
       hardware buffer is disarmed by the STALL write and would never
       complete); clearing the halt resets the data toggle and re-arms the
       head of the queue; stale buffer completions latched for transfers
       aborted by a halt are ignored.  RP2040/RP23XX now select
       ARCH_USBDEV_STALLQUEUE, which also retires the RACEWAR's two 100 ms
       sleeps per failed command.
    
    2. The USB_REQ_GETSTATUS handler in ep0setup() validated the request
       but never queued the two-byte response, for all three recipients
       (device/interface/endpoint), so EP0 NAKed the host's data stage
       forever and every GET STATUS timed out.  macOS issues GetPipeStatus
       = GET STATUS(endpoint) on a halted pipe before running Bulk-Only
       reset recovery and hit this on every probe; lsusb -v's device-status
       query hangs on it as well.  Send the response: endpoint recipient
       reports the halt bit, device recipient reports self-powered,
       interface reports zeros; the status stage is armed by handle_zlp()
       exactly as for class-dispatched IN transfers.
    
    Validated on RP2350 silicon (Raspberry Pi Pico 2 W, composite
    CDC-ACM + CDC-NCM + USBMSC): pre-fix, a raw-usbfs replay of macOS's
    sequence and timing reproduced both defects deterministically (CSW read
    ETIMEDOUT after a delayed clear-halt; all GET STATUS variants
    ETIMEDOUT).  Post-fix: the CSW survives the halt and is delivered after
    Clear-Halt with correct tag/status/residue across a post-stall delay
    sweep of 0-1000 ms; all GET STATUS variants answer immediately with
    correct halt reporting; no regressions in the exact-length SCSI suite,
    Bulk-Only reset, FAT mount and full reads, CDC-NCM/ACM, or warm
    reboots; and macOS now mounts the volume (together with the companion
    usbmsc fixes).  The rp2040 driver shares the code and receives the
    identical fix (build-tested).
    
    Assisted-by: Claude (Anthropic Claude Code)
    Signed-off-by: Ricard Rosson <[email protected]>
---
 arch/arm/Kconfig                    |   2 +
 arch/arm/src/rp2040/rp2040_usbdev.c | 131 ++++++++++++++++++++++++------------
 arch/arm/src/rp23xx/rp23xx_usbdev.c | 131 ++++++++++++++++++++++++------------
 3 files changed, 180 insertions(+), 84 deletions(-)

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 6b2ea0d0886..15e449e52b7 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -381,6 +381,7 @@ config ARCH_CHIP_RP2040
        select ARM_HAVE_WFE_SEV
        select ARCH_BOARD_COMMON
        select ARCH_HAVE_CUSTOM_TESTSET
+       select ARCH_USBDEV_STALLQUEUE if USBDEV
        ---help---
                Raspberry Pi RP2040 architectures (ARM dual Cortex-M0+).
 
@@ -396,6 +397,7 @@ config ARCH_CHIP_RP23XX
        select ARCH_HAVE_CUSTOM_TESTSET
        select ARCH_HAVE_TICKLESS
        select ARCH_BOARD_COMMON
+       select ARCH_USBDEV_STALLQUEUE if USBDEV
        ---help---
                Raspberry Pi RP23XX architectures (ARM dual Cortex-M33 or 
RISC-V).
 
diff --git a/arch/arm/src/rp2040/rp2040_usbdev.c 
b/arch/arm/src/rp2040/rp2040_usbdev.c
index 470f32567ed..1c962115442 100644
--- a/arch/arm/src/rp2040/rp2040_usbdev.c
+++ b/arch/arm/src/rp2040/rp2040_usbdev.c
@@ -318,9 +318,6 @@ static void rp2040_update_buffer_control(struct rp2040_ep_s 
*privep,
 static int rp2040_epwrite(struct rp2040_ep_s *privep, uint8_t *buf,
                           uint16_t nbytes);
 static int rp2040_epread(struct rp2040_ep_s *privep, uint16_t nbytes);
-static void rp2040_abortrequest(struct rp2040_ep_s *privep,
-                                struct rp2040_req_s *privreq,
-                                int16_t result);
 static void rp2040_reqcomplete(struct rp2040_ep_s *privep, int16_t result);
 static void rp2040_txcomplete(struct rp2040_ep_s *privep);
 static int rp2040_wrrequest(struct rp2040_ep_s *privep);
@@ -586,30 +583,6 @@ static int rp2040_epread(struct rp2040_ep_s *privep, 
uint16_t nbytes)
   return OK;
 }
 
-/****************************************************************************
- * Name: rp2040_abortrequest
- *
- * Description:
- *   Discard a request
- *
- ****************************************************************************/
-
-static void rp2040_abortrequest(struct rp2040_ep_s *privep,
-                                struct rp2040_req_s *privreq,
-                                int16_t result)
-{
-  usbtrace(TRACE_DEVERROR(RP2040_TRACEERR_REQABORTED),
-          (uint16_t)privep->epphy);
-
-  /* Save the result in the request structure */
-
-  privreq->req.result = result;
-
-  /* Callback to the request completion handler */
-
-  privreq->req.callback(&privep->ep, &privreq->req);
-}
-
 /****************************************************************************
  * Name: rp2040_reqcomplete
  *
@@ -1052,6 +1025,11 @@ static void rp2040_ep0setup(struct rp2040_usbdev_s *priv)
                 }
               else
                 {
+                  uint8_t response[2];
+
+                  response[0] = 0;
+                  response[1] = 0;
+
                   switch (priv->ctrl.type & USB_REQ_RECIPIENT_MASK)
                     {
                       case USB_REQ_RECIPIENT_ENDPOINT:
@@ -1068,10 +1046,23 @@ static void rp2040_ep0setup(struct rp2040_usbdev_s 
*priv)
                                 priv->ctrl.type);
                               priv->stalled = true;
                             }
+                          else if (privep->stalled)
+                            {
+                              response[0] = 1; /* Endpoint HALTed */
+                            }
                         }
                         break;
 
                       case USB_REQ_RECIPIENT_DEVICE:
+                        usbtrace(TRACE_INTDECODE(
+                                 RP2040_TRACEINTID_GETIFDEV),
+                                 0);
+                        if (priv->selfpowered)
+                          {
+                            response[0] = 1 << USB_FEATURE_SELFPOWERED;
+                          }
+                        break;
+
                       case USB_REQ_RECIPIENT_INTERFACE:
                         usbtrace(TRACE_INTDECODE(
                                  RP2040_TRACEINTID_GETIFDEV),
@@ -1087,6 +1078,17 @@ static void rp2040_ep0setup(struct rp2040_usbdev_s *priv)
                         }
                         break;
                     }
+
+                  /* Send the two-byte endpoint status.  Without a data
+                   * stage EP0 NAKs the host's IN forever; macOS queries
+                   * GET_STATUS on a halted bulk pipe during Bulk-Only reset
+                   * recovery, so this is required for MSC error recovery.
+                   */
+
+                  if (!priv->stalled)
+                    {
+                      rp2040_epwrite(ep0, response, 2);
+                    }
                 }
             }
             break;
@@ -1394,7 +1396,15 @@ static bool rp2040_usbintr_buffstat(struct 
rp2040_usbdev_s *priv)
 
               if (privep->in)
                 {
-                  if (!rp2040_rqempty(privep))
+                  if (privep->epphy != 0 && privep->stalled)
+                    {
+                      /* Completion latched for a transfer the halt already
+                       * aborted (canceled by rp2040_epstall); don't
+                       * attribute the stale buffer event to post-halt
+                       * requests.
+                       */
+                    }
+                  else if (!rp2040_rqempty(privep))
                     {
                       rp2040_txcomplete(privep);
                     }
@@ -1694,24 +1704,22 @@ static int rp2040_epsubmit(struct usbdev_ep_s *ep,
 
   flags = enter_critical_section();
 
-  if (privep->stalled && privep->in)
-    {
-      rp2040_abortrequest(privep, privreq, -EBUSY);
-      ret = -EBUSY;
-    }
-
   /* Handle IN (device-to-host) requests */
 
-  else if (privep->in)
+  if (privep->in)
     {
-      /* Add the new request to the request queue for the IN endpoint */
+      /* Queue the request on the IN endpoint.  If halted, only queue it;
+       * it is armed when the halt clears (rp2040_epstall).  MSC relies on
+       * this: the CSW is submitted while the bulk IN is still halted
+       * (ARCH_USBDEV_STALLQUEUE).
+       */
 
       bool empty = rp2040_rqempty(privep);
 
       rp2040_rqenqueue(privep, privreq);
       usbtrace(TRACE_INREQQUEUED(privep->epphy), privreq->req.len);
 
-      if (empty)
+      if (empty && !privep->stalled)
         {
           rp2040_wrrequest(privep);
         }
@@ -1721,7 +1729,9 @@ static int rp2040_epsubmit(struct usbdev_ep_s *ep,
 
   else
     {
-      /* Add the new request to the request queue for the OUT endpoint */
+      /* Queue on the OUT endpoint.  As for IN, don't arm while halted:
+       * arming rewrites the buffer control word and clears the STALL bit.
+       */
 
       bool empty = rp2040_rqempty(privep);
 
@@ -1731,7 +1741,7 @@ static int rp2040_epsubmit(struct usbdev_ep_s *ep,
 
       /* This there a incoming data pending the availability of a request? */
 
-      if (empty)
+      if (empty && !privep->stalled)
         {
           ret = rp2040_rdrequest(privep);
         }
@@ -1829,14 +1839,17 @@ static int rp2040_epstall(struct usbdev_ep_s *ep, bool 
resume)
 {
   struct rp2040_ep_s *privep = (struct rp2040_ep_s *)ep;
   struct rp2040_usbdev_s *priv = privep->dev;
+  irqstate_t irqflags;
   irqstate_t flags;
 
+  irqflags = enter_critical_section();
   flags = spin_lock_irqsave(&priv->lock);
 
   if (resume)
     {
       usbtrace(TRACE_EPRESUME, privep->epphy);
       privep->stalled = false;
+      privep->pending_stall = false;
       if (privep->epphy == 0)
         {
           clrbits_reg32(privep->in ?
@@ -1849,8 +1862,29 @@ static int rp2040_epstall(struct usbdev_ep_s *ep, bool 
resume)
                         ~(RP2040_USBCTRL_DPSRAM_EP_BUFF_CTRL_STALL),
                         0);
 
+      /* Halt clearing resets the data toggle to DATA0 (USB 2.0 9.4.5) */
+
       privep->next_pid = 0;
       priv->zlp_stat = RP2040_ZLP_NONE;
+
+      spin_unlock_irqrestore(&priv->lock, flags);
+
+      /* Restart any requests that were queued (but not armed) while the
+       * endpoint was halted -- e.g. the mass storage CSW that concludes
+       * a failed command (ARCH_USBDEV_STALLQUEUE).
+       */
+
+      if (privep->epphy != 0 && !rp2040_rqempty(privep))
+        {
+          if (privep->in)
+            {
+              rp2040_wrrequest(privep);
+            }
+          else
+            {
+              rp2040_rdrequest(privep);
+            }
+        }
     }
   else
     {
@@ -1861,18 +1895,31 @@ static int rp2040_epstall(struct usbdev_ep_s *ep, bool 
resume)
           /* EP0 IN Transfer ongoing : postpone the stall until the end */
 
           privep->pending_stall = true;
+          priv->zlp_stat = RP2040_ZLP_NONE;
+          spin_unlock_irqrestore(&priv->lock, flags);
         }
       else
         {
           /* Stall immediately */
 
           rp2040_epstall_exec_nolock(ep);
-        }
+          priv->zlp_stat = RP2040_ZLP_NONE;
+          spin_unlock_irqrestore(&priv->lock, flags);
 
-      priv->zlp_stat = RP2040_ZLP_NONE;
+          /* Terminate the IN transfer the halt aborted: its buffer was
+           * disarmed by the STALL write above and would never complete.
+           * Anything the class resubmits stays queued until the halt
+           * clears.
+           */
+
+          if (privep->epphy != 0 && privep->in && !rp2040_rqempty(privep))
+            {
+              rp2040_cancelrequests(privep);
+            }
+        }
     }
 
-  spin_unlock_irqrestore(&priv->lock, flags);
+  leave_critical_section(irqflags);
 
   return OK;
 }
diff --git a/arch/arm/src/rp23xx/rp23xx_usbdev.c 
b/arch/arm/src/rp23xx/rp23xx_usbdev.c
index 357b96eb8d8..af7550e754d 100644
--- a/arch/arm/src/rp23xx/rp23xx_usbdev.c
+++ b/arch/arm/src/rp23xx/rp23xx_usbdev.c
@@ -318,9 +318,6 @@ static void rp23xx_update_buffer_control(struct rp23xx_ep_s 
*privep,
 static int rp23xx_epwrite(struct rp23xx_ep_s *privep, uint8_t *buf,
                           uint16_t nbytes);
 static int rp23xx_epread(struct rp23xx_ep_s *privep, uint16_t nbytes);
-static void rp23xx_abortrequest(struct rp23xx_ep_s *privep,
-                                struct rp23xx_req_s *privreq,
-                                int16_t result);
 static void rp23xx_reqcomplete(struct rp23xx_ep_s *privep, int16_t result);
 static void rp23xx_txcomplete(struct rp23xx_ep_s *privep);
 static int rp23xx_wrrequest(struct rp23xx_ep_s *privep);
@@ -585,30 +582,6 @@ static int rp23xx_epread(struct rp23xx_ep_s *privep, 
uint16_t nbytes)
   return OK;
 }
 
-/****************************************************************************
- * Name: rp23xx_abortrequest
- *
- * Description:
- *   Discard a request
- *
- ****************************************************************************/
-
-static void rp23xx_abortrequest(struct rp23xx_ep_s *privep,
-                                struct rp23xx_req_s *privreq,
-                                int16_t result)
-{
-  usbtrace(TRACE_DEVERROR(RP23XX_TRACEERR_REQABORTED),
-          (uint16_t)privep->epphy);
-
-  /* Save the result in the request structure */
-
-  privreq->req.result = result;
-
-  /* Callback to the request completion handler */
-
-  privreq->req.callback(&privep->ep, &privreq->req);
-}
-
 /****************************************************************************
  * Name: rp23xx_reqcomplete
  *
@@ -1051,6 +1024,11 @@ static void rp23xx_ep0setup(struct rp23xx_usbdev_s *priv)
                 }
               else
                 {
+                  uint8_t response[2];
+
+                  response[0] = 0;
+                  response[1] = 0;
+
                   switch (priv->ctrl.type & USB_REQ_RECIPIENT_MASK)
                     {
                       case USB_REQ_RECIPIENT_ENDPOINT:
@@ -1067,10 +1045,23 @@ static void rp23xx_ep0setup(struct rp23xx_usbdev_s 
*priv)
                                 priv->ctrl.type);
                               priv->stalled = true;
                             }
+                          else if (privep->stalled)
+                            {
+                              response[0] = 1; /* Endpoint HALTed */
+                            }
                         }
                         break;
 
                       case USB_REQ_RECIPIENT_DEVICE:
+                        usbtrace(TRACE_INTDECODE(
+                                 RP23XX_TRACEINTID_GETIFDEV),
+                                 0);
+                        if (priv->selfpowered)
+                          {
+                            response[0] = 1 << USB_FEATURE_SELFPOWERED;
+                          }
+                        break;
+
                       case USB_REQ_RECIPIENT_INTERFACE:
                         usbtrace(TRACE_INTDECODE(
                                  RP23XX_TRACEINTID_GETIFDEV),
@@ -1086,6 +1077,17 @@ static void rp23xx_ep0setup(struct rp23xx_usbdev_s *priv)
                         }
                         break;
                     }
+
+                  /* Send the two-byte endpoint status.  Without a data
+                   * stage EP0 NAKs the host's IN forever; macOS queries
+                   * GET_STATUS on a halted bulk pipe during Bulk-Only reset
+                   * recovery, so this is required for MSC error recovery.
+                   */
+
+                  if (!priv->stalled)
+                    {
+                      rp23xx_epwrite(ep0, response, 2);
+                    }
                 }
             }
             break;
@@ -1394,7 +1396,15 @@ static bool rp23xx_usbintr_buffstat(struct 
rp23xx_usbdev_s *priv)
 
               if (privep->in)
                 {
-                  if (!rp23xx_rqempty(privep))
+                  if (privep->epphy != 0 && privep->stalled)
+                    {
+                      /* Completion latched for a transfer the halt already
+                       * aborted (canceled by rp23xx_epstall); don't
+                       * attribute the stale buffer event to post-halt
+                       * requests.
+                       */
+                    }
+                  else if (!rp23xx_rqempty(privep))
                     {
                       rp23xx_txcomplete(privep);
                     }
@@ -1696,24 +1706,22 @@ static int rp23xx_epsubmit(struct usbdev_ep_s *ep,
 
   flags = enter_critical_section();
 
-  if (privep->stalled && privep->in)
-    {
-      rp23xx_abortrequest(privep, privreq, -EBUSY);
-      ret = -EBUSY;
-    }
-
   /* Handle IN (device-to-host) requests */
 
-  else if (privep->in)
+  if (privep->in)
     {
-      /* Add the new request to the request queue for the IN endpoint */
+      /* Queue the request on the IN endpoint.  If halted, only queue it;
+       * it is armed when the halt clears (rp23xx_epstall).  MSC relies on
+       * this: the CSW is submitted while the bulk IN is still halted
+       * (ARCH_USBDEV_STALLQUEUE).
+       */
 
       bool empty = rp23xx_rqempty(privep);
 
       rp23xx_rqenqueue(privep, privreq);
       usbtrace(TRACE_INREQQUEUED(privep->epphy), privreq->req.len);
 
-      if (empty)
+      if (empty && !privep->stalled)
         {
           rp23xx_wrrequest(privep);
         }
@@ -1723,7 +1731,9 @@ static int rp23xx_epsubmit(struct usbdev_ep_s *ep,
 
   else
     {
-      /* Add the new request to the request queue for the OUT endpoint */
+      /* Queue on the OUT endpoint.  As for IN, don't arm while halted:
+       * arming rewrites the buffer control word and clears the STALL bit.
+       */
 
       bool empty = rp23xx_rqempty(privep);
 
@@ -1733,7 +1743,7 @@ static int rp23xx_epsubmit(struct usbdev_ep_s *ep,
 
       /* This there a incoming data pending the availability of a request? */
 
-      if (empty)
+      if (empty && !privep->stalled)
         {
           ret = rp23xx_rdrequest(privep);
         }
@@ -1818,14 +1828,17 @@ static int rp23xx_epstall(struct usbdev_ep_s *ep, bool 
resume)
 {
   struct rp23xx_ep_s *privep = (struct rp23xx_ep_s *)ep;
   struct rp23xx_usbdev_s *priv = privep->dev;
+  irqstate_t irqflags;
   irqstate_t flags;
 
+  irqflags = enter_critical_section();
   flags = spin_lock_irqsave(&g_usbdev.lock);
 
   if (resume)
     {
       usbtrace(TRACE_EPRESUME, privep->epphy);
       privep->stalled = false;
+      privep->pending_stall = false;
       if (privep->epphy == 0)
         {
           clrbits_reg32(privep->in ?
@@ -1838,8 +1851,29 @@ static int rp23xx_epstall(struct usbdev_ep_s *ep, bool 
resume)
                         ~(RP23XX_USBCTRL_DPSRAM_EP_BUFF_CTRL_STALL),
                         0);
 
+      /* Halt clearing resets the data toggle to DATA0 (USB 2.0 9.4.5) */
+
       privep->next_pid = 0;
       priv->zlp_stat = RP23XX_ZLP_NONE;
+
+      spin_unlock_irqrestore(&g_usbdev.lock, flags);
+
+      /* Restart any requests that were queued (but not armed) while the
+       * endpoint was halted -- e.g. the mass storage CSW that concludes
+       * a failed command (ARCH_USBDEV_STALLQUEUE).
+       */
+
+      if (privep->epphy != 0 && !rp23xx_rqempty(privep))
+        {
+          if (privep->in)
+            {
+              rp23xx_wrrequest(privep);
+            }
+          else
+            {
+              rp23xx_rdrequest(privep);
+            }
+        }
     }
   else
     {
@@ -1850,18 +1884,31 @@ static int rp23xx_epstall(struct usbdev_ep_s *ep, bool 
resume)
           /* EP0 IN Transfer ongoing : postpone the stall until the end */
 
           privep->pending_stall = true;
+          priv->zlp_stat = RP23XX_ZLP_NONE;
+          spin_unlock_irqrestore(&g_usbdev.lock, flags);
         }
       else
         {
           /* Stall immediately */
 
           rp23xx_epstall_exec(ep);
-        }
+          priv->zlp_stat = RP23XX_ZLP_NONE;
+          spin_unlock_irqrestore(&g_usbdev.lock, flags);
 
-      priv->zlp_stat = RP23XX_ZLP_NONE;
+          /* Terminate the IN transfer the halt aborted: its buffer was
+           * disarmed by the STALL write above and would never complete.
+           * Anything the class resubmits stays queued until the halt
+           * clears.
+           */
+
+          if (privep->epphy != 0 && privep->in && !rp23xx_rqempty(privep))
+            {
+              rp23xx_cancelrequests(privep);
+            }
+        }
     }
 
-  spin_unlock_irqrestore(&g_usbdev.lock, flags);
+  leave_critical_section(irqflags);
 
   return OK;
 }

Reply via email to