IMPORTANT NOTE: The reference implementation contained on this page is no longer up-to-date and is kept as a reference for design teams working with older kernels.
If you are working with a kernel newer than 3.6 (corresponding to the Xilinx-v14.4 tag on
GitHub), the DMA330 driver is obsolete. The hardware DMA components in the Zynq device are controlled through the standard Linux DMA API.
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:
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.
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
KERN_SRC=/path/to/kernel/source
obj-m := xfifo_dma.o
all:
make -C $(KERN_SRC) ARCH=arm M=`pwd` modules
clean:
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.
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.
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.
IMPORTANT NOTE: The reference implementation contained on this page is no longer up-to-date and is kept as a reference for design teams working with older kernels.
If you are working with a kernel newer than 3.6 (corresponding to the Xilinx-v14.4 tag on GitHub), the DMA330 driver is obsolete. The hardware DMA components in the Zynq device are controlled through the standard Linux DMA API.
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