This is an automated email from the ASF dual-hosted git repository.
acassis 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 a8ce5f7e4c9 arch/rp23xx: add bounded timeout to SPI DMA completion wait
a8ce5f7e4c9 is described below
commit a8ce5f7e4c965aa8d80017c506115f5688dbdd5e
Author: Sarthak Pandey <[email protected]>
AuthorDate: Sun Oct 11 22:13:44 2026 +0530
arch/rp23xx: add bounded timeout to SPI DMA completion wait
The SPI DMA exchange path previously waited for completion with two
untimed, uninterruptible semaphore waits (nxsem_wait_uninterruptible). If a DMA
transfer never completes (e.g. stalled slave or bus glitch), the calling thread
sleeps indefinitely.
In network drivers like W5500 on SPI0 running from the LP work queue while
holding net_lock, this causes the worker to hang permanently, wedging the whole
network stack and blocking recovery handlers.
This patch updates both ARM and RISC-V ports of rp23xx:
- Switches to nxsem_tickwait_uninterruptible() with a transfer-size-derived
timeout plus a safety margin.
- On timeout, forcibly stops both DMA channels with rp23xx_dmastop(),
disables SPI DMA in the control register, resets the semaphore count, drains
leftover RX FIFO bytes, and logs an error to degrade gracefully instead of
deadlocking.
Fixes #19305
Signed-off-by: Sarthak Pandey <[email protected]>
---
arch/arm/src/rp23xx/rp23xx_spi.c | 75 ++++++++++++++++++++++++++++------
arch/risc-v/src/rp23xx-rv/rp23xx_spi.c | 75 ++++++++++++++++++++++++++++------
2 files changed, 124 insertions(+), 26 deletions(-)
diff --git a/arch/arm/src/rp23xx/rp23xx_spi.c b/arch/arm/src/rp23xx/rp23xx_spi.c
index 699a847ce99..8db3951aa57 100644
--- a/arch/arm/src/rp23xx/rp23xx_spi.c
+++ b/arch/arm/src/rp23xx/rp23xx_spi.c
@@ -38,7 +38,9 @@
#include <arch/board/board.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
+#include <nuttx/clock.h>
#include <nuttx/mutex.h>
+#include <nuttx/semaphore.h>
#include <nuttx/spi/spi.h>
#include "arm_internal.h"
@@ -115,7 +117,7 @@ static void spi_dmatxsetup(struct rp23xx_spidev_s *priv,
const void *txbuffer, size_t nwords);
static void spi_dmarxsetup(struct rp23xx_spidev_s *priv,
const void *rxbuffer, size_t nwords);
-static void spi_dmatrxwait(struct rp23xx_spidev_s *priv);
+static int spi_dmatrxwait(struct rp23xx_spidev_s *priv, size_t nwords);
#ifndef CONFIG_SPI_EXCHANGE
static void spi_dmasndblock(struct spi_dev_s *dev,
const void *buffer, size_t nwords);
@@ -361,6 +363,7 @@ static void spi_enable_ssp(struct spi_dev_s *dev)
{
struct rp23xx_spidev_s *priv = (struct rp23xx_spidev_s *)dev;
uint32_t regval = spi_getreg(priv, RP23XX_SPI_SSPCR1_OFFSET);
+
regval |= RP23XX_SPI_SSPCR1_SSE;
spi_putreg(priv, RP23XX_SPI_SSPCR1_OFFSET, regval);
}
@@ -383,6 +386,7 @@ static void spi_disable_ssp(struct spi_dev_s *dev)
{
struct rp23xx_spidev_s *priv = (struct rp23xx_spidev_s *)dev;
uint32_t regval = spi_getreg(priv, RP23XX_SPI_SSPCR1_OFFSET);
+
regval &= ~RP23XX_SPI_SSPCR1_SSE;
spi_putreg(priv, RP23XX_SPI_SSPCR1_OFFSET, regval);
}
@@ -607,7 +611,8 @@ static uint32_t spi_send(struct spi_dev_s *dev, uint32_t wd)
/* Wait while the TX FIFO is full */
while (!(spi_getreg(priv, RP23XX_SPI_SSPSR_OFFSET) & RP23XX_SPI_SSPSR_TNF))
- ;
+ {
+ }
/* Write the byte to the TX FIFO */
@@ -616,7 +621,8 @@ static uint32_t spi_send(struct spi_dev_s *dev, uint32_t wd)
/* Wait for the RX FIFO not empty */
while (!(spi_getreg(priv, RP23XX_SPI_SSPSR_OFFSET) & RP23XX_SPI_SSPSR_RNE))
- ;
+ {
+ }
/* Get the value from the RX FIFO and return it */
@@ -972,17 +978,22 @@ void spi_flush(struct spi_dev_s *dev)
/* Wait for the TX FIFO not full indication */
while (!(spi_getreg(priv, RP23XX_SPI_SSPSR_OFFSET) & RP23XX_SPI_SSPSR_TNF))
- ;
+ {
+ }
+
spi_putreg(priv, RP23XX_SPI_SSPDR_OFFSET, 0xff);
/* Wait until TX FIFO and TX shift buffer are empty */
- while (spi_getreg(priv, RP23XX_SPI_SSPSR_OFFSET) & RP23XX_SPI_SSPSR_BSY);
+ while (spi_getreg(priv, RP23XX_SPI_SSPSR_OFFSET) & RP23XX_SPI_SSPSR_BSY)
+ {
+ }
/* Wait until RX FIFO is not empty */
while (!(spi_getreg(priv, RP23XX_SPI_SSPSR_OFFSET) & RP23XX_SPI_SSPSR_RNE))
- ;
+ {
+ }
/* Then read and discard bytes until the RX FIFO is empty */
@@ -1023,7 +1034,7 @@ static void spi_dmaexchange(struct spi_dev_s *dev,
/* Then wait for each to complete */
- spi_dmatrxwait(priv);
+ spi_dmatrxwait(priv, nwords);
}
#ifndef CONFIG_SPI_EXCHANGE
@@ -1185,22 +1196,45 @@ static void spi_dmarxsetup(struct rp23xx_spidev_s *priv,
* Name: spi_dmatrxwait
*
* Description:
- * Wait for TX RX DMA to complete.
+ * Wait for TX RX DMA to complete with a bounded timeout.
*
****************************************************************************/
-static void spi_dmatrxwait(struct rp23xx_spidev_s *priv)
+static int spi_dmatrxwait(struct rp23xx_spidev_s *priv, size_t nwords)
{
+ uint32_t freq;
uint32_t val;
+ uint64_t bits;
+ unsigned int ms;
+ clock_t timeout;
+ int ret;
+
+ /* Calculate timeout based on transfer size and SPI clock frequency.
+ * Add a 100ms safety margin to allow for OS scheduling delays.
+ */
- if (nxsem_wait_uninterruptible(&priv->dmasem) != OK)
+ freq = priv->actual > 0 ? priv->actual : priv->frequency;
+ if (freq == 0)
{
- spierr("dma error\n");
+ freq = 1000000;
}
- if (nxsem_wait_uninterruptible(&priv->dmasem) != OK)
+ bits = (uint64_t)nwords * (priv->nbits > 0 ? priv->nbits : 8);
+ ms = (unsigned int)((bits * 1000) / freq) + 100;
+ timeout = MSEC2TICK(ms);
+
+ ret = nxsem_tickwait_uninterruptible(&priv->dmasem, timeout);
+ if (ret < 0)
{
- spierr("dma error\n");
+ spierr("ERROR: DMA TX wait failed or timed out: %d\n", ret);
+ }
+ else
+ {
+ ret = nxsem_tickwait_uninterruptible(&priv->dmasem, timeout);
+ if (ret < 0)
+ {
+ spierr("ERROR: DMA RX wait failed or timed out: %d\n", ret);
+ }
}
rp23xx_dmastop(priv->txdmach);
@@ -1209,6 +1243,21 @@ static void spi_dmatrxwait(struct rp23xx_spidev_s *priv)
val = spi_getreg(priv, RP23XX_SPI_SSPDMACR_OFFSET);
val &= ~(RP23XX_SPI_SSPDMACR_RXDMAE | RP23XX_SPI_SSPDMACR_TXDMAE);
spi_putreg(priv, RP23XX_SPI_SSPDMACR_OFFSET, val);
+
+ if (ret < 0)
+ {
+ /* Clean up residual semaphore count and flush RX FIFO */
+
+ nxsem_reset(&priv->dmasem, 0);
+
+ while (spi_getreg(priv, RP23XX_SPI_SSPSR_OFFSET) &
+ RP23XX_SPI_SSPSR_RNE)
+ {
+ spi_getreg(priv, RP23XX_SPI_SSPDR_OFFSET);
+ }
+ }
+
+ return ret;
}
#endif
diff --git a/arch/risc-v/src/rp23xx-rv/rp23xx_spi.c
b/arch/risc-v/src/rp23xx-rv/rp23xx_spi.c
index ec9c0746bd7..59d9b0fc9b9 100644
--- a/arch/risc-v/src/rp23xx-rv/rp23xx_spi.c
+++ b/arch/risc-v/src/rp23xx-rv/rp23xx_spi.c
@@ -38,7 +38,9 @@
#include <arch/board/board.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
+#include <nuttx/clock.h>
#include <nuttx/mutex.h>
+#include <nuttx/semaphore.h>
#include <nuttx/spi/spi.h>
#include "riscv_internal.h"
@@ -115,7 +117,7 @@ static void spi_dmatxsetup(struct rp23xx_spidev_s *priv,
const void *txbuffer, size_t nwords);
static void spi_dmarxsetup(struct rp23xx_spidev_s *priv,
const void *rxbuffer, size_t nwords);
-static void spi_dmatrxwait(struct rp23xx_spidev_s *priv);
+static int spi_dmatrxwait(struct rp23xx_spidev_s *priv, size_t nwords);
#ifndef CONFIG_SPI_EXCHANGE
static void spi_dmasndblock(struct spi_dev_s *dev,
const void *buffer, size_t nwords);
@@ -361,6 +363,7 @@ static void spi_enable_ssp(struct spi_dev_s *dev)
{
struct rp23xx_spidev_s *priv = (struct rp23xx_spidev_s *)dev;
uint32_t regval = spi_getreg(priv, RP23XX_RV_SPI_SSPCR1_OFFSET);
+
regval |= RP23XX_RV_SPI_SSPCR1_SSE;
spi_putreg(priv, RP23XX_RV_SPI_SSPCR1_OFFSET, regval);
}
@@ -383,6 +386,7 @@ static void spi_disable_ssp(struct spi_dev_s *dev)
{
struct rp23xx_spidev_s *priv = (struct rp23xx_spidev_s *)dev;
uint32_t regval = spi_getreg(priv, RP23XX_RV_SPI_SSPCR1_OFFSET);
+
regval &= ~RP23XX_RV_SPI_SSPCR1_SSE;
spi_putreg(priv, RP23XX_RV_SPI_SSPCR1_OFFSET, regval);
}
@@ -608,7 +612,8 @@ static uint32_t spi_send(struct spi_dev_s *dev, uint32_t wd)
while (!(spi_getreg(priv, RP23XX_RV_SPI_SSPSR_OFFSET) &
RP23XX_RV_SPI_SSPSR_TNF))
- ;
+ {
+ }
/* Write the byte to the TX FIFO */
@@ -618,7 +623,8 @@ static uint32_t spi_send(struct spi_dev_s *dev, uint32_t wd)
while (!(spi_getreg(priv, RP23XX_RV_SPI_SSPSR_OFFSET) &
RP23XX_RV_SPI_SSPSR_RNE))
- ;
+ {
+ }
/* Get the value from the RX FIFO and return it */
@@ -976,19 +982,24 @@ void spi_flush(struct spi_dev_s *dev)
while (!(spi_getreg(priv, RP23XX_RV_SPI_SSPSR_OFFSET) &
RP23XX_RV_SPI_SSPSR_TNF))
- ;
+ {
+ }
+
spi_putreg(priv, RP23XX_RV_SPI_SSPDR_OFFSET, 0xff);
/* Wait until TX FIFO and TX shift buffer are empty */
while (spi_getreg(priv, RP23XX_RV_SPI_SSPSR_OFFSET) &
- RP23XX_RV_SPI_SSPSR_BSY);
+ RP23XX_RV_SPI_SSPSR_BSY)
+ {
+ }
/* Wait until RX FIFO is not empty */
while (!(spi_getreg(priv, RP23XX_RV_SPI_SSPSR_OFFSET) &
RP23XX_RV_SPI_SSPSR_RNE))
- ;
+ {
+ }
/* Then read and discard bytes until the RX FIFO is empty */
@@ -1030,7 +1041,7 @@ static void spi_dmaexchange(struct spi_dev_s *dev,
/* Then wait for each to complete */
- spi_dmatrxwait(priv);
+ spi_dmatrxwait(priv, nwords);
}
#ifndef CONFIG_SPI_EXCHANGE
@@ -1192,22 +1203,45 @@ static void spi_dmarxsetup(struct rp23xx_spidev_s *priv,
* Name: spi_dmatrxwait
*
* Description:
- * Wait for TX RX DMA to complete.
+ * Wait for TX RX DMA to complete with a bounded timeout.
*
****************************************************************************/
-static void spi_dmatrxwait(struct rp23xx_spidev_s *priv)
+static int spi_dmatrxwait(struct rp23xx_spidev_s *priv, size_t nwords)
{
+ uint32_t freq;
uint32_t val;
+ uint64_t bits;
+ unsigned int ms;
+ clock_t timeout;
+ int ret;
+
+ /* Calculate timeout based on transfer size and SPI clock frequency.
+ * Add a 100ms safety margin to allow for OS scheduling delays.
+ */
- if (nxsem_wait_uninterruptible(&priv->dmasem) != OK)
+ freq = priv->actual > 0 ? priv->actual : priv->frequency;
+ if (freq == 0)
{
- spierr("dma error\n");
+ freq = 1000000;
}
- if (nxsem_wait_uninterruptible(&priv->dmasem) != OK)
+ bits = (uint64_t)nwords * (priv->nbits > 0 ? priv->nbits : 8);
+ ms = (unsigned int)((bits * 1000) / freq) + 100;
+ timeout = MSEC2TICK(ms);
+
+ ret = nxsem_tickwait_uninterruptible(&priv->dmasem, timeout);
+ if (ret < 0)
{
- spierr("dma error\n");
+ spierr("ERROR: DMA TX wait failed or timed out: %d\n", ret);
+ }
+ else
+ {
+ ret = nxsem_tickwait_uninterruptible(&priv->dmasem, timeout);
+ if (ret < 0)
+ {
+ spierr("ERROR: DMA RX wait failed or timed out: %d\n", ret);
+ }
}
rp23xx_dmastop(priv->txdmach);
@@ -1216,6 +1250,21 @@ static void spi_dmatrxwait(struct rp23xx_spidev_s *priv)
val = spi_getreg(priv, RP23XX_RV_SPI_SSPDMACR_OFFSET);
val &= ~(RP23XX_RV_SPI_SSPDMACR_RXDMAE | RP23XX_RV_SPI_SSPDMACR_TXDMAE);
spi_putreg(priv, RP23XX_RV_SPI_SSPDMACR_OFFSET, val);
+
+ if (ret < 0)
+ {
+ /* Clean up residual semaphore count and flush RX FIFO */
+
+ nxsem_reset(&priv->dmasem, 0);
+
+ while (spi_getreg(priv, RP23XX_RV_SPI_SSPSR_OFFSET) &
+ RP23XX_RV_SPI_SSPSR_RNE)
+ {
+ spi_getreg(priv, RP23XX_RV_SPI_SSPDR_OFFSET);
+ }
+ }
+
+ return ret;
}
#endif