Linux DMA Slave Channel Setup- Free Linux Device Drivers Tutorial

Linux DMA Slave Channel Setup
Free Linux Kernel Development Course — Chapter 12, Lecture 4
Kernel 6.x Verified
Original Code Examples
Free Embedded Linux Course

Keywords Covered In This Lecture

linux dma slave channel setup
free linux kernel development course
free linux device drivers course
free embedded linux course
dma_request_chan
dma_slave_config
dmaengine_prep_slave_sg

In the previous lecture of this free Linux kernel development course, you learned how a DMA transaction is submitted and how a driver waits for its completion using dmaengine_submit() and struct completion. This lecture steps back to the beginning of that flow: how a peripheral driver actually gets hold of a DMA channel, tells the DMA controller how the peripheral’s FIFO behaves, and asks for a transfer descriptor before it can ever be submitted.

This is the Slave-DMA API of the kernel’s DMA Engine framework — the API used by UART, SPI, I2S, SDIO and similar peripheral drivers whenever they hand off bulk data transfer to a dedicated DMA controller instead of using the CPU to copy bytes.

What You Will Learn

Why peripheral drivers use DMA channels
Requesting a channel with dma_request_chan()
Configuring struct dma_slave_config
Getting a descriptor with dmaengine_prep_slave_sg()
Memcpy descriptors with dmaengine_prep_dma_memcpy()
Submitting the transaction
Old vs modern channel-request API
A working platform driver example

Prerequisites

Before this lecture, you should be comfortable with:

Platform device drivers
Device Tree basics
Lecture 12.1 — DMA coherent mapping
Lecture 12.2 — DMA streaming mapping
Lecture 12.3 — DMA submission and completion

The Slave-DMA Transfer Pipeline

Every peripheral driver that offloads a transfer to a DMA controller follows the same five-step pipeline. This lecture covers the first three steps; step 4 (submit) and step 5 (start + wait) were covered in Lecture 12.3.

Slave DMA Setup Pipeline
1. Request Channel
dma_request_chan()
2. Configure
dmaengine_slave_config()
3. Get Descriptor
dmaengine_prep_slave_sg()
4. Submit
dmaengine_submit()
5. Start
dma_async_issue_pending()

Step 1: Requesting A DMA Channel

Older drivers used dma_request_channel() with a filter function and a hardware-specific filter parameter. In modern kernel 6.x drivers this pattern is discouraged. The recommended way is dma_request_chan(), which looks up the channel purely from the dmas / dma-names properties in the Device Tree node of your peripheral — no filter callback needed.

struct dma_chan *chan;

chan = dma_request_chan(dev, "tx");
if (IS_ERR(chan)) {
    dev_err(dev, "Failed to request DMA channel: %ld\n", PTR_ERR(chan));
    return PTR_ERR(chan);
}

The string "tx" here must match a name listed under the dma-names property of the corresponding node in your Device Tree — this ties the request back to the DMA controller and channel wired up in hardware.

Where dma_request_chan() Looks
Device Tree node: dmas = <&dma0 4>; dma-names = "tx";
dma_request_chan(dev, “tx”) resolves the phandle and returns a struct dma_chan *

Step 2: Configuring struct dma_slave_config

Once you hold a channel, you must tell the controller how your peripheral’s data register behaves — its bus address, its register width, and how many words to move in one burst. This information lives in struct dma_slave_config.

struct dma_slave_config cfg = {0};

cfg.direction      = DMA_MEM_TO_DEV;
cfg.dst_addr       = peripheral_fifo_phys_addr;
cfg.dst_addr_width  = DMA_SLAVE_BUSWIDTH_4_BYTES;
cfg.dst_maxburst   = 4;

ret = dmaengine_slave_config(chan, &cfg);
if (ret) {
    dev_err(dev, "DMA slave config failed: %d\n", ret);
    dma_release_channel(chan);
    return ret;
}

Key members you will use most often:

Field Meaning
direction Data flow direction for this configuration (memory→device, device→memory, etc.)
src_addr / dst_addr Bus address of the peripheral FIFO register, ignored on the memory side
src_addr_width / dst_addr_width Register width in bytes — 1, 2, 4 or 8
src_maxburst / dst_maxburst Words per burst — typically half the FIFO depth to avoid overflow

