Skip to content

Bus: send DMA-inaccessible sources (PSRAM / flash on ESP32) through the CPU path - #324

Merged
lovyan03 merged 1 commit into
m5stack:developfrom
ainyan03:dma_source_check
Oct 1, 2026
Merged

lovyan03 merged 1 commit into
m5stack:developfrom
ainyan03:dma_source_check

Conversation

@ainyan03

@ainyan03 ainyan03 commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

pushImageDMA() hands the caller's pointer straight to the bus. On ESP32 the SPI DMA cannot read PSRAM or flash, so an image held in PSRAM is sent as garbage. Sprites are not affected: pushSprite() already checks heap_capable_dma() before asking for DMA.

The SPI bus and the ESP32-S2 / ESP32-S3 parallel buses now check heap_capable_dma() on the source and fall back to their CPU (copy) path when it is false.

Target PSRAM source Flash source
ESP32 CPU (was DMA → garbage) CPU (was DMA)
ESP32-S3 and other GDMA targets DMA (unchanged) CPU (was handed to the DMA as external RAM)
ESP32-S2 CPU (was DMA without writing the data cache back) CPU (was DMA)

Internal RAM sources keep using DMA on all targets. addDMAQueue() drains the queue and waits before the CPU output, as the existing flash-encryption fallback already does, so a queued descriptor never reads a reused flip buffer.

Reproduction (ESP32 with 2 MB quad PSRAM, 135x240 ST7789 on SPI, Arduino core 3.x with -DBOARD_HAS_PSRAM)

auto buf = (uint16_t*)heap_caps_malloc(w * h * 2, MALLOC_CAP_SPIRAM);
// fill buf ...
display.startWrite();
display.pushImageDMA(0, 0, w, h, buf);
display.endWrite();

Reading the full screen back with readRect() after a full-screen transfer:

Internal RAM source PSRAM source
Before 0 / 64800 pixels differ 64800 / 64800 differ
After 0 / 64800 0 / 64800 (CPU, 13.4 ms)

ESP32-S3 boards (octal and quad PSRAM) still read back identical and keep the PSRAM DMA timing.

pushImageDMA() hands the caller's pointer straight to the bus. On ESP32 the
SPI DMA cannot read PSRAM or flash, so an image held in PSRAM was sent as
garbage (every pixel differed when read back). Sprites were not affected:
pushSprite() already checks heap_capable_dma() before asking for DMA.

The SPI bus and the ESP32-S2 / ESP32-S3 parallel buses now check
heap_capable_dma() on the source and fall back to their CPU (copy) path
when it is false:
- ESP32: PSRAM and flash sources take the CPU path.
- ESP32-S3 and the other GDMA targets: PSRAM keeps using DMA. Flash
  constants, which were handed to the DMA as external RAM, take the CPU path.
- ESP32-S2: PSRAM sources take the CPU path. They used to go to the DMA
  without writing the data cache back first.
@lovyan03
lovyan03 merged commit fe7b244 into m5stack:develop Oct 1, 2026
28 checks passed
@ainyan03
ainyan03 deleted the dma_source_check branch October 1, 2026 00:47
ainyan03 added a commit to ainyan03/M5GFX that referenced this pull request Oct 2, 2026
pushImageDMA() hands the caller's pointer straight to the bus. On the
ESP32 the SPI DMA cannot read PSRAM or flash, so an image held in PSRAM
was sent as garbage. The SPI bus now sends such sources by CPU, after
the queued DMA has finished. External RAM stays on the DMA path where
the SPI DMA can read it (GDMA targets without flash encryption), with
the existing alignment handling.

The ESP32-S2 / S3 parallel buses copy DMA-inaccessible sources (PSRAM,
flash) into their internal DMA buffer instead of passing them to the
DMA without writing the data cache back.

Backport of m5stack#324.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants