arch/xtensa/esp32s3: fix CAM DMA race and heap-allocate descriptors

- Remove debug polling loop from esp32s3_cam_start_capture() that
  busy-waited on DMA status register.

- Fix DMA ISR race: stop DMA before invoking capture callback to
  prevent ISR re-triggering while callback processes the buffer.
  Check priv->capturing before restarting DMA in ISR.

- Move DMA descriptors from struct to heap allocation, avoiding
  stack/BSS alignment issues with cache-line-aligned descriptors.

Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
This commit is contained in:
wangjianyu3
2026-03-21 10:00:00 +08:00
committed by Alan C. Assis
parent f8ef8e7bb1
commit f92b3d8d79
+80 -56
View File
@@ -89,7 +89,7 @@ struct esp32s3_cam_s
uint8_t cpu;
int32_t dma_channel;
struct esp32s3_dmadesc_s dmadesc[ESP32S3_CAM_DMADESC_NUM];
struct esp32s3_dmadesc_s *dmadesc; /* Heap-allocated DMA descriptors */
uint8_t *fb; /* Frame buffer */
uint32_t fb_size; /* Frame buffer size */
@@ -195,7 +195,13 @@ static int IRAM_ATTR cam_interrupt(int irq, void *context, void *arg)
struct timeval ts;
uint32_t regval;
/* Stop capture */
/* Stop capture and DMA before invoking callback.
* The callback may call set_buf() which rewrites DMA
* descriptors. If DMA is still draining the CAM AFIFO
* it could read half-written descriptors and follow a
* corrupted pbuf pointer, writing pixel data over
* unrelated memory (e.g. g_cam_priv.data.ops).
*/
regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
regval &= ~LCD_CAM_CAM_START_M;
@@ -205,54 +211,72 @@ static int IRAM_ATTR cam_interrupt(int irq, void *context, void *arg)
regval |= LCD_CAM_CAM_UPDATE_REG_M;
putreg32(regval, LCD_CAM_CAM_CTRL_REG);
/* Stop DMA channel before callback to prevent race */
esp32s3_dma_reset_channel(priv->dma_channel, false);
gettimeofday(&ts, NULL);
/* Notify frame complete */
priv->cb(0, priv->fb_size, &ts, priv->cb_arg);
/* Restart capture for next frame */
/* Check if callback called stop_capture. With a
* single-buffer FIFO the V4L2 layer stops capture
* inside the callback; restarting DMA after that
* would run unsynchronized and corrupt memory.
*/
priv->vsync_cnt = 0;
if (!priv->capturing)
{
priv->vsync_cnt = 0;
}
else
{
/* Restart capture for next frame */
/* Reset DMA */
priv->vsync_cnt = 0;
esp32s3_dma_reset_channel(priv->dma_channel, false);
/* DMA channel was already reset before the
* callback above. Reset CAM + AFIFO now.
*/
/* Reset CAM + AFIFO */
/* Reset CAM + AFIFO */
regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
regval |= LCD_CAM_CAM_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval &= ~LCD_CAM_CAM_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
regval |= LCD_CAM_CAM_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval &= ~LCD_CAM_CAM_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval |= LCD_CAM_CAM_AFIFO_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval &= ~LCD_CAM_CAM_AFIFO_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval |= LCD_CAM_CAM_AFIFO_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval &= ~LCD_CAM_CAM_AFIFO_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
/* Re-set REC_DATA_BYTELEN after reset */
/* Re-set REC_DATA_BYTELEN after reset */
regval &= ~LCD_CAM_CAM_REC_DATA_BYTELEN_M;
regval |= ((ESP32S3_CAM_DMA_BUFLEN - 1)
<< LCD_CAM_CAM_REC_DATA_BYTELEN_S);
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval &= ~LCD_CAM_CAM_REC_DATA_BYTELEN_M;
regval |= ((ESP32S3_CAM_DMA_BUFLEN - 1)
<< LCD_CAM_CAM_REC_DATA_BYTELEN_S);
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
/* Reload DMA descriptors */
/* Reload DMA descriptors */
esp32s3_dma_load(priv->dmadesc, priv->dma_channel, false);
esp32s3_dma_enable(priv->dma_channel, false);
esp32s3_dma_load(priv->dmadesc, priv->dma_channel,
false);
esp32s3_dma_enable(priv->dma_channel, false);
/* Restart */
/* Restart */
regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
regval |= LCD_CAM_CAM_START_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
regval |= LCD_CAM_CAM_START_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval = getreg32(LCD_CAM_CAM_CTRL_REG);
regval |= LCD_CAM_CAM_UPDATE_REG_M;
putreg32(regval, LCD_CAM_CAM_CTRL_REG);
regval = getreg32(LCD_CAM_CAM_CTRL_REG);
regval |= LCD_CAM_CAM_UPDATE_REG_M;
putreg32(regval, LCD_CAM_CAM_CTRL_REG);
}
}
}
}
@@ -497,6 +521,23 @@ static int esp32s3_cam_init(struct imgdata_s *data)
spin_lock_init(&priv->lock);
/* Allocate DMA descriptors from heap so they are isolated from
* the driver struct. If GDMA ever follows a stale/corrupted
* descriptor it will scribble on heap, not on g_cam_priv.
*/
if (priv->dmadesc == NULL)
{
priv->dmadesc = kmm_memalign(4,
sizeof(struct esp32s3_dmadesc_s) *
ESP32S3_CAM_DMADESC_NUM);
if (priv->dmadesc == NULL)
{
snerr("ERROR: Failed to allocate DMA descriptors\n");
return -ENOMEM;
}
}
/* Configure GPIO pins */
esp32s3_cam_gpio_config();
@@ -539,6 +580,14 @@ static int esp32s3_cam_uninit(struct imgdata_s *data)
priv->dma_channel = -1;
}
/* Free DMA descriptors */
if (priv->dmadesc)
{
kmm_free(priv->dmadesc);
priv->dmadesc = NULL;
}
/* Free frame buffer */
if (priv->fb)
@@ -708,31 +757,6 @@ static int esp32s3_cam_start_capture(struct imgdata_s *data,
priv->capturing = true;
syslog(LOG_INFO, "CAM start: CTRL=0x%08lx CTRL1=0x%08lx ENA=0x%08lx\n",
(unsigned long)getreg32(LCD_CAM_CAM_CTRL_REG),
(unsigned long)getreg32(LCD_CAM_CAM_CTRL1_REG),
(unsigned long)getreg32(LCD_CAM_LC_DMA_INT_ENA_REG));
/* Debug: poll RAW register to see if VSYNC appears */
for (int i = 0; i < 50; i++)
{
up_mdelay(20);
uint32_t raw = getreg32(LCD_CAM_LC_DMA_INT_RAW_REG);
uint32_t st = getreg32(LCD_CAM_LC_DMA_INT_ST_REG);
if (raw || st)
{
syslog(LOG_INFO, "CAM poll[%d]: raw=0x%08lx st=0x%08lx\n",
i, (unsigned long)raw, (unsigned long)st);
break;
}
if (i == 49)
{
syslog(LOG_INFO, "CAM poll: no VSYNC after 1s\n");
}
}
return OK;
}