A DMA engine, short for direct memory access, is a small block that copies data between memories and devices by itself, so the processor doesn't have to move every word. The processor tells it what to move and where, then goes on with other work, and the engine raises an interrupt when it is done. Almost every SoC has several. This article explains what a DMA engine contains, how descriptor chains work, how it saves processor time, the pitfalls of caches and ordering, and how to decide when to use one.

11 min read | Intermediate

What You'll Learn

  • Describe the parts of a DMA engine and how software programs it
  • Explain descriptor chains and scatter-gather transfers
  • Estimate how much processor time DMA saves
  • Handle cache coherence and ordering when using DMA

Why DMA Exists

Moving data with the processor is expensive. Each word needs a load and a store, plus loop instructions, and each access may wait on the bus. Worse, when data comes from a slow device, the processor spends most of its time waiting. Polling, Interrupts and DMA measured this: in one small SoC sending a 256-byte message to a UART by polling, 72 percent of the instructions executed were the polling loop itself, and the processor could do nothing else.

A DMA engine moves the data instead. It is a manager on the interconnect, like the processor, but it does only one thing: read from a source address, write to a destination address, and repeat. Because it is built for that, it can keep many transfers in flight and use long bursts, as Outstanding Transactions and Bursts explains, so it often moves data faster than the processor could, as well as freeing the processor for other work.

What It Contains

A DMA engine with SRC, DST, LEN and CTRL/STATUS registers, read and write paths to the interconnect, an interrupt output, and a chain of descriptors in memory each holding src, dst, len and next

A simple engine has:

  • Registers for the source address, the destination address, the length, and a control register to start it and select options, plus a status register.
  • A read port and a write port, or one port used for both, as a manager on the interconnect.
  • A small buffer, so data read can wait until it is written, letting reads and writes overlap.
  • An interrupt output, raised when a transfer finishes or fails.
  • Often several channels, each an independent set of registers, sharing the ports through an arbiter.

Software sets the registers and writes "start". The engine reads from the source in bursts, fills its buffer, writes to the destination, updates the addresses and the count, and repeats until the length reaches zero. Then it sets a status bit and raises its interrupt.

Addressing Modes

A copy between two memories increments both addresses. A transfer from memory to a peripheral, such as a UART's transmit register, increments the source and keeps the destination fixed, since every byte goes to the same register. Engines also support strides, to copy a rectangle out of an image, and different widths on each side, such as 32-bit reads and 8-bit writes.

Handshaking with Peripherals

A peripheral can't accept data faster than it sends it. So a DMA engine serving a peripheral waits for a request signal from it: the UART raises its request when its FIFO has room, and the engine sends one chunk each time. This handshake replaces the processor's polling loop with a wire.

Quick Check

Q: A DMA engine sends a 1 KB buffer to a UART's TXDATA register. Which address increments, and why must the engine wait for the UART's request signal?

Show Answer

The source address increments through the buffer, and the destination stays fixed at TXDATA. The engine must wait for the request because the UART's FIFO fills much faster than it empties; writing without waiting would overflow it and lose bytes.

Descriptor Chains

Setting registers for every transfer still costs processor time, and real data is often not in one piece: a network packet may be spread over several buffers, and a video frame may be stored in tiles. Scatter-gather DMA handles this with descriptors: small structures in memory, each describing one transfer, with a pointer to the next one.


struct dma_desc {
    u32 src;        // source address
    u32 dst;        // destination address
    u32 len;        // bytes to move
    u32 ctrl;       // options: interrupt when done, increment modes
    u32 next;       // address of the next descriptor, or 0 to stop
};

Software builds the chain, writes the address of the first descriptor to the engine, and starts it. The engine fetches each descriptor, does its transfer, and follows next until it reaches 0. One start can move dozens of pieces. Network and storage controllers use rings of descriptors that software keeps refilling, so the hardware never stops. This is how high-speed devices move data with very little processor involvement.

What It Saves

Take a peripheral that must send 1,000 bytes. With polling, the processor spends the whole transfer time on it. With interrupts but no DMA, the processor runs a short handler every time the FIFO needs refilling, say every 8 bytes: 125 interrupts. With DMA, the processor spends a few hundred cycles setting it up, and one interrupt at the end.

Some numbers make that concrete. Assume each interrupt costs 60 cycles of overhead plus 8 bytes x 15 cycles of work, and the DMA setup and completion together cost 400 cycles. Interrupt-driven: 125 x (60 + 120) = 22,500 cycles. DMA: about 400 cycles. These numbers are assumptions for this example. DMA uses about one fiftieth of the processor time, and the processor can sleep or do useful work meanwhile. For large or fast transfers, the difference is decisive, which is why every high-speed peripheral has DMA.

DMA isn't free. It costs area, a share of the interconnect's bandwidth, and software complexity: drivers must build descriptors, handle completion and errors, and manage buffers. For a few bytes, setting up DMA costs more than copying them, the same break-even idea as for accelerators in Accelerators and Hardware/Software Partitioning.

Where Is This Used?

A microcontroller typically has a DMA controller with 8 or 16 channels, each tied to a peripheral's request line. Larger devices, such as USB, Ethernet and storage controllers, usually have their own DMA built in, with descriptor rings, so they can move data at full speed. Large SoCs also have general-purpose copy engines, and accelerators, such as image processors, read and write memory by themselves. In a phone, most bytes moved across the chip are moved by DMA, not by the CPU.

