Linux DMA Channel Filter Guide- Free Linux Device Drivers Tutorial

« PREV_LECNEXT_LEC »

Linux DMA Channel Filter Guide
Free Linux Kernel Development Course — DMA Engine Chapter, Lecture 5
Kernel 6.x Ready
Beginner Friendly
Hands-On Driver Demo
free linux kernel development course
free linux device drivers course
free embedded linux course
free embedded systems course
linux dma channel filter function

If you have been following this free Linux kernel development course, you already know how to request a DMA channel, map buffers, and submit a transaction. But what happens when your system has several DMA controllers and several channels, and you need exactly one specific channel for your peripheral? That is where a Linux DMA channel filter function comes in. In this lecture of our free Linux device drivers course we break down how the DMA engine core lets a client driver pick the right channel out of many, using a small callback function called a filter.

What You Will Learn

Role of dma_cap_mask_t
Writing a dma_filter_fn callback
Legacy dma_request_channel() usage
Modern dma_request_chan() flow
Full filter-based demo driver
Reading dmesg output

Prerequisites

Lectures lddch12_1 to lddch12_4
Basic platform driver knowledge
Kernel module build environment

What Is a DMA Channel Filter Function in Linux?

A DMA channel filter function is a small callback that the DMA engine core calls once for every free channel in the system while it is looking for a channel on your behalf. Your job inside that callback is simple: look at the channel that was handed to you, decide whether it is the one your driver actually needs, and return true or false. The core keeps calling the filter on the next free channel until one returns true, and that channel becomes yours.

This mechanism exists because a capability mask alone (memcpy, slave transfer, cyclic, and so on) is often not specific enough. Two DMA controllers on the same SoC can both support DMA_SLAVE, but only one of them is physically wired to your UART or your audio codec. The filter function is how the driver tells the framework “not just any slave channel — this exact one.”

How dma_request_channel() Uses a Filter
Driver calls dma_request_channel(mask, filter_fn, param)
Core walks every channel that matches the capability mask
filter_fn(chan1) → false
filter_fn(chan2) → true
chan2 is marked exclusive and returned to the driver

Why the Kernel Needs Filter Functions for DMA Channel Selection

Before Device Tree became the standard way to describe hardware, board files carried private data structures that told a driver which DMA request line, priority, or peripheral type to use. The filter function was the bridge between that board-specific data and the generic dmaengine core, which knows nothing about individual SoCs. You would pack your private data into a small struct, pass its pointer as the filter_param argument, and let your filter callback compare it against fields stored on the channel’s private data.

Even today, this pattern is useful whenever a board is not fully described through Device Tree, or when a driver still supports legacy non-DT platforms alongside newer DT-based ones. Understanding it also makes the newer, simpler DT-based API easier to appreciate, since dma_request_chan() effectively builds a matching filter for you behind the scenes from the “dmas” and “dma-names” properties in your device node.

dma_request_channel() vs the Modern dma_request_chan() API

Lecture lddch12_4 introduced dma_request_chan() as the preferred, Device-Tree-aware way to obtain a slave channel. The table below places it side by side with the older filter-based interface so you can see exactly where a Linux DMA channel filter function is still relevant.

API Channel Selection Typical Use Today
dma_request_channel(mask, filter_fn, param) Your filter_fn inspects every candidate channel Legacy / non-DT platform code, generic memcpy engines
dma_request_chan(dev, name) Core builds the match from Device Tree “dmas” property Recommended for slave devices described in DT
dma_request_chan_by_mask(mask) First channel satisfying the mask, no filter needed Generic memcpy/xor engines with no specific peripheral wiring

Writing a Custom DMA Filter Callback

A filter function has a fixed signature defined by the dmaengine core: it takes the candidate channel and an opaque parameter, and returns a boolean. Inside the function you typically stash your matching criteria in the channel’s private field, or compare against something the platform layer already attached to the channel structure. Here is a minimal, original filter callback used purely for teaching:

#include <linux/dmaengine.h>

