Zynq Linux pl330 DMA

Table of Contents


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:

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.

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.

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.

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.

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.
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.

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.
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.

Driver Statistics

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

FIFO DMA Test Driver Source