Zynq Linux PL330 DMA

Linux Driver Example for the PL330 DMA Controller

The Zynq-7000 family processor block includes an eight-channel PL330 DMA controller that you can use to significantly improve throughput between your custom hardware peripherals and external memory. Xilinx provides a Linux driver for the PL330 DMA controller itself, but in order to use it in your applications you will need to write custom software drivers to configure it for your application. This page hosts a simple example driver that illustrates DMA-based transfers between the Linux user space and a FIFO-based AXI interface similar to the Xilinx AXI Streaming FIFO (axi_mm2s_fifo).

Using the PL330 DMA Driver

The Linux PL330 DMA API is modeled on the ISA DMA API and performs DMA transfers betwen a device and memory, i.e. a fixed address an a memory region. Configuration for the various parameters of the DMA transaction, such as source and destination burst size, burst length, protection control, etc. are passed through exported functions provided by the driver. The driver will construct PL330 DMA programs and pass control to the PL330 itself to execute the programs.

You need to set up the AXI bus transaction configurations for both the target and destination sides of the DMA transfer. You pass these settings via the structs pl330_client_data and the function set_pl330_client_data, both of which are defined in arch/arm/mach-zynq/include/mach/pl330.h.

The driver has interrupt service routines for both the DMA done interrupt and DMA fault interrupt. You can pass your own callbacks for these interrupts to the driver using the set_pl330_done_callback and set_pl330_fault_callback functions.

Here is a simple example of how to start a DMA transaction:

