DMA Drivers
Overview
The common DMA framework is a header-only interface defined in
include/nuttx/dma/dma.h. It was introduced in 2018 (596f52c3ee,
“Nuttx/dma: add dma framework for nuttx”) with link transfers added in
2019 (51475e4273), per-channel DRQ selection in 2023 (06ee8d9673),
and source/destination address stepping in 2023 (366628bf5a).
There is no upper-half implementation file: drivers/dma/ contains
only Kconfig and Make.defs. The header defines the controller
and channel structures, the operation table, and thin macros through
which peripheral drivers (DMA clients) drive SoC-specific DMA
controller implementations. All hardware-specific functionality lives
in the controller implementation.
The framework is enabled with CONFIG_DMA (drivers/dma/Kconfig),
which selects ARCH_DMA. Scatter/gather-style link transfers
additionally require CONFIG_DMA_LINK.
Architecture
Peripheral driver (DMA client)
|
v
DMA common interface (macros in include/nuttx/dma/dma.h)
|
v
DMA controller implementation (struct dma_dev_s,
per-channel struct dma_chan_s + struct dma_ops_s)
|
v
DMA hardware
Terminology as used by the header:
DMA controller: represented by
struct dma_dev_s. It owns a set of channels and hands them out on request.DMA channel: represented by
struct dma_chan_s. The client-visible portion is only anopspointer; the controller implementation extends this structure with its own device-specific fields after it.DMA client: a peripheral driver that acquires a channel and starts transfers through the macros below (in-tree:
drivers/audio/audio_dma.c,drivers/serial/uart_16550.c).DMA transfer: a single
DMA_START*invocation described by a callback, a destination/source address pair, and lengths. There is no transfer descriptor structure in the framework.
Most DMA code under arch/ (for example stm32_dma.c,
kinetis_edma.c, cxd56_dmac.c, rp2040_dmac.c) does not
implement this framework; those drivers expose their own SoC-specific
APIs (for example stm32_dmastart(), cxd56_dmastart()). No
in-tree controller implements struct dma_dev_s yet, so a board
using the common framework must supply its own controller.
Controller Interface
struct dma_dev_s (include/nuttx/dma/dma.h) is the controller
vtable:
get_chan(dev, ident): return the channel identified byidentwith mutually exclusive access. Per the header, the call waits until the current holder releases the channel withput_chan().put_chan(dev, chan): release a channel and wake any waiter. The releasedchanmust not be used again until re-acquired.
struct dma_ops_s is the per-channel operation table. Every entry is
invoked through a DMA_* macro taking the channel as first argument:
config(chan, cfg): configure the channel fromstruct dma_config_sbefore use.start(chan, callback, arg, dst, src, len): one-shot transfer oflenbytes fromsrctodst;callbackruns on completion.start_cyclic(chan, callback, arg, dst, src, len, period_len): cyclic transfer overlenbytes;callbackruns perperiod_lenchunk.start_link(chan, callback, arg, work_mode, cfg)(CONFIG_DMA_LINKonly): scatter/gather transfer described bystruct dma_link_config_s;callbackruns when the link transfer finishes.work_modeis one ofDMA_BLOCK_MODE,DMA_SRC_LINK_MODE,DMA_DST_LINK_MODE,DMA_DUAL_LINK_MODE.stop(chan): stop the transfer.pause(chan)/resume(chan): pause a transfer;resume()must followpause()to restart it.residual(chan): bytes remaining to be transferred.
struct dma_config_s fields: direction (DMA_MEM_TO_MEM,
DMA_MEM_TO_DEV, DMA_DEV_TO_MEM, DMA_DEV_TO_DEV),
priority, timeout, option, dst_width / src_width
(FIFO/register width in bytes: 1, 2, 4, 8), dst_drq / src_drq
(physical DMA request IDs), dst_step / src_step (address shift
after each transfer; negative counts down). Zero means “keep the
current value”.
dma_callback_t (void (*)(struct dma_chan_s *chan, void *arg,
ssize_t len)): completion callback. chan is the finished channel,
arg is the value passed to start*, and len is the transfer
length on success or a negative error code on failure. The DMA module
performs no cache maintenance; on RX completion the client must
invalidate the DMA buffers.
Using DMA from a Driver
Client-facing macros (all in include/nuttx/dma/dma.h):
DMA_GET_CHAN(dev, ident)/DMA_PUT_CHAN(dev, chan): acquire / release a channel from astruct dma_dev_s *.DMA_CONFIG(chan, cfg): apply astruct dma_config_s.DMA_START(chan, callback, arg, dst, src, len): one-shot transfer.DMA_START_CYCLIC(chan, callback, arg, dst, src, len, period_len): cyclic transfer.DMA_START_LINK(chan, callback, arg, mode, link_cfg)(CONFIG_DMA_LINKonly): link transfer.DMA_PAUSE(chan)/DMA_RESUME(chan)/DMA_STOP(chan)/DMA_RESIDUAL(chan): transfer control and status.
Address and cache rules, established by the header comments and both in-tree clients:
Addresses passed to
start*are physical addresses. Both clients translate withup_addrenv_va_to_pa()(FIFO register addresses and DMA buffers alike).The DMA module performs no cache operations. The client cleans (
up_clean_dcache()) TX buffers beforeDMA_STARTand invalidates (up_invalidate_dcache()) RX data after completion. Seeu16550_dmasend()/u16550_dmareceive()indrivers/serial/uart_16550.candaudio_dma_enqueuebuffer()/audio_dma_callback()indrivers/audio/audio_dma.c.
Channel and Transfer Lifecycle
The framework defines no registration helper; the controller instance reaches the client out of band (see the examples below). The observed lifecycle is:
Acquire channel (DMA_GET_CHAN or equivalent)
|
v
Configure channel (DMA_CONFIG)
|
v
Start transfer (DMA_START / DMA_START_CYCLIC / DMA_START_LINK)
|
v
Hardware execution, completion callback per transfer/period
|
v
Pause/resume around flow control or underrun (optional)
|
v
Stop (DMA_STOP) and/or release (DMA_PUT_CHAN)
Two in-tree patterns exist:
One-shot TX:
drivers/serial/uart_16550.cu16550_dmasend()callsDMA_STARTonce per UART TX buffer with completion callbacku16550_dmasend_done(), which reports the byte count viauart_xmitchars_done()and chains the next buffer, or retries the transfer iflenindicates failure.Never-ending cyclic RX:
u16550_dmarxfree()configuresDMA_DEV_TO_MEMwith a timeout derived from baud rate and startsDMA_START_CYCLIConce over the RX ring buffer with perioddmarxsize / 4;u16550_dmareceive_done()advancesdmarxheadfrom the callbacklenand drains viauart_recvchars_dma().DMA_PAUSE()/DMA_RESUME()implement RX flow control.drivers/audio/audio_dma.cfollows the same cyclic model:audio_dma_configure()sets direction and FIFO width,audio_dma_start()callsDMA_START_CYCLICover all audio buffers,audio_dma_callback()dequeues one finished buffer per period, and underrun pauses the channel until more buffers are enqueued.audio_dma_stop()callsDMA_STOP()and flushes the pending queue.
Gaps established from source (no in-tree use besides the header):
DMA_PUT_CHAN()is never called in-tree; neither client releases its channel after acquisition.DMA_RESIDUAL()andDMA_START_LINK()have no in-tree callers.DMA_STOP()is used only byaudio_dma; the UART driver never stops its channels.
DMA Links
When CONFIG_DMA_LINK is set, a transfer can walk arrays of
struct dma_link_s (addr plus link_num / link_size)
bundled in struct dma_link_config_s (dst_link_num,
src_link_num, dst_link, src_link) instead of a single
contiguous buffer. The mode argument selects which side uses links:
DMA_BLOCK_MODE, DMA_SRC_LINK_MODE, DMA_DST_LINK_MODE,
DMA_DUAL_LINK_MODE.
This mechanism is optional and currently has no in-tree controller
implementation and no in-tree user; only CONFIG_DMA_LINK=y board
defconfigs (for example boards/xtensa/esp32/esp32-sparrow-kit)
select the option. Controller authors should treat the link array
layout and ownership as defined solely by include/nuttx/dma/dma.h
until an in-tree implementation exists.
Implementing a DMA Controller
A controller implementation must provide:
An instance of
struct dma_dev_swith workingget_chan/put_chanoperations implementing the blocking-acquire, use-after-release forbidden semantics documented in the header.Channel structures whose first member is
struct dma_chan_s(so astruct dma_chan_s *converts to the implementation structure), each carrying a populatedstruct dma_ops_stable.At minimum the
config/start/start_cyclic/stop/pause/resumeoperations used by the intended clients;residualandstart_linkare required only if clients useDMA_RESIDUAL()/DMA_START_LINK()(none do in-tree).
How the controller reaches the client depends on the client:
Audio:
audio_dma_initialize()(drivers/audio/audio_dma.c,include/nuttx/audio/audio_dma.h) takes astruct dma_dev_s *plus channel number directly and acquires the channel withDMA_GET_CHAN().16550 UART: the UART obtains channels through the platform hook
dmachan(priv, ident)instruct u16550_ops_s(include/nuttx/serial/uart_16550.h), which is expected to return astruct dma_chan_s *. The default hook,uart_dmachan(), is declared but has no in-tree definition, so a platform using 16550 DMA must either overrideops->dmachanor provide its ownuart_dmachan()(drivers/serial/uart_pci_16550.cshows the override pattern with a stub returningNULL, meaning no DMA). Channel identities, RX buffer sizes, and RX timeouts come fromCONFIG_16550_UARTn_DMA*options (drivers/serial/Kconfig-16550).
Existing Implementations
drivers/audio/audio_dma.c: clearest end-to-end client. Shows acquire (audio_dma_initialize()), direction/width configuration, cyclic start over the buffer set, per-period dequeue in the callback, pause on underrun, resume on enqueue, and stop with queue flush.drivers/serial/uart_16550.c: shows both one-shot TX chained from the completion callback and background cyclic RX with pause/resume flow control. Channel acquisition is delegated to the platformdmachanhook rather thanDMA_GET_CHAN().arch/arm64/src/bcm2711/hardware/bcm2711_dma.h: register definitions only, not a framework controller.Espressif
lldesc_t/gdma_*code (for examplearch/risc-v/src/common/espressif/esp_i2s.c) implements its own descriptor chaining outside this framework and must not be confused withCONFIG_DMA_LINKlink transfers.
Configuration
CONFIG_DMA: enable the common DMA interface (selectsARCH_DMA).CONFIG_DMA_LINK: enable link-transfer structures andDMA_START_LINK().ARCH_DMA/ARCH_DMA_NO_FLASH_TRANSFER(arch/Kconfig): SoC capability selects.CONFIG_16550_UARTn_DMA,CONFIG_16550_UARTn_DMA_TX,CONFIG_16550_UARTn_DMA_RX,CONFIG_16550_UARTn_DMA_RXBUFSIZE,CONFIG_16550_UARTn_DMA_RXTIMEOUT: 16550 UART DMA binding.CONFIG_AUDIO_BUFFER_NUMBYTES/CONFIG_AUDIO_NUM_BUFFERS: default buffer geometry used byaudio_dma_initialize().