/* Private data we use to identify "our" channel */
struct ep_dma_filter_param {
	int req_line;   /* logical DMA request line for our peripheral */
};

static bool ep_dma_filter(struct dma_chan *chan, void *param)
{
	struct ep_dma_filter_param *p = param;

	/* chan->private is set by the controller driver to something
	 * it understands (often a request-line or peripheral id).
	 * We just compare the pointer value we were handed. */
	if (chan->private && (long)chan->private == p->req_line)
		return true;

	return false;
}

Notice the function does nothing more than answer a yes/no question. All the heavy lifting — walking the channel list, honoring the capability mask, marking the channel exclusive — is handled by the dmaengine core inside dma_request_channel().

Building a Complete DMA Filter Demo Driver

Let us put the filter function to work inside a small, original platform driver. This example requests a memcpy-capable channel, copies a known pattern from a source buffer to a destination buffer, and confirms completion with a struct completion, exactly the pattern covered in lddch12_3, now combined with filter-based channel selection.

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

#define EP_BUF_SIZE 256

struct ep_dma_filter_param ep_param = { .req_line = 7 };

static struct dma_chan *ep_chan;
static struct completion ep_done;
static void *ep_src, *ep_dst;
static dma_addr_t ep_src_dma, ep_dst_dma;

static void ep_dma_callback(void *arg)
{
	complete(&ep_done);
}

static int ep_dma_run_transfer(struct device *dev)
{
	struct dma_async_tx_descriptor *tx;
	dma_cookie_t cookie;

	tx = dmaengine_prep_dma_memcpy(ep_chan, ep_dst_dma, ep_src_dma,
					EP_BUF_SIZE, DMA_PREP_INTERRUPT);
	if (!tx) {
		dev_err(dev, "ep_dma: failed to get descriptor\n");
		return -EIO;
	}

	tx->callback = ep_dma_callback;
	tx->callback_param = NULL;

	cookie = dmaengine_submit(tx);
	if (dma_submit_error(cookie))
		return -EIO;

	reinit_completion(&ep_done);
	dma_async_issue_pending(ep_chan);

	if (!wait_for_completion_timeout(&ep_done, msecs_to_jiffies(2000))) {
		dev_err(dev, "ep_dma: transfer timed out\n");
		return -ETIMEDOUT;
	}

	dev_info(dev, "ep_dma: transfer completed, cookie=%d\n", cookie);
	return 0;
}

static int ep_dma_probe(struct platform_device *pdev)
{
	dma_cap_mask_t mask;
	int ret;

	dma_cap_zero(mask);
	dma_cap_set(DMA_MEMCPY, mask);

	ep_chan = dma_request_channel(mask, ep_dma_filter, &ep_param);
	if (!ep_chan) {
		dev_err(&pdev->dev, "ep_dma: no channel matched the filter\n");
		return -ENODEV;
	}

	dev_info(&pdev->dev, "ep_dma: got channel %s\n",
		 dma_chan_name(ep_chan));

	ep_src = kzalloc(EP_BUF_SIZE, GFP_KERNEL);
	ep_dst = kzalloc(EP_BUF_SIZE, GFP_KERNEL);
	if (!ep_src || !ep_dst) {
		ret = -ENOMEM;
		goto err_release;
	}

	memset(ep_src, 0xAB, EP_BUF_SIZE);

	ep_src_dma = dma_map_single(ep_chan->device->dev, ep_src,
				     EP_BUF_SIZE, DMA_TO_DEVICE);
	ep_dst_dma = dma_map_single(ep_chan->device->dev, ep_dst,
				     EP_BUF_SIZE, DMA_FROM_DEVICE);

	init_completion(&ep_done);

	ret = ep_dma_run_transfer(&pdev->dev);

	dma_unmap_single(ep_chan->device->dev, ep_src_dma,
			  EP_BUF_SIZE, DMA_TO_DEVICE);
	dma_unmap_single(ep_chan->device->dev, ep_dst_dma,
			  EP_BUF_SIZE, DMA_FROM_DEVICE);

	if (!ret && !memcmp(ep_src, ep_dst, EP_BUF_SIZE))
		dev_info(&pdev->dev, "ep_dma: data verified OK\n");
	else
		dev_warn(&pdev->dev, "ep_dma: data mismatch or error\n");

	return 0;

err_release:
	dma_release_channel(ep_chan);
	return ret;
}