struct pl330_client_data client_data = {
     .dev_addr = my_device_addr,
     .dev_bus_des = {
         .burst_size = 4,
         .burst_len = 4,
     .mem_bus_des = {
         .burst_size = 4,
         .burst_len = 4,

status = request_dma(channel, DRIVER_NAME);

if (status != 0)
    goto failed;

set_dma_mode(channel, DMA_MODE_READ);

set_dma_addr(channel, buf_bus_addr);

set_dma_count(channel, num_of_bytes);

set_pl330_client_data(channel, &client_data);

set_pl330_done_callback(channel, my_done_callback, my_dev);

set_pl330_fault_callback(channel, my_fault_callback2, my_dev);


Creating Custom Drivers Using PL330 DMA Functions

Of course, the above function calls must be made in a kernel context with allocated DMA buffers, etc. which requires that you write a custom driver for your hardware. In the example below, we've put together a driver for a generic FIFO-based system. This is a very simple example, performing only blocking writes to a FIFO interface modeled on the AXI MM2S FIFO core (or other similar generic FIFO).

Setting up the Build Environment

This step requires the ARM GNU tools, which are part of Xilinx SDK, to be installed on your host system. Specify the ARM cross-compiler by setting the CROSS_COMPILE environment variable and adding the cross-compiler to your PATH.

bash> export CROSS_COMPILE=arm-xilinx-linux-gnueabi-
bash> export PATH=/path/to/cross/compiler/bin:$PATH

Creating a Makefile

Linux drivers can either be compiled into the kernel at build time or compiled separately as loadable kernel modules. When developing a device driver, it's often advantageous to compile it separately to shorten the build process and allow you to dynamically load and unload the module.

If you want to build the kernel module outside of the Linux source tree, you'll need to create a makefile that links into the kernel build mechanism.

# Cross compiler makefile for FIFO DMA example
obj-m := xfifo_dma.o

    make -C $(KERN_SRC) ARCH=arm M=`pwd` modules
    make -C $(KERN_SRC) ARCH=arm M=`pwd=` clean

Updating the DTS File

After building a Linux kernel module, the kernel needs to have a way to associate it with a particular hardware device in your system. If you're doing development that you know is only going to target one particular hardware platform you could, of course, hard-code things like device addresses into the driver itself. However, it's generally considered bad practice and it's preferable to register your module as a platform device driver. On Linux for Xilinx devices, most of this is accomplished via Open Firmware using a device tree (DTS) file.

In order for your driver to read information from this file you'll need to register your device as a platform device with a corresponding probe function (explained in more detail later) and also add a hardware instance to your DTS file.

    fifo_dma0: fifo_dma@78000000 {
        compatible = "xlnx,fifo-dma";
        reg = <0x78000000 0x2000>;
        fifo-depth = <2048>;
        dma-channel = <1>;
        burst-length = <4>;

Building the Driver

Place the driver source file into the same directory as your makefile, and run make to compile the driver. Assuming there are no errors in the build process, you'll wind up with a file called fifo_dma.ko which is a loadable kernel object.

bash> make

Transfer your Kernel Module to the Target Platform

The Linux kernel module tools insmod and rmmod expect the source modules to be placed into a specific location that doesn't exist by default in the Zynq ramdisk8M.image.gz root file system. Once your system is booted, you'll need to create a modules directory to hold your kernel object.

zynq> mkdir -p /lib/modules/`uname -r`
zynq> ln -s /lib/modules/`uname -r` /lib/modules/3.3

After creating the required directory structure and the symbolic link for ease of use, upload the xfifo_dma.ko kernel module to /lib/modules/3.3 (if using FTP to transfer the file, be sure that your FTP client is in binary mode).

Load the Kernel Module

After transferring the kernel module to the board, you'll need to load it into memory.

zynq> cd /lib/modules/3.3
zynq> insmod xfifo_dma.ko

We have 1 resources
xfifo_dma 78000000.fifo_dma: read DMA channel is 1
xfifo_dma 78000000.fifo_dma: DMA fifo depth is 2048
xfifo_dma 78000000.fifo_dma: DMA burst length is 4
devno is 0x3c00000, pdev id is 0
xfifo_dma: mapped 0x78000000 to 0xf0074000
xfifo_dma 78000000.fifo_dma: added Xilinx FIFO DMA successfully

Create a Device Node

Finally, before you can access the driver from userspace you'll need to create a device node under /dev to use for file operations.

zynq> mknod /dev/fifo-dma0 c 60 0

The driver is coded to request a major number of 60.

Using the Driver

Now that the kernel object has been loaded, you can access it as normal using file operations. Note that as with any DMA transaction, there is an additional period required to set up the DMA making it less efficient than processor-driven transfers for small blocks of data. For larger blocks, other system considerations such as memory bandwidth utilization, AXI bandwidth, or the bandwidth of your hardware peripherals will contribute much more heavily.

zynq> dd if=/dev/urandom bs=1024 count=1 of=/dev/fifo-dma0
dma buffer alloc - d @0x2e100000 v @0xffdf9000
dma write 1024 bytes
1+0 records in
1+0 records out
1024 bytes (1.0KB) copied, 0.006820 seconds, 146.6KB/s

Driver Statistics

As a final note, this driver keeps statistics that are available under /proc/driver/xfifo_dma.

zynq> cat /proc/driver/xfifo_dma


Device Physical Address: 0x78000000
Device Virtual Address:  0xf0074000
Device Address Space:    8192 bytes
DMA Channel:             1
FIFO Depth:              2048 bytes
Burst Length:            4 words

Opens:                   1
Writes:                  1
Bytes Written:           1024
Closes:                  1
Errors:                  0
Busy:                    0

FIFO DMA Test Driver Source

 * Driver for Linux DMA test application (FIFO)
 * Copyright (C) 2012 Xilinx, Inc.
 * Copyright (C) 2012 Robert Armstrong
 * Author: Robert Armstrong <robert.armstrong-jr@xilinx.com>
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * GNU General Public License for more details.
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/dma-mapping.h>
#include <linux/dmapool.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <asm/uaccess.h>
#include <asm/sizes.h>
#include <asm/dma.h>
#include <asm/io.h>
#include <mach/pl330.h>
#include <linux/of.h>
/* Define debugging for use during our driver bringup */
#undef PDEBUG
#define PDEBUG(fmt, args...) printk(KERN_INFO fmt, ## args)
/* Offsets for control registers in the AXI MM2S FIFO */
#define AXI_TXFIFO_STS          0x0
#define AXI_TXFIFO_RST          0x08
#define AXI_TXFIFO_VAC          0x0c
#define AXI_TXFIFO              0x10
#define AXI_TXFIFO_LEN          0x14
#define TXFIFO_STS_CLR          0xffffffff
#define TXFIFO_RST              0x000000a5
#define MODULE_NAME             "xfifo_dma"
#define XFIFO_DMA_MINOR         0
int xfifo_dma_major = 60;
module_param(xfifo_dma_major, int, 0);
dma_addr_t write_buffer;
struct xfifo_dma_dev {
        dev_t devno;
        struct mutex mutex;
        struct cdev cdev;
        struct platform_device *pdev;
        struct pl330_client_data *client_data;
        u32 dma_channel;
        u32 fifo_depth;
        u32 burst_length;
        /* Current DMA buffer information */
        dma_addr_t buffer_d_addr;
        void *buffer_v_addr;
        size_t count;
        int busy;
        /* Hardware device constants */
        u32 dev_physaddr;
        void *dev_virtaddr;
        u32 dev_addrsize;
        /* Driver reference counts */
        u32 writers;
        /* Driver statistics */
        u32 bytes_written;
        u32 writes;
        u32 opens;
        u32 closes;
        u32 errors;
struct xfifo_dma_dev *xfifo_dma_dev;
static void xfifo_dma_reset_fifo(void)
        iowrite32(TXFIFO_STS_CLR, xfifo_dma_dev->dev_virtaddr + AXI_TXFIFO_STS);
        iowrite32(TXFIFO_RST, xfifo_dma_dev->dev_virtaddr + AXI_TXFIFO_RST);
/* File operations */
int xfifo_dma_open(struct inode *inode, struct file *filp)
        struct xfifo_dma_dev *dev;
        int retval;
        retval = 0;
        dev = container_of(inode->i_cdev, struct xfifo_dma_dev, cdev);
        filp->private_data = dev;       /* For use elsewhere */
        if (mutex_lock_interruptible(&dev->mutex)) {
                return -ERESTARTSYS;
        /* We're only going to allow one write at a time, so manage that via
         * reference counts
        switch (filp->f_flags & O_ACCMODE) {
        case O_RDONLY:
        case O_WRONLY:
                if (dev->writers || dev->busy) {
                        retval = -EBUSY;
                        goto out;
                else {
        case O_RDWR:
                if (dev->writers || dev->busy) {
                        retval = -EBUSY;
                        goto out;
                else {
        return retval;
int xfifo_dma_release(struct inode *inode, struct file *filp)
        struct xfifo_dma_dev *dev = filp->private_data;
        if (mutex_lock_interruptible(&dev->mutex)) {
                return -EINTR;
        /* Manage writes via reference counts */
        switch (filp->f_flags & O_ACCMODE) {
        case O_RDONLY:
        case O_WRONLY:
        case O_RDWR:
        return 0;
ssize_t xfifo_dma_read(struct file *filp, char __user *buf, size_t count, 
        loff_t *f_pos)
        return 0;
static void xfifo_dma_fault_callback(unsigned int channel,
        unsigned int fault_type,
        unsigned int fault_address,
        void *data)
        struct xfifo_dma_dev *dev = data;
                "DMA fault type %d at address 0x%0x on channel %d\n",
                fault_type, fault_address, channel);
        dev->busy = 0;
static void xfifo_dma_done_callback(unsigned int channel, void *data)
        struct xfifo_dma_dev *dev = data;
        dev->bytes_written += dev->count;
        dev->busy = 0;
        /* Write the count to the FIFO control register */
        iowrite32(dev->count, xfifo_dma_dev->dev_virtaddr + AXI_TXFIFO_LEN);
ssize_t xfifo_dma_write(struct file *filp, const char __user *buf, size_t count,
        loff_t *f_pos)
        struct xfifo_dma_dev *dev = filp->private_data;
        size_t transfer_size;
        int retval = 0;
        if (mutex_lock_interruptible(&dev->mutex)) {
                return -EINTR;
        transfer_size = count;
        if (count > dev->fifo_depth) {
                transfer_size = dev->fifo_depth;
        /* Allocate a DMA buffer for the transfer */
        dev->buffer_v_addr = dma_alloc_coherent(&dev->pdev->dev, transfer_size,
                &dev->buffer_d_addr, GFP_KERNEL);
        if (!dev->buffer_v_addr) {
                        "coherent DMA buffer allocation failed\n");
                retval = -ENOMEM;
                goto fail_buffer;
        PDEBUG("dma buffer alloc - d @0x%0x v @0x%0x\n", 
                (u32)dev->buffer_d_addr, (u32)dev->buffer_v_addr);
        if (request_dma(dev->dma_channel, MODULE_NAME)) {
                        "unable to alloc DMA channel %d\n",
                retval = -EBUSY;
                goto fail_client_data;
        dev->busy = 1;
        dev->count = transfer_size;
        set_dma_mode(dev->dma_channel, DMA_MODE_WRITE);
        set_dma_addr(dev->dma_channel, dev->buffer_d_addr);
        set_dma_count(dev->dma_channel, transfer_size);
        set_pl330_client_data(dev->dma_channel, dev->client_data);
                xfifo_dma_done_callback, dev);
                xfifo_dma_fault_callback, dev);
        set_pl330_incr_dev_addr(dev->dma_channel, 0);
        /* Load our DMA buffer with the user data */
        copy_from_user(dev->buffer_v_addr, buf, transfer_size);
        /* Kick off the DMA */
        wait_event_interruptible(xfifo_dma_wait, dev->busy == 0);
        /* Deallocate the DMA buffer and free the channel */
        dma_free_coherent(&dev->pdev->dev, dev->count, dev->buffer_v_addr,
        PDEBUG("dma write %d bytes\n", transfer_size);
        return transfer_size;
        dma_free_coherent(&dev->pdev->dev, transfer_size, dev->buffer_v_addr, 
        return retval;
struct file_operations xfifo_dma_fops = {
        .owner = THIS_MODULE,
        .read = xfifo_dma_read,
        .write = xfifo_dma_write,
        .open = xfifo_dma_open,
        .release = xfifo_dma_release
/* Driver /proc filesystem operations so that we can show some statistics */
static void *xfifo_dma_proc_seq_start(struct seq_file *s, loff_t *pos)
        if (*pos == 0) {
                return xfifo_dma_dev;
        return NULL;
static void *xfifo_dma_proc_seq_next(struct seq_file *s, void *v, loff_t *pos)
        return NULL;
static void xfifo_dma_proc_seq_stop(struct seq_file *s, void *v)
static int xfifo_dma_proc_seq_show(struct seq_file *s, void *v)
        struct xfifo_dma_dev *dev;
        dev = v;
        if (mutex_lock_interruptible(&dev->mutex)) {
                return -EINTR;
        seq_printf(s, "\nFIFO DMA Test:\n\n");
        seq_printf(s, "Device Physical Address: 0x%0x\n", dev->dev_physaddr);
        seq_printf(s, "Device Virtual Address:  0x%0x\n", 
        seq_printf(s, "Device Address Space:    %d bytes\n", dev->dev_addrsize);
        seq_printf(s, "DMA Channel:             %d\n", dev->dma_channel);
        seq_printf(s, "FIFO Depth:              %d bytes\n", dev->fifo_depth);
        seq_printf(s, "Burst Length:            %d words\n", dev->burst_length);
        seq_printf(s, "\n");
        seq_printf(s, "Opens:                   %d\n", dev->opens);
        seq_printf(s, "Writes:                  %d\n", dev->writes);
        seq_printf(s, "Bytes Written:           %d\n", dev->bytes_written);
        seq_printf(s, "Closes:                  %d\n", dev->closes);
        seq_printf(s, "Errors:                  %d\n", dev->errors);
        seq_printf(s, "Busy:                    %d\n", dev->busy);
        seq_printf(s, "\n");
        return 0;
/* SEQ operations for /proc */
static struct seq_operations xfifo_dma_proc_seq_ops = {
        .start = xfifo_dma_proc_seq_start,
        .next = xfifo_dma_proc_seq_next,
        .stop = xfifo_dma_proc_seq_stop,
        .show = xfifo_dma_proc_seq_show
static int xfifo_dma_proc_open(struct inode *inode, struct file *file)
        return seq_open(file, &xfifo_dma_proc_seq_ops);
static struct file_operations xfifo_dma_proc_ops = {
        .owner = THIS_MODULE,
        .open = xfifo_dma_proc_open,
        .read = seq_read,
        .llseek = seq_lseek,
        .release = seq_release
static int xfifo_dma_remove(struct platform_device *pdev)
        remove_proc_entry("driver/xfifo_dma", NULL);
        unregister_chrdev_region(xfifo_dma_dev->devno, 1);
        /* Unmap the I/O memory */
        if (xfifo_dma_dev->dev_virtaddr) {
        /* Free the PL330 buffer client data descriptors */
        if (xfifo_dma_dev->client_data) {
        if (xfifo_dma_dev) {
        return 0;
#ifdef CONFIG_OF
static struct of_device_id xfifodma_of_match[] __devinitdata = {
        { .compatible = "xlnx,fifo-dma", },
        { /* end of table */}
MODULE_DEVICE_TABLE(of, xfifodma_of_match);
#define xfifodma_of_match NULL
#endif /* CONFIG_OF */
static int xfifo_dma_probe(struct platform_device *pdev)
        int status;
        struct proc_dir_entry *proc_entry;
        struct resource *xfifo_dma_resource;
        /* Get our platform device resources */
        PDEBUG("We have %d resources\n", pdev->num_resources);
        xfifo_dma_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (xfifo_dma_resource == NULL) {
                dev_err(&pdev->dev, "No resources found\n");
                return -ENODEV;
        /* Allocate a private structure to manage this device */
        xfifo_dma_dev = kmalloc(sizeof(struct xfifo_dma_dev), GFP_KERNEL);
        if (xfifo_dma_dev == NULL) {
                        "unable to allocate device structure\n");
                return -ENOMEM;
        memset(xfifo_dma_dev, 0, sizeof(struct xfifo_dma_dev));
        /* Get our device properties from the device tree, if they exist */
        if (pdev->dev.of_node) {
                if (of_property_read_u32(pdev->dev.of_node, "dma-channel",
                        &xfifo_dma_dev->dma_channel) < 0) {
                                "DMA channel unspecified - assuming 0\n");
                        xfifo_dma_dev->dma_channel = 0;
                        "read DMA channel is %d\n", xfifo_dma_dev->dma_channel);
                if (of_property_read_u32(pdev->dev.of_node, "fifo-depth",
                        &xfifo_dma_dev->fifo_depth) < 0) {
                                "depth unspecified, assuming 0xffffffff\n");
                        xfifo_dma_dev->fifo_depth = 0xffffffff;
                        "DMA fifo depth is %d\n", xfifo_dma_dev->fifo_depth);
                if (of_property_read_u32(pdev->dev.of_node, "burst-length",
                        &xfifo_dma_dev->burst_length) < 0) {
                                "burst length unspecified - assuming 1\n");
                        xfifo_dma_dev->burst_length = 1;
                        "DMA burst length is %d\n", 
        xfifo_dma_dev->pdev = pdev;
        xfifo_dma_dev->devno = MKDEV(xfifo_dma_major, XFIFO_DMA_MINOR);
        PDEBUG("devno is 0x%0x, pdev id is %d\n", xfifo_dma_dev->devno, XFIFO_DMA_MINOR);
        status = register_chrdev_region(xfifo_dma_dev->devno, 1, MODULE_NAME);
        if (status < 0) {
                dev_err(&pdev->dev, "unable to register chrdev %d\n",
                goto fail;
        /* Register with the kernel as a character device */
        cdev_init(&xfifo_dma_dev->cdev, &xfifo_dma_fops);
        xfifo_dma_dev->cdev.owner = THIS_MODULE;
        xfifo_dma_dev->cdev.ops = &xfifo_dma_fops;
        /* Initialize our device mutex */
        xfifo_dma_dev->dev_physaddr = xfifo_dma_resource->start;
        xfifo_dma_dev->dev_addrsize = xfifo_dma_resource->end -
                xfifo_dma_resource->start + 1;
        if (!request_mem_region(xfifo_dma_dev->dev_physaddr,
                xfifo_dma_dev->dev_addrsize, MODULE_NAME)) {
                dev_err(&pdev->dev, "can't reserve i/o memory at 0x%08X\n",
                status = -ENODEV;
                goto fail;
        xfifo_dma_dev->dev_virtaddr = ioremap(xfifo_dma_dev->dev_physaddr,
        PDEBUG("xfifo_dma: mapped 0x%0x to 0x%0x\n", xfifo_dma_dev->dev_physaddr,
                (unsigned int)xfifo_dma_dev->dev_virtaddr);
        xfifo_dma_dev->client_data = kmalloc(sizeof(struct pl330_client_data),
        if (!xfifo_dma_dev->client_data) {
                dev_err(&pdev->dev, "can't allocate PL330 client data\n");
                goto fail;
        memset(xfifo_dma_dev->client_data, 0, sizeof(struct pl330_client_data));
        xfifo_dma_dev->client_data->dev_addr =
                xfifo_dma_dev->dev_physaddr + AXI_TXFIFO;
        xfifo_dma_dev->client_data->dev_bus_des.burst_size = 4;
        xfifo_dma_dev->client_data->dev_bus_des.burst_len =
        xfifo_dma_dev->client_data->mem_bus_des.burst_size = 4;
        xfifo_dma_dev->client_data->mem_bus_des.burst_len =
        status = cdev_add(&xfifo_dma_dev->cdev, xfifo_dma_dev->devno, 1);
        /* Create statistics entry under /proc */
        proc_entry = create_proc_entry("driver/xfifo_dma", 0, NULL);
        if (proc_entry) {
                proc_entry->proc_fops = &xfifo_dma_proc_ops;
        dev_info(&pdev->dev, "added Xilinx FIFO DMA successfully\n");
        return 0;
        return status;
static struct platform_driver xfifo_dma_driver = {
        .driver = {
                .name = MODULE_NAME,
                .owner = THIS_MODULE,
                .of_match_table = xfifodma_of_match,
        .probe = xfifo_dma_probe,
        .remove = xfifo_dma_remove,
static void __exit xfifo_dma_exit(void)
static int __init xfifo_dma_init(void)
        int status;
        status = platform_driver_register(&xfifo_dma_driver);
        return status;
MODULE_AUTHOR("Xilinx, Inc.");
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License