Caches and DMA

DMA creates the most common bug in driver code. If the processor has a write-back data cache, the newest data may be in the cache, not in memory. A DMA engine reading memory then sees old data. The reverse is just as bad: after DMA writes new data into memory, the processor's cache may still hold old copies of those addresses and return them. Replacement and Write Policies explains why.

There are three standard solutions:

  • Cache maintenance: before DMA reads a buffer, the driver cleans the cache for that range, writing dirty lines to memory. After DMA writes a buffer, the driver invalidates the cached copies before reading it.
  • Uncached buffers: buffers shared with DMA are placed in memory marked as not cacheable. This is simple, but processor access to them is slow.
  • Coherent DMA: the engine's accesses snoop the caches, as Multicore Integration and Coherent Interconnects explains, so no maintenance is needed. This costs hardware but removes a class of bugs.

Ordering and Completion

When the interrupt arrives, is the data really in memory? It depends. If writes are posted, buffered on the way, the engine may report completion while the last data is still in flight. Engines that are used correctly only report completion once their writes are acknowledged by the destination. Likewise, software must make sure that its writes to a descriptor are visible in memory before it starts the engine, which may need a memory barrier. Getting this wrong gives rare, timing-dependent corruption, which is one of the hardest kinds of bug to find.

Errors need handling too. If a DMA access hits an address that belongs to nobody, the interconnect returns an error response, as Buses and Transactions describes. The engine must stop, record the failing address, and tell software, rather than carrying on and corrupting memory.

Quick Check

Q: A driver fills a buffer, starts a DMA transfer to a network controller, and the packets sometimes contain old data. The processor has a write-back cache. What is the likely cause and fix?

Show Answer

The new data is still in the cache, in dirty lines, and the DMA engine reads stale data from memory. The driver must clean the cache for the buffer before starting the transfer, or the buffer must be uncached, or the DMA must be coherent.

DMA and Virtual Memory

On a system with an operating system, programs use virtual addresses, translated by the processor's MMU, as Why Virtual Memory explains. A DMA engine without translation uses physical addresses. That creates two problems. A buffer that is contiguous in virtual memory is usually scattered over many physical pages, so the driver must build a descriptor for each piece. And a DMA engine can reach any physical address, so a buggy or malicious device could overwrite the kernel.

An IOMMU, an MMU for devices, solves both. It sits between DMA-capable devices and memory, and translates the addresses they use through page tables set up by the operating system. A device then sees a contiguous buffer, and it can reach only the pages it has been given. IOMMUs are standard in servers, laptops and phones, and they are essential when devices are given to virtual machines. RISC-V defines a standard IOMMU specification for the same purpose.

Devices that don't need the full range of memory, or that run in a secure area, can also be limited by simpler hardware: a memory protection unit at the device's port that only lets it reach listed address ranges.

Translation adds latency to each DMA access, so IOMMUs have their own TLBs, and device drivers try to reuse mapped buffers rather than mapping new ones for each transfer.

Common Mistakes

Mistake #1: Forgetting cache maintenance

Without cleaning before a DMA read and invalidating after a DMA write, data is silently corrupted, often only under load. Use the driver framework's DMA functions, which do it correctly.

Mistake #2: Reusing a buffer too early

Software that changes a buffer before the DMA has finished reading it sends corrupted data. Wait for completion before reusing it.

Mistake #3: Using DMA for tiny transfers

Setup and completion cost hundreds of cycles. For a few bytes, a direct copy by the processor is faster and simpler.

Practice Exercise

Challenge: Is DMA Worth It Here?

A sensor delivers 4,096 bytes every 10 ms through an SPI interface with an 8-byte FIFO. The processor runs at 100 MHz. These numbers are assumptions for this exercise. Option 1: interrupt every 8 bytes, with 80 cycles of overhead and 40 cycles of work per interrupt. Option 2: DMA with a 500-cycle setup and one 150-cycle completion interrupt per block. (a) How many processor cycles per 10 ms does each option use? (b) What share of the processor is that? (c) Which would you choose, and what must the driver do about caches?

Hint

4,096 bytes at 8 bytes per interrupt is 512 interrupts. 10 ms at 100 MHz is 1,000,000 cycles.

Solution

(a) Option 1: 512 x 120 = 61,440 cycles. Option 2: 650 cycles. (b) 6.1 percent and 0.07 percent. (c) DMA: it uses about one ninetieth of the processor time. If the processor's cache is write-back and not coherent with DMA, the driver must invalidate the buffer's cache lines after each transfer and before reading the data.

Summary

  • A DMA engine is a manager that copies data by itself, freeing the processor.
  • Software sets source, destination, length and control, or builds a chain of descriptors for scatter-gather transfers.
  • Peripheral handshake signals replace polling loops with hardware.
  • DMA can cut processor time by a large factor for large or frequent transfers, but costs setup time and complexity.
  • Caches must be cleaned or invalidated, or DMA made coherent, and completion must mean the data has really arrived.
Did you find what you were looking for?

Quiz

No quiz questions available for this article.