static int ep_dma_remove(struct platform_device *pdev)
{
	kfree(ep_src);
	kfree(ep_dst);
	dma_release_channel(ep_chan);
	dev_info(&pdev->dev, "ep_dma: channel released\n");
	return 0;
}

static struct platform_driver ep_dma_driver = {
	.probe = ep_dma_probe,
	.remove = ep_dma_remove,
	.driver = {
		.name = "ep_dma_filter_demo",
	},
};
module_platform_driver(ep_dma_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala DMA filter function demo");

In a real board this driver would be bound through a matching platform device (either registered in board code for legacy setups or matched via Device Tree compatible string). The req_line value inside ep_param is intentionally simple here so you can adapt the comparison to whatever your own DMA controller driver stores in chan->private.

Compiling and Running the Filter Demo on Your Board

Build the module the same way we have throughout this free embedded Linux course, against your target kernel headers:

make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

Load it and register a matching platform device (or bind it through your board’s DT overlay), then watch the kernel log:

sudo insmod ep_dma_filter_demo.ko
dmesg | tail -n 6

On a board that actually has a DMA_MEMCPY-capable channel available, you should see output similar to this:

[  102.114213] ep_dma: got channel dma0chan0
[  102.116502] ep_dma: transfer completed, cookie=1
[  102.116540] ep_dma: data verified OK

If your board has no free memcpy channel, or the filter never returns true, dma_request_channel() returns NULL and you will see the “no channel matched the filter” message instead — that is expected behavior, not a bug, and it is exactly the safety net the filter mechanism is meant to provide.

Common Mistakes When Using DMA Filter Functions

Three mistakes come up again and again when engineers new to the Linux DMA channel filter function mechanism write their first driver:

  • Forgetting to zero and set the capability mask with dma_cap_zero()/dma_cap_set() before calling dma_request_channel(), which leaves the mask holding garbage from the stack.
  • Comparing against chan->private without first confirming the underlying DMA controller driver actually populates that field — not every driver does.
  • Never calling dma_release_channel() on the error and remove paths, which leaves the channel marked exclusive and unusable by any other driver until reboot.

Frequently Asked Questions

What is a DMA channel filter function used for in Linux?

It is a callback passed to dma_request_channel() that lets a client driver examine each candidate DMA channel and accept or reject it, so the driver ends up with the exact channel its hardware needs instead of any channel that merely matches a capability mask.

Is dma_request_channel() deprecated in modern kernels?

It is still present and used by drivers that support legacy, non-Device-Tree platforms or generic memcpy-style engines, but for slave devices described in Device Tree, dma_request_chan() is the recommended API since it builds the matching logic for you.

What does chan->private normally contain?

It is a driver-defined pointer that the underlying DMA controller driver may set to identify a channel, such as a request line number or peripheral type. Its meaning is entirely up to the controller driver, so your filter function must know the convention used by the hardware you are targeting.

What happens if no channel satisfies my filter function?

dma_request_channel() returns NULL. Your probe function should treat this the same as any other missing-resource error, log it clearly, and fail probing (or defer probing with -EPROBE_DEFER if the channel may appear later).

Do I need a filter function when using dma_request_chan_by_mask()?

No. That variant simply returns the first channel satisfying the capability mask, with no per-channel inspection, so it is only suitable when any channel with the right capability will do, such as a generic memcpy offload.

 

Continue the Free Linux Kernel Development Course

Next we complete the DMA engine chapter by combining everything into a single production-style slave driver.

« PREV_LECNEXT_LEC »

Leave a Reply

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