Keywords Covered In This Lecture
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
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.
dma_request_chan()
dmaengine_slave_config()
dmaengine_prep_slave_sg()
dmaengine_submit()
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.
dmas = <&dma0 4>; dma-names = "tx";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.
