This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 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 58d1d9f9d6f drivers/usbdev/cdcacm: serialize the TX ring drain with
the class spinlock
58d1d9f9d6f is described below
commit 58d1d9f9d6f512ce52828a850ac750aa41ceedd2
Author: raiden00pl <[email protected]>
AuthorDate: Tue Jul 21 15:26:35 2026 +0200
drivers/usbdev/cdcacm: serialize the TX ring drain with the class spinlock
cdcacm_sndpacket() runs from task context and from the bulk IN
completion callback, which may be interrupt context. cdcuart_dmasend()
advances the xmit tail non-atomically, so a completion arriving mid
setup re-sends the same region and advances the tail past the head,
re-transmitting a ring of stale data.
c497c5feb0 dropped the critical section that used to cover this.
Restore it with priv->lock held across the setup and EP_SUBMIT; the
submit must stay inside to keep request order. cdcuart_dmasend() now
runs with the lock held, so its own acquisition is removed.
The race needs the writer to keep the ring non-empty across
completions, so it only appears at high sustained write rates. On
nRF52840, 131072-byte writes were received as ~147600 bytes - one extra
CDCACM_TXBUFSIZE of stale data per hit. With this change the host
receives exactly what was sent.
Assisted-by: Claude Code
Signed-off-by: raiden00pl <[email protected]>
---
drivers/usbdev/cdcacm.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/usbdev/cdcacm.c b/drivers/usbdev/cdcacm.c
index 5b003218fb6..87671cf0ae4 100644
--- a/drivers/usbdev/cdcacm.c
+++ b/drivers/usbdev/cdcacm.c
@@ -424,9 +424,9 @@ static int cdcacm_sndpacket(FAR struct cdcacm_dev_s *priv)
FAR struct uart_dev_s *dev = &priv->serdev;
FAR struct cdcacm_wrreq_s *wrcontainer;
FAR struct usbdev_req_s *req;
- irqstate_t flags;
int ret;
#endif
+ irqstate_t flags;
#ifdef CONFIG_DEBUG_FEATURES
if (priv == NULL)
@@ -485,10 +485,17 @@ static int cdcacm_sndpacket(FAR struct cdcacm_dev_s *priv)
spin_unlock_irqrestore_nopreempt(&priv->lock, flags);
#else
+ /* Serialize against the write completion callback, which may call
+ * this from interrupt context.
+ */
+
+ flags = spin_lock_irqsave(&priv->lock);
if (!sq_empty(&priv->txfree))
{
uart_xmitchars_dma(&priv->serdev);
}
+
+ spin_unlock_irqrestore(&priv->lock, flags);
#endif
out:
@@ -3003,6 +3010,8 @@ static int cdcuart_release(FAR struct uart_dev_s *dev)
* Description:
* Set up to transfer bytes from the TX circular buffer.
*
+ * Called from cdcacm_sndpacket() with priv->lock held.
+ *
****************************************************************************/
static void cdcuart_dmasend(FAR struct uart_dev_s *dev)
@@ -3012,7 +3021,6 @@ static void cdcuart_dmasend(FAR struct uart_dev_s *dev)
FAR struct usbdev_ep_s *ep = priv->epbulkin;
FAR struct cdcacm_wrreq_s *wrcontainer;
FAR struct usbdev_req_s *req;
- irqstate_t flags;
size_t nbytes;
size_t reqlen;
int ret;
@@ -3023,11 +3031,9 @@ static void cdcuart_dmasend(FAR struct uart_dev_s *dev)
/* Peek at the request in the container at the head of the list */
- flags = spin_lock_irqsave(&priv->lock);
wrcontainer = (FAR struct cdcacm_wrreq_s *)sq_remfirst(&priv->txfree);
req = wrcontainer->req;
priv->nwrq--;
- spin_unlock_irqrestore(&priv->lock, flags);
/* Fill the request with serial TX data */