GitHub

Docs / API reference / syn_hal_dma.h

syn_hal_dma.hv0.5.0

<synaptic/syn_hal_dma.h> is the DMA hardware abstraction layer. It models channel-based transfers between peripherals and memory, including circular (continuously re-arming) transfers for live capture. As of v0.5.0 the API is implemented: on the FRDM-MCXN947 it drives the eDMA for memory-to-memory transfers (the zero-copy ingest path benchmarked at +251% over CPU copy), and on QEMU a software stub preserves the callback contract so the ingest pump is unit-testable. Peripheral endpoints (SYN_DMA_PERIPH_CAMERA, SYN_DMA_PERIPH_SPI) return -ENOTSUP until the camera path lands — SmartDMA stays reserved for that Phase 6 bring-up.

Types & constants

NameKindDescription
syn_dma_periph_tenumTransfer endpoint type: SYN_DMA_PERIPH_CAMERA (camera interface), SYN_DMA_PERIPH_SPI (SPI peripheral), SYN_DMA_PERIPH_MEMORY (plain memory — use for both ends of a memory-to-memory copy).
syn_dma_config_tstructChannel configuration: src_periph / dst_periph (endpoint types), src_addr / dst_addr (buffer or peripheral addresses), transfer_size (bytes per transfer), circular (when true, the channel re-arms automatically after each completed transfer).
syn_dma_cb_ttypedefvoid (*)(int channel, int status, void *user_data) — completion callback fired per finished transfer. status is 0 on success or a negative errno.

Functions

SignatureDescription
int syn_hal_dma_init(void)Initialize the DMA subsystem. Returns 0 on success or a negative errno.
int syn_hal_dma_configure(int channel, const syn_dma_config_t *config)Program a channel with endpoints, addresses, transfer size, and circular mode. Must precede syn_hal_dma_start(). Returns 0 on success or a negative errno (e.g. invalid channel or config).
int syn_hal_dma_start(int channel, syn_dma_cb_t callback, void *user_data)Arm the channel and begin transferring. callback is invoked with user_data when a transfer completes; in circular mode it fires once per lap. Returns 0 on success or a negative errno.
int syn_hal_dma_stop(int channel)Stop an active channel. Required to end a circular transfer. Returns 0 on success or a negative errno.
int syn_hal_dma_get_remaining(int channel, size_t *remaining)Write the number of bytes still outstanding in the current transfer to *remaining. Returns 0 on success or a negative errno.

Backends

mcxn947 — eDMA (src/hal/mcxn947/syn_hal_dma_edma.c, new in Phase 5). Implements the API over Zephyr's DMA driver on edma0, mapping SynapticOS channels 0–3 to eDMA channels 8–11 (clear of the FlexComm request lines). Memory-to-memory only; word-wide when 4-aligned, else bytes; callbacks fire in ISR context; circular mode is a software re-arm from the completion callback. The board bring-up surfaced real silicon behavior the HAL now handles itself: Zephyr 3.7's driver never issues the software START that eDMA v4 mem-to-mem transfers need (and arms the hardware request on mux source 0), the latched DONE flag is write-1-to-clear and silently gates the next START, and TrustZone secure-alias (bit 28) addresses bus-error inside the DMA — DMA-visible buffers must use plain aliases. Builds without CONFIG_DMA compile -ENOSYS fallbacks.

stub (src/hal/stub/syn_hal_dma_stub.c, new in Phase 5). Asynchronous copies on the system workqueue preserving the callback contract; circular mode re-arms until stopped. Functional only — stub "DMA" is CPU work, so no throughput claims on QEMU.

Usage

The intended camera-to-arena streaming pattern once the camera path lands (peripheral endpoints return -ENOTSUP in v0.5.0; memory-to-memory transfers, including circular ones, work today — the double-buffered ingest pump in src/core/syn_ingest.c is the reference consumer):

c
#include <synaptic/syn_hal_dma.h>

static void frame_done(int channel, int status, void *user_data)
{
    if (status == 0) {
        /* A full frame is in the arena buffer: kick off inference */
    }
}

syn_dma_config_t cfg = {
    .src_periph    = SYN_DMA_PERIPH_CAMERA,
    .dst_periph    = SYN_DMA_PERIPH_MEMORY,
    .src_addr      = NULL,            /* camera FIFO, backend-resolved */
    .dst_addr      = frame_buf,       /* tensor arena destination     */
    .transfer_size = 96 * 96 * 3,     /* one RGB frame                */
    .circular      = true,            /* keep streaming frames        */
};

syn_hal_dma_init();
syn_hal_dma_configure(0, &cfg);
syn_hal_dma_start(0, frame_done, NULL);
/* ... later ... */
syn_hal_dma_stop(0);

Notes

Status · implemented in Phase 5 (memory-to-memory)

The frozen v0.1.0 contract is what the Phase 5 eDMA backend now fulfills, board-verified on 2026-08-10: 1000 × 8 KB frames through the double-buffered ingest pump at 131 µs/frame vs 460 µs/frame for the CPU-copy baseline, zero corrupt frames, zero DMA errors. One honest caveat from the same session: the tensor arena region is not eDMA-reachable under the current bus security attributes — and because the SoC shares the eDMA error IRQ away from this path, a faulted transfer is a silent timeout. The benchmark uses DMA-reachable static buffers; making the arena DMA-visible (MPC/SAU attributes) is a Phase 6 item, so today "zero-copy into the tensor arena" is accurately "zero-copy into DMA-reachable buffers".

  • Channels are caller-managed integers 0–3, mapped by the eDMA backend to hardware channels 8–11.
  • Completion callbacks run in ISR context: keep them short and defer real work to a thread.
  • circular = true is the mode intended for continuous capture; pair it with syn_hal_dma_get_remaining() to observe progress within a frame.
  • Do not stop and re-arm a channel between back-to-back one-shot transfers — an abort in that window wedges the next completion (board finding); keep the channel armed, as the ingest pump does.