Step 3: Getting A Transfer Descriptor

With the channel configured, you ask the DMA driver to prepare a descriptor for the specific transfer you want. The kernel exposes one client-facing helper per transaction type; you never call the controller’s internal hooks directly.

/* For a memory-to-memory copy */
struct dma_async_tx_descriptor *tx;

tx = dmaengine_prep_dma_memcpy(chan, dst_dma_addr, src_dma_addr,
                                len, DMA_CTRL_ACK);
if (!tx) {
    dev_err(dev, "Failed to prepare DMA descriptor\n");
    dma_release_channel(chan);
    return -EIO;
}

/* For a scatter-gather transfer to/from a peripheral */
struct dma_async_tx_descriptor *sg_tx;

sg_tx = dmaengine_prep_slave_sg(chan, sgl, nents,
                                 DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
if (!sg_tx) {
    dev_err(dev, "Failed to prepare slave SG descriptor\n");
    return -EIO;
}

Choose the helper that matches your transaction:

Helper Use Case
dmaengine_prep_dma_memcpy() Simple memory-to-memory copy
dmaengine_prep_slave_sg() Scatter-gather transfer to/from a peripheral
dmaengine_prep_dma_cyclic() Repeating buffer, e.g. audio ring buffers
dmaengine_prep_interleaved_dma() Complex strided/2D transfer patterns

Old API vs Modern Kernel 6.x API

Aspect Older Kernels Kernel 6.x (Current)
Channel request dma_request_channel() + filter function dma_request_chan() by DT name
Descriptor prep Calling dma_dev->device_prep_dma_memcpy() directly dmaengine_prep_dma_memcpy() client wrapper
Config struct direction field Required Being phased out — direction now comes from the prepare call

Example Driver: ep_dma_slave_setup_demo

This is an original platform driver written for this course. It requests a memory-to-memory DMA channel, configures it, prepares a memcpy descriptor, and logs each step to the kernel log. Build and run it on any Linux system with a DMA-capable platform (e.g. Raspberry Pi, BeagleBone, or QEMU virt board with a DMA controller).

// ep_dma_slave_setup_demo.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>

#define EP_BUF_SIZE 4096

struct ep_dma_slave_dev {
    struct dma_chan *chan;
    char *src_buf;
    char *dst_buf;
    dma_addr_t src_dma, dst_dma;
};

static void ep_dma_callback(void *param)
{
    pr_info("ep_dma_slave_setup: transfer completed\n");
}

static int ep_dma_slave_probe(struct platform_device *pdev)
{
    struct ep_dma_slave_dev *edev;
    struct dma_async_tx_descriptor *tx;
    dma_cookie_t cookie;

    edev = devm_kzalloc(&pdev->dev, sizeof(*edev), GFP_KERNEL);
    if (!edev)
        return -ENOMEM;

    /* Step 1: request a memcpy-capable channel */
    dma_cap_mask_t mask;
    dma_cap_zero(mask);
    dma_cap_set(DMA_MEMCPY, mask);
    edev->chan = dma_request_chan_by_mask(&mask);
    if (IS_ERR(edev->chan)) {
        dev_err(&pdev->dev, "No DMA memcpy channel available\n");
        return PTR_ERR(edev->chan);
    }

    edev->src_buf = kzalloc(EP_BUF_SIZE, GFP_KERNEL);
    edev->dst_buf = kzalloc(EP_BUF_SIZE, GFP_KERNEL);
    if (!edev->src_buf || !edev->dst_buf) {
        dma_release_channel(edev->chan);
        return -ENOMEM;
    }
    memset(edev->src_buf, 0xAB, EP_BUF_SIZE);

    edev->src_dma = dma_map_single(edev->chan->device->dev,
                                    edev->src_buf, EP_BUF_SIZE, DMA_TO_DEVICE);
    edev->dst_dma = dma_map_single(edev->chan->device->dev,
                                    edev->dst_buf, EP_BUF_SIZE, DMA_FROM_DEVICE);

    /* Step 3: get a memcpy descriptor */
    tx = dmaengine_prep_dma_memcpy(edev->chan, edev->dst_dma,
                                    edev->src_dma, EP_BUF_SIZE, DMA_CTRL_ACK);
    if (!tx) {
        dev_err(&pdev->dev, "Failed to prepare descriptor\n");
        goto err_unmap;
    }
    tx->callback = ep_dma_callback;

    /* Step 4 & 5: submit and start (covered in Lecture 12.3) */
    cookie = dmaengine_submit(tx);
    if (dma_submit_error(cookie)) {
        dev_err(&pdev->dev, "Submit failed\n");
        goto err_unmap;
    }
    dma_async_issue_pending(edev->chan);

    platform_set_drvdata(pdev, edev);
    pr_info("ep_dma_slave_setup: channel requested and transfer queued\n");
    return 0;

err_unmap:
    dma_release_channel(edev->chan);
    return -EIO;
}

static void ep_dma_slave_remove(struct platform_device *pdev)
{
    struct ep_dma_slave_dev *edev = platform_get_drvdata(pdev);

    dma_release_channel(edev->chan);
    kfree(edev->src_buf);
    kfree(edev->dst_buf);
    pr_info("ep_dma_slave_setup: channel released\n");
}

static struct platform_driver ep_dma_slave_driver = {
    .probe  = ep_dma_slave_probe,
    .remove = ep_dma_slave_remove,
    .driver = {
        .name = "ep_dma_slave_setup_demo",
    },
};
module_platform_driver(ep_dma_slave_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Demo: DMA slave channel request and memcpy descriptor setup");

Build And Run

Use a simple out-of-tree Makefile against your configured kernel source tree:

obj-m += ep_dma_slave_setup_demo.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
$ make
$ sudo insmod ep_dma_slave_setup_demo.ko
$ dmesg | tail

Expected dmesg output (on a board with a memcpy-capable DMA engine registered, e.g. a system using dmatest-compatible controllers):

[  102.441823] ep_dma_slave_setup: channel requested and transfer queued
[  102.442901] ep_dma_slave_setup: transfer completed
$ sudo rmmod ep_dma_slave_setup_demo
$ dmesg | tail -2
[  118.220015] ep_dma_slave_setup: channel released

If no DMA_MEMCPY-capable channel is registered on your board, dma_request_chan_by_mask() returns -ENODEV — this is expected on boards without a general-purpose DMA controller exposed for memcpy operations; try this on a Raspberry Pi, BeagleBone Black, or a QEMU virt machine with CONFIG_DMATEST enabled.

Frequently Asked Questions

What is the difference between dma_request_chan() and dma_request_channel()?

dma_request_chan() looks up the channel by name directly from the Device Tree and is the recommended approach in kernel 6.x. dma_request_channel() requires a filter function and hardware-specific parameter, and is considered legacy for new drivers.

Do I always need struct dma_slave_config?

Only for slave transfers to or from a peripheral FIFO. Pure memory-to-memory copies via dmaengine_prep_dma_memcpy() generally do not need it, since there is no peripheral register width or burst size to describe.

Can I call a controller’s device_prep_dma_memcpy hook directly?

You can, but it is not recommended. Always go through the client-facing dmaengine_prep_* wrapper functions — they exist precisely so driver code stays portable across different DMA controller backends.

What happens if dmaengine_prep_slave_sg() returns NULL?

It means the DMA controller could not prepare a descriptor for the given scatterlist — commonly due to an unmapped scatterlist, unsupported burst configuration, or no free descriptors. Always check for NULL before using the returned pointer.

Is dma_slave_config’s direction field still required?

The kernel documentation notes this field is being phased out because direction is now specified again in the prepare call (e.g. dmaengine_prep_slave_sg()). Most current drivers still set it for compatibility with older controller backends.

What is the next lecture in this DMA chapter?

Use the NEXT_LEC link on this page — it will be filled in once the next lecture in this free Linux kernel development course is published.

Continue The Free Linux Kernel Development Course

More lectures on device drivers, memory management, and DMA — completely free.

Leave a Reply

Your email address will not be published. Required fields are marked *