📈
Ween's Lab
UdemyYouTubeTikTok
  • Welcome
  • 📻FPGA Tutorials
    • FPGA Boards: Getting Started
      • Getting Started with PYNQ on Kria KV260 Vision AI Starter Kit
      • Getting Started with PYNQ on Red Pitaya STEMlab 125-14
      • Getting Started with PYNQ on ZYBO
    • FPGA Ethernet Tutorial
      • FPGA Tutorial Ethernet 1: Simple TCP Server
    • PYNQ FPGA Tutorial 101
      • Part 0: Introduction
      • Part 1: GPIO
      • Part 2: Custom IP
      • Part 3: Memory
      • Part 4: ANN Processor
    • PYNQ FPGA Tutorial 102
      • Part 0: Introduction
      • Part 1: Memory Mapped
      • Part 2: Direct Memory Access
      • Part 3: AXI-Lite Multiplier
      • Part 4: AXI-Stream Multiplier with DMA
      • Part 5: AXI-Lite GCD
      • Part 6: AXI-Stream GCD with DMA
      • Part 7: Access to DDR from PL
    • ZYNQ FPGA Tutorial
      • Part 1: Gate-Level Combinational Circuit
      • Part 2: RT-Level Combinational Circuit
      • Part 3: Regular Sequential Circuit
      • Part 4: FSM Sequential Circuit
      • Part 5: ZYNQ Architecture
      • Part 6: ARM CPU and FPGA Module
      • Part 7: FPGA Memory
      • Part 8: Hardware Accelerator for Neural Networks
    • ZYNQ FPGA Linux Kernel Module
      • Cross Compiling Kernel, Kernel Module, and User Program for PYNQ
      • Configure PL to PS Interrupt in Kernel Module
      • Configure AXI DMA in Kernel Module
  • 📟Proyek Arduino
    • Kumpulan Proyek
      • Rangkaian LED
      • LED Berkedip Nyala Api
      • LED Chaser
      • LED Binary Counter
      • OLED 128x4 Bitcoin Ticker
      • Rangkaian Button
      • Button Multifungsi
      • Button Interrupt
      • Button Debouncing
    • Pelatihan Mikrokontroler Arduino ESP32
      • Bab 1 Pengenalan Mikrokontroler
      • Bab 2 Pengenalan Arduino
      • Bab 3 Pengenalan Bahasa C
      • Bab 4 Digital Output
      • Bab 5 Digital Input
      • Bab 6 Serial Communication
      • Bab 7 Analog-to-Digital Conversion
      • Bab 8 Interrupt
      • Bab 9 Timer
      • Bab 10 Pulse-Width Modulation
      • Bab 11 SPI Communication
      • Bab 12 I2C Communication
  • 💰Finance
    • Coding for Finance
      • Build a Bitcoin Price Alert with Google Cloud and Telegram
      • Build a Bitcoin Ticker with ESP32 and Arduino
      • Stock Price Forecasting with LSTM
    • Trading dan Investasi
      • Istilah Ekonomi, Keuangan, Bisnis, Trading, dan Investasi
      • Jalan Menuju Financial Abundance
      • Memahami Korelasi Emas, Oil, Dollar, BTC, Bonds, dan Saham
      • Mindset Trading dan Investasi
      • Rangkuman Buku: Rahasia Analisis Fundamental Saham
      • Rangkuman Buku: The Psychology of Money
      • Rangkuman Kuliah: Introduction to Adaptive Markets
      • Rumus Menjadi Orang Kaya
  • 📝Life
    • Life Quotes
Powered by GitBook
On this page
  • One AXI DMA Loopback Zynq 7000
  • Hardware Design
  • Devicetree Settings
  • Kernel Module
  • Testing
  • One AXI DMA Loopback Zynq UltraScale+
  • Hardware Design
  • Devicetree Settings
  • Kernel Module
  • Testing
  • Two AXI DMA Loopback Zynq UltraScale+
  • Hardware Design
  • Devicetree Settings
  • Kernel Module
  • Testing
  1. FPGA Tutorials
  2. ZYNQ FPGA Linux Kernel Module

Configure AXI DMA in Kernel Module

PreviousConfigure PL to PS Interrupt in Kernel ModuleNextKumpulan Proyek

Last updated 5 months ago

One AXI DMA Loopback Zynq 7000

Hardware Design

Create hardware design that consists of one AXI DMA and one AXIS FIFO. The M_AXIS_MM2S is looped back to S_AXIS_S2MM. Connect the interrupt via the concat IP.

In Zynq configuration, enable the S AXI HP0. Set the port to 64. Don't change it to 32.

In AXI DMA configuration, change the settings to the following.

Devicetree Settings

Create a devicetree overlay file named dma.dts.

  • The clocks parameter should be set with clock value of fclk_clk0 <0x1 0xf>. The value for every board or OS image can be different. Please check. Failing to set this up can cause error -517.

  • The interrupt-parent should be the same as the rest of devicetree.

  • Set the xlnx,addrwidth to 0x20 the same as in AXI DMA setting not the PS.

  • Make sure the DMA address is the same as in Vivado, or there will be error -19 (device not found).

/dts-v1/;
/plugin/;

/ {
	fragment@0 {
		target-path="/fpga-axi@0";
		__overlay__ {
			axidmatest_1: axidmatest@1 {
				compatible ="xlnx,axi-dma-test-1.00.a";
				dmas = <&dma_core 0 &dma_core 1>;
				dma-names = "axidma0", "axidma1";
			} ;

			dma_core: dma@40400000 {
				#dma-cells = <0x1>;
				clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
				clocks = <0x1 0xf 0x1 0xf 0x1 0xf 0x1 0xf>;
				compatible = "xlnx,axi-dma-1.00.a";
				interrupt-names = "mm2s_introut", "s2mm_introut";
				interrupt-parent = <0x1>;
				interrupts = <0x0 0x1f 0x4 0x0 0x20 0x4>;
				reg = <0x40400000 0x10000>;
				xlnx,addrwidth = <0x20>;
				xlnx,include-sg;
				xlnx,sg-length-width = <0xe>;

				dma-channel@40400000 {
					compatible = "xlnx,axi-dma-mm2s-channel";
					dma-channels = <0x1>;
					interrupts = <0x0 0x1f 0x4>;
					xlnx,datawidth = <0x20>;
					xlnx,device-id = <0x1>;
				};

				dma-channel@40400030 {
					compatible = "xlnx,axi-dma-s2mm-channel";
					dma-channels = <0x1>;
					interrupts = <0x0 0x20 0x4>;
					xlnx,datawidth = <0x20>;
					xlnx,device-id = <0x1>;
				};
			};
		};
	};
};

Compile it using the following command.

dtc -@ -I dts -O dtb -o dma.dtbo dma.dts

Kernel Module

Create the source code dma.c. The of_device_id compatible should be the same as in devicetree.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/dmaengine.h>
#include <linux/of_dma.h>
#include <linux/dma/xilinx_dma.h>
#include <linux/slab.h> // for kmalloc
#include <linux/random.h> // for get_random_bytes

static void chan_to_ps_callback(void *completion)
{
	printk(KERN_INFO "RX callback triggered\n");
	complete(completion);
}

static void chan_to_pl_callback(void *completion)
{
	printk(KERN_INFO "TX callback triggered\n");
	complete(completion);
}

static int hello_dma_probe(struct platform_device *pdev)
{
	int err;
	struct dma_chan *tx_chan, *rx_chan;
	struct dma_device *tx_dev;
	struct dma_device *rx_dev;
	u32 rand_byte;
	u16 len = 16;
	u32 *data_src;
	u32 *data_dst;
	u16 i;
	dma_addr_t dma_mapping_addr_src;
	dma_addr_t dma_mapping_addr_dst;
	int bd_cnt = 1;
	struct scatterlist tx_sg;
	struct scatterlist rx_sg;
	struct dma_async_tx_descriptor *txd = NULL;
	struct dma_async_tx_descriptor *rxd = NULL;
	dma_cookie_t tx_cookie;
	dma_cookie_t rx_cookie;

	struct completion chan_to_pl_cmp;
	struct completion chan_to_ps_cmp;
	unsigned long chan_to_ps_tmo =	msecs_to_jiffies(300000);
	unsigned long chan_to_pl_tmo =  msecs_to_jiffies(30000);
	enum dma_status status;

	printk(KERN_INFO "hello_dma_probe() 1 core called\n");

	tx_chan = dma_request_chan(&pdev->dev, "axidma0");
	if (IS_ERR(tx_chan))
	{
		err = PTR_ERR(tx_chan);
		printk("Error: %d\n", err);
		return err;
	}
	rx_chan = dma_request_chan(&pdev->dev, "axidma1");
	if (IS_ERR(rx_chan))
	{
		err = PTR_ERR(rx_chan);
		printk("Error: %d\n", err);
		return err;
	}

	rx_dev = rx_chan->device;
	tx_dev = tx_chan->device;

	data_src = kmalloc(len*sizeof(u32), GFP_KERNEL);
	data_dst = kmalloc(len*sizeof(u32), GFP_KERNEL);

	for (i = 0; i < len; i++)
	{
		get_random_bytes(&rand_byte, sizeof(rand_byte));
		*(data_src+i) = rand_byte;
	}
	for (i = 0; i < len; i++)
		*(data_dst+i) = 0;

	printk(KERN_CONT "Data Src (before): ");
	for (i = 0; i < len; i++)
		printk(KERN_CONT "%d,", *(data_src+i));
	printk(KERN_CONT "\n");
	printk(KERN_CONT "Data Dst (before): ");
	for (i = 0; i < len; i++)
		printk(KERN_CONT "%d,", *(data_dst+i));
	printk(KERN_CONT "\n");

	dma_mapping_addr_src = dma_map_single(tx_dev->dev, data_src, len*sizeof(u32), DMA_MEM_TO_DEV);
	if (dma_mapping_error(tx_dev->dev, dma_mapping_addr_src))
	{
		printk("dma_loopback_test WARNING chan_to_pl_dev DMA mapping error\n");
	}
	dma_mapping_addr_dst = dma_map_single(rx_dev->dev, data_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	if (dma_mapping_error(rx_dev->dev, dma_mapping_addr_dst))
	{
		printk("dma_loopback_test WARNING chan_to_ps_dev DMA mapping error\n");
	}

	wmb();

	sg_init_table(&tx_sg, bd_cnt);
	sg_init_table(&rx_sg, bd_cnt);
	sg_dma_address(&tx_sg) = dma_mapping_addr_src;
	sg_dma_address(&rx_sg) = dma_mapping_addr_dst;
	sg_dma_len(&tx_sg) = len*sizeof(u32);
	sg_dma_len(&rx_sg) = len*sizeof(u32);

	rxd = rx_dev->device_prep_slave_sg(rx_chan, &rx_sg, bd_cnt,
			DMA_DEV_TO_MEM, DMA_CTRL_ACK | DMA_PREP_INTERRUPT, NULL);
	txd = tx_dev->device_prep_slave_sg(tx_chan, &tx_sg, bd_cnt,
			DMA_MEM_TO_DEV, DMA_CTRL_ACK | DMA_PREP_INTERRUPT, NULL);

	if (!rxd || !txd)
	{
		printk("Error: device_prep_slave_sg\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	}

	init_completion(&chan_to_pl_cmp);
	txd->callback = chan_to_pl_callback;
	txd->callback_param = &chan_to_pl_cmp;
	tx_cookie = txd->tx_submit(txd);

	init_completion(&chan_to_ps_cmp);
	rxd->callback = chan_to_ps_callback;
	rxd->callback_param = &chan_to_ps_cmp;
	rx_cookie = rxd->tx_submit(rxd);

	if (dma_submit_error(rx_cookie) || dma_submit_error(tx_cookie))
	{
		printk("Error: dma_submit_error\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	}

	wmb();

	dma_async_issue_pending(rx_chan);
	dma_async_issue_pending(tx_chan);

	chan_to_pl_tmo = wait_for_completion_timeout(&chan_to_pl_cmp, chan_to_pl_tmo);
	status = dma_async_is_tx_complete(tx_chan, tx_cookie, NULL, NULL);
	if (chan_to_pl_tmo == 0) {
		printk("dma_loopback_test chan_to_pl_tmo == 0\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	} else if (status != DMA_COMPLETE) {
		printk("dma_loopback_test chan_to_pl status != DMA_COMPLETE\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	}
	printk("Status chan_to_pl: %d\n", status);

	chan_to_ps_tmo = wait_for_completion_timeout(&chan_to_ps_cmp, chan_to_ps_tmo);
	status = dma_async_is_tx_complete(rx_chan, rx_cookie, NULL, NULL);
	if (chan_to_ps_tmo == 0) {
		printk("dma_loopback_test chan_to_pl_tmo == 0\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	} else if (status != DMA_COMPLETE) {
		printk("dma_loopback_test chan_to_pl status != DMA_COMPLETE\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	}
	printk("Status chan_to_ps: %d\n", status);

	rmb();

	printk(KERN_CONT "Data Src (after): ");
	for (i = 0; i < len; i++)
		printk(KERN_CONT "%d,", *(data_src+i));
	printk(KERN_CONT "\n");
	printk(KERN_CONT "Data Dst (after): ");
	for (i = 0; i < len; i++)
		printk(KERN_CONT "%d,", *(data_dst+i));
	printk(KERN_CONT "\n");

	dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
	dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);

	kfree(data_src);
	kfree(data_dst);

	pr_info("xilinx_dmatest: dropped channel %s\n", dma_chan_name(rx_chan));
	dmaengine_terminate_all(rx_chan);
	dma_release_channel(rx_chan);

	pr_info("xilinx_dmatest: dropped channel %s\n", dma_chan_name(tx_chan));
	dmaengine_terminate_all(tx_chan);
	dma_release_channel(tx_chan);

	return 0;
}

static int hello_dma_remove(struct platform_device *pdev)
{
	printk(KERN_INFO "hello_dma_remove() called\n");
	return 0;
}

static const struct of_device_id hello_dma_of_ids[] = {
	{ .compatible = "xlnx,axi-dma-test-1.00.a",},
	{}
};

static struct platform_driver hello_dma_driver = {
	.driver = {
		.name = "xilinx_axidmatest",
		.of_match_table = hello_dma_of_ids,
	},
	.probe = hello_dma_probe,
	.remove = hello_dma_remove,
};

static int __init init_hello_dma(void)
{
	printk(KERN_INFO "init_hello_dma() called\n");
	return platform_driver_register(&hello_dma_driver);
}
late_initcall(init_hello_dma);

static void __exit cleanup_hello_dma(void)
{
	printk(KERN_INFO "cleanup_hello_dma() called\n");
	platform_driver_unregister(&hello_dma_driver);
}
module_exit(cleanup_hello_dma);

MODULE_AUTHOR("ouyang168");
MODULE_LICENSE("GPL");

This is the Makefile.

ARCH = arm
COMPILER = arm-linux-gnueabihf-
obj-m := dma_test.o
KERNELDIR := /home/erwin/Workspace/kernel/linux-xlnx-xlnx_rebase_v5.15_LTS_2022.1_update32
PWD := $(shell pwd)

default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(COMPILER) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) clean

Testing

Program the bitstream and devicetree from PYNQ Overlay.

from pynq import Overlay
overlay = Overlay('/home/xilinx/workspace/dma.bit', dtbo='/home/xilinx/workspace/dma.dtbo')

Load the kernel module.

insmod dma.ko

Check the kernel log.

root@pynq:/home/xilinx/workspace# dmesg
[  302.195902] init_hello_dma() called
[  302.196129] hello_dma_probe() 1 core called
[  302.198141] Data Src (before): -1332256886,1275566844,1964206342,1481593683,-1719961659,-301784639,1495780154,-819762099,-585545515,1598919762,1239023186,494217514,1929676417,-1329315840,140794627,177868873,
[  302.198247] Data Dst (before): 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
[  302.198365] TX callback triggered
[  302.198378] RX callback triggered
[  302.198398] Status chan_to_pl: 0
[  302.198409] Status chan_to_ps: 0
[  302.198416] Data Src (after): -1332256886,1275566844,1964206342,1481593683,-1719961659,-301784639,1495780154,-819762099,-585545515,1598919762,1239023186,494217514,1929676417,-1329315840,140794627,177868873,
[  302.198513] Data Dst (after): -1332256886,1275566844,1964206342,1481593683,-1719961659,-301784639,1495780154,-819762099,-585545515,1598919762,1239023186,494217514,1929676417,-1329315840,140794627,177868873,
[  302.198611] xilinx_dmatest: dropped channel dma1chan0
[  302.198656] xilinx_dmatest: dropped channel dma1chan1

Check the DMA interrupt (0x1F, 31+32=63 and 0x20, 32+32=64).

root@pynq:/home/xilinx/workspace# cat /proc/interrupts
           CPU0       CPU1
 24:          0          0     GIC-0  27 Edge      gt
 25:      23430      21760     GIC-0  29 Edge      twd
 26:          0          0     GIC-0  37 Level     arm-pmu
 27:          0          0     GIC-0  38 Level     arm-pmu
 28:         43          0     GIC-0  39 Level     f8007100.adc
 30:          2          0     GIC-0  57 Level     cdns-i2c
 32:          0          0     GIC-0  35 Level     f800c000.ocmc
 33:       1089          0     GIC-0  82 Level     xuartps
 34:          0          0     GIC-0  51 Level     e000d000.spi
 35:       6801          0     GIC-0  54 Level     eth0
 36:      33825          0     GIC-0  56 Level     mmc0
 37:          0          0     GIC-0  45 Level     f8003000.dmac
 38:          0          0     GIC-0  46 Level     f8003000.dmac
 39:          0          0     GIC-0  47 Level     f8003000.dmac
 40:          0          0     GIC-0  48 Level     f8003000.dmac
 41:          0          0     GIC-0  49 Level     f8003000.dmac
 42:          0          0     GIC-0  72 Level     f8003000.dmac
 43:          0          0     GIC-0  73 Level     f8003000.dmac
 44:          0          0     GIC-0  74 Level     f8003000.dmac
 45:          0          0     GIC-0  75 Level     f8003000.dmac
 46:         36          0     GIC-0  40 Level     f8007000.devcfg
 48:          0          0     GIC-0  43 Level     ttc_clockevent
 53:          0          0     GIC-0  53 Level     e0002000.usb
 54:          0          0     GIC-0  41 Edge      f8005000.watchdog
 56:          0          0  zynq-gpio  50 Edge      btn4
 57:          0          0  zynq-gpio  51 Edge      btn5
 58:          2          0     GIC-0  63 Level     xilinx-dma-controller
 59:          2          0     GIC-0  64 Level     xilinx-dma-controller
IPI0:          0          0  CPU wakeup interrupts
IPI1:          0          0  Timer broadcast interrupts
IPI2:       9462      15286  Rescheduling interrupts
IPI3:        478        407  Function call interrupts
IPI4:          0          0  CPU stop interrupts
IPI5:          0          0  IRQ work interrupts
IPI6:          0          0  completion interrupts

How many interrupts occurred can be monitored here on the CPU0 column.

One AXI DMA Loopback Zynq UltraScale+

Hardware Design

Create hardware design that consists of one AXI DMA and one AXIS FIFO. The M_AXIS_MM2S is looped back to S_AXIS_S2MM. Connect the interrupt via the concat IP.

In Zynq configuration, enable the AXI_HP0. Set the port to 128. Don't change it to 64.

In AXI DMA configuration, change the settings to the following.

Devicetree Settings

Create a devicetree overlay file named dma.dts.

  • The clocks parameter should be set with clock value of fclk0 <0x3 0x47>. Failing to set this up can cause error -517.

  • The interrupt-parent should be the same as the rest of devicetree.

  • Set the xlnx,addrwidth to 0x40 the same as in AXI DMA setting not the PS.

  • Make sure the DMA address is the same as in Vivado, or there will be error -19 (device not found).

/dts-v1/;
/plugin/;

/ {
	fragment@0 {
		target-path="/fpga-axi@0";
		__overlay__ {
			axidmatest_1: axidmatest@1 {
				compatible ="xlnx,axi-dma-test-1.00.a";
				dmas = <&dma_core 0 &dma_core 1>;
				dma-names = "axidma0", "axidma1";
			};

			dma_core: dma@a0000000 {
				#dma-cells = <0x1>;
				clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
				clocks = <0x3 0x47>, <0x3 0x47>, <0x3 0x47>, <0x3 0x47>;
				compatible = "xlnx,axi-dma-1.00.a";
				interrupt-names = "mm2s_introut", "s2mm_introut";
				interrupt-parent = <0x4>;
				interrupts = <0x0 0x5B 0x4 0x0 0x5C 0x4>;
				reg = <0xa0000000 0x10000>;
				xlnx,addrwidth = <0x40>;
				xlnx,include-sg;
				xlnx,sg-length-width = <0xe>;

				dma-channel@a0000000 {
					compatible = "xlnx,axi-dma-mm2s-channel";
					dma-channels = <0x1>;
					interrupts = <0x0 0x5B 0x4>;
					xlnx,datawidth = <0x20>;
					xlnx,device-id = <0x1>;
				};

				dma-channel@a0000030 {
					compatible = "xlnx,axi-dma-s2mm-channel";
					dma-channels = <0x1>;
					interrupts = <0x0 0x5C 0x4>;
					xlnx,datawidth = <0x20>;
					xlnx,device-id = <0x1>;
				};
			};
		};
	};
};

Compile it using the following command.

dtc -@ -I dts -O dtb -o dma.dtbo dma.dts

Kernel Module

Create the source code dma.c. The of_device_id compatible should be the same as in devicetree.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/dmaengine.h>
#include <linux/of_dma.h>
#include <linux/dma/xilinx_dma.h>
#include <linux/slab.h> // for kmalloc
#include <linux/random.h> // for get_random_bytes

static void chan_to_ps_callback(void *completion)
{
	printk(KERN_INFO "RX callback triggered\n");
	complete(completion);
}

static void chan_to_pl_callback(void *completion)
{
	printk(KERN_INFO "TX callback triggered\n");
	complete(completion);
}

static int hello_dma_probe(struct platform_device *pdev)
{
	int err;
	struct dma_chan *tx_chan, *rx_chan;
	struct dma_device *tx_dev;
	struct dma_device *rx_dev;
	u32 rand_byte;
	u16 len = 16;
	u32 *data_src;
	u32 *data_dst;
	u16 i;
	dma_addr_t dma_mapping_addr_src;
	dma_addr_t dma_mapping_addr_dst;
	int bd_cnt = 1;
	struct scatterlist tx_sg;
	struct scatterlist rx_sg;
	struct dma_async_tx_descriptor *txd = NULL;
	struct dma_async_tx_descriptor *rxd = NULL;
	dma_cookie_t tx_cookie;
	dma_cookie_t rx_cookie;

	struct completion chan_to_pl_cmp;
	struct completion chan_to_ps_cmp;
	unsigned long chan_to_ps_tmo =	msecs_to_jiffies(300000);
	unsigned long chan_to_pl_tmo =  msecs_to_jiffies(30000);
	enum dma_status status;

	printk(KERN_INFO "hello_dma_probe() 1 core called\n");

	tx_chan = dma_request_chan(&pdev->dev, "axidma0");
	if (IS_ERR(tx_chan))
	{
		err = PTR_ERR(tx_chan);
		printk("Error: %d\n", err);
		return err;
	}
	rx_chan = dma_request_chan(&pdev->dev, "axidma1");
	if (IS_ERR(rx_chan))
	{
		err = PTR_ERR(rx_chan);
		printk("Error: %d\n", err);
		return err;
	}

	rx_dev = rx_chan->device;
	tx_dev = tx_chan->device;

	data_src = kmalloc(len*sizeof(u32), GFP_KERNEL);
	data_dst = kmalloc(len*sizeof(u32), GFP_KERNEL);

	for (i = 0; i < len; i++)
	{
		// get_random_bytes(&rand_byte, sizeof(rand_byte));
		*(data_src+i) = i;
	}
	for (i = 0; i < len; i++)
		*(data_dst+i) = 0;

	printk(KERN_CONT "Data Src (before): ");
	for (i = 0; i < len; i++)
		printk(KERN_CONT "%d,", *(data_src+i));
	printk(KERN_CONT "\n");
	printk(KERN_CONT "Data Dst (before): ");
	for (i = 0; i < len; i++)
		printk(KERN_CONT "%d,", *(data_dst+i));
	printk(KERN_CONT "\n");

	dma_mapping_addr_src = dma_map_single(tx_dev->dev, data_src, len*sizeof(u32), DMA_MEM_TO_DEV);
	if (dma_mapping_error(tx_dev->dev, dma_mapping_addr_src))
	{
		printk("dma_loopback_test WARNING chan_to_pl_dev DMA mapping error\n");
	}
	dma_mapping_addr_dst = dma_map_single(rx_dev->dev, data_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	if (dma_mapping_error(rx_dev->dev, dma_mapping_addr_dst))
	{
		printk("dma_loopback_test WARNING chan_to_ps_dev DMA mapping error\n");
	}

	wmb();

	sg_init_table(&tx_sg, bd_cnt);
	sg_init_table(&rx_sg, bd_cnt);
	sg_dma_address(&tx_sg) = dma_mapping_addr_src;
	sg_dma_address(&rx_sg) = dma_mapping_addr_dst;
	sg_dma_len(&tx_sg) = len*sizeof(u32);
	sg_dma_len(&rx_sg) = len*sizeof(u32);

	rxd = rx_dev->device_prep_slave_sg(rx_chan, &rx_sg, bd_cnt,
			DMA_DEV_TO_MEM, DMA_CTRL_ACK | DMA_PREP_INTERRUPT, NULL);
	txd = tx_dev->device_prep_slave_sg(tx_chan, &tx_sg, bd_cnt,
			DMA_MEM_TO_DEV, DMA_CTRL_ACK | DMA_PREP_INTERRUPT, NULL);

	if (!rxd || !txd)
	{
		printk("Error: device_prep_slave_sg\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	}

	init_completion(&chan_to_pl_cmp);
	txd->callback = chan_to_pl_callback;
	txd->callback_param = &chan_to_pl_cmp;
	tx_cookie = txd->tx_submit(txd);

	init_completion(&chan_to_ps_cmp);
	rxd->callback = chan_to_ps_callback;
	rxd->callback_param = &chan_to_ps_cmp;
	rx_cookie = rxd->tx_submit(rxd);

	if (dma_submit_error(rx_cookie) || dma_submit_error(tx_cookie))
	{
		printk("Error: dma_submit_error\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	}

	wmb();

	dma_async_issue_pending(rx_chan);
	dma_async_issue_pending(tx_chan);

	chan_to_pl_tmo = wait_for_completion_timeout(&chan_to_pl_cmp, chan_to_pl_tmo);
	status = dma_async_is_tx_complete(tx_chan, tx_cookie, NULL, NULL);
	if (chan_to_pl_tmo == 0) {
		printk("dma_loopback_test chan_to_pl_tmo == 0\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	} else if (status != DMA_COMPLETE) {
		printk("dma_loopback_test chan_to_pl status != DMA_COMPLETE\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	}
	printk("Status chan_to_pl: %d\n", status);

	chan_to_ps_tmo = wait_for_completion_timeout(&chan_to_ps_cmp, chan_to_ps_tmo);
	status = dma_async_is_tx_complete(rx_chan, rx_cookie, NULL, NULL);
	if (chan_to_ps_tmo == 0) {
		printk("dma_loopback_test chan_to_pl_tmo == 0\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	} else if (status != DMA_COMPLETE) {
		printk("dma_loopback_test chan_to_pl status != DMA_COMPLETE\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	}
	printk("Status chan_to_ps: %d\n", status);

	rmb();

	printk(KERN_CONT "Data Src (after): ");
	for (i = 0; i < len; i++)
		printk(KERN_CONT "%d,", *(data_src+i));
	printk(KERN_CONT "\n");
	printk(KERN_CONT "Data Dst (after): ");
	for (i = 0; i < len; i++)
		printk(KERN_CONT "%d,", *(data_dst+i));
	printk(KERN_CONT "\n");

	dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
	dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);

	kfree(data_src);
	kfree(data_dst);

	pr_info("xilinx_dmatest: dropped channel %s\n", dma_chan_name(rx_chan));
	dmaengine_terminate_all(rx_chan);
	dma_release_channel(rx_chan);

	pr_info("xilinx_dmatest: dropped channel %s\n", dma_chan_name(tx_chan));
	dmaengine_terminate_all(tx_chan);
	dma_release_channel(tx_chan);

	return 0;
}

static int hello_dma_remove(struct platform_device *pdev)
{
	printk(KERN_INFO "hello_dma_remove() called\n");
	return 0;
}

static const struct of_device_id hello_dma_of_ids[] = {
	{ .compatible = "xlnx,axi-dma-test-1.00.a",},
	{}
};

static struct platform_driver hello_dma_driver = {
	.driver = {
		.name = "xilinx_axidmatest",
		.of_match_table = hello_dma_of_ids,
	},
	.probe = hello_dma_probe,
	.remove = hello_dma_remove,
};

static int __init init_hello_dma(void)
{
	printk(KERN_INFO "init_hello_dma() called\n");
	return platform_driver_register(&hello_dma_driver);
}
late_initcall(init_hello_dma);

static void __exit cleanup_hello_dma(void)
{
	printk(KERN_INFO "cleanup_hello_dma() called\n");
	platform_driver_unregister(&hello_dma_driver);
}
module_exit(cleanup_hello_dma);

MODULE_AUTHOR("ouyang168");
MODULE_LICENSE("GPL");

This is the Makefile.

ARCH = arm64
COMPILER = aarch64-linux-gnu-
obj-m := dma.o
KERNELDIR := /home/erwin/Workspace/kernel/linux-xlnx-xlnx_rebase_v5.15_LTS_2022.1_update
PWD := $(shell pwd)
EXTRA_CFLAGS += -fstack-protector -D__KERNEL__

default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(COMPILER) modules EXTRA_CFLAGS="$(EXTRA_CFLAGS)"

clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) clean

Testing

Program the bitstream and devicetree from PYNQ Overlay.

from pynq import Overlay
overlay = Overlay('/home/xilinx/workspace/dma.bit', dtbo='/home/xilinx/workspace/dma.dtbo')

Load the kernel module.

insmod dma.ko

Check the kernel log.

root@pynq:/home/xilinx/workspace# dmesg
[ 5383.312510] init_hello_dma() called
[ 5383.312756] hello_dma_probe() 1 core called
[ 5383.313047] Data Src (before): 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
[ 5383.313089] Data Dst (before): 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
[ 5383.313156] TX callback triggered
[ 5383.313174] RX callback triggered
[ 5383.313980] Status chan_to_pl: 0
[ 5383.313985] Status chan_to_ps: 0
[ 5383.313988] Data Src (after): 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
[ 5383.314028] Data Dst (after): 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
[ 5383.314068] xilinx_dmatest: dropped channel dma17chan0
[ 5383.314094] xilinx_dmatest: dropped channel dma17chan1

Check the DMA interrupt (0x5B, 91+32=123 and 0x5C, 92+32=124).

root@pynq:/home/xilinx/workspace# cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3
 11:      88013     104938     106808     150556     GICv2  30 Level     arch_timer
 14:          0          0          0          0     GICv2  67 Level     zynqmp_ipi
 15:          0          0          0          0     GICv2 175 Level     arm-pmu
 16:          0          0          0          0     GICv2 176 Level     arm-pmu
 17:          0          0          0          0     GICv2 177 Level     arm-pmu
 18:          0          0          0          0     GICv2 178 Level     arm-pmu
 19:          0          0          0          0     GICv2 155 Level     axi-pmon, axi-pmon
 20:          0          0          0          0     GICv2 156 Level     zynqmp-dma
 21:          0          0          0          0     GICv2 157 Level     zynqmp-dma
 22:          0          0          0          0     GICv2 158 Level     zynqmp-dma
 23:          0          0          0          0     GICv2 159 Level     zynqmp-dma
 24:          0          0          0          0     GICv2 160 Level     zynqmp-dma
 25:          0          0          0          0     GICv2 161 Level     zynqmp-dma
 26:          0          0          0          0     GICv2 162 Level     zynqmp-dma
 27:          0          0          0          0     GICv2 163 Level     zynqmp-dma
 28:          0          0          0          0     GICv2 109 Level     zynqmp-dma
 29:          0          0          0          0     GICv2 110 Level     zynqmp-dma
 30:          0          0          0          0     GICv2 111 Level     zynqmp-dma
 31:          0          0          0          0     GICv2 112 Level     zynqmp-dma
 32:          0          0          0          0     GICv2 113 Level     zynqmp-dma
 33:          0          0          0          0     GICv2 114 Level     zynqmp-dma
 34:          0          0          0          0     GICv2 115 Level     zynqmp-dma
 35:          0          0          0          0     GICv2 116 Level     zynqmp-dma
 37:      99096          0          0          0     GICv2  91 Level     eth0, eth0
 39:         36          0          0          0     GICv2  49 Level     cdns-i2c
 40:          0          0          0          0     GICv2  50 Level     cdns-i2c
 41:          0          0          0          0     GICv2  42 Level     ff960000.memory-controller
 42:          0          0          0          0     GICv2  57 Level     axi-pmon, axi-pmon
 43:          0          0          0          0     GICv2  58 Level     ffa60000.rtc
 44:          0          0          0          0     GICv2  59 Level     ffa60000.rtc
 45:     115325          0          0          0     GICv2  80 Level     mmc0
 46:        368          0          0          0     GICv2  51 Level     ff040000.spi
 47:         83          0          0          0     GICv2  52 Level     ff050000.spi
 48:       2340          0          0          0     GICv2  54 Level     xuartps
 49:          0          0          0          0     GICv2  88 Level     ams-irq
 50:          0          0          0          0     GICv2 154 Level     fd4c0000.dma-controller
 51:          0          0          0          0     GICv2 151 Level     fd4a0000.display
 53:          0          0          0          0     GICv2  97 Level     dwc3
 55:          0          0          0          0     GICv2 107 Level     usb-wakeup, usb-wakeup
 56:         71          0          0          0     GICv2 102 Level     xhci-hcd:usb1
 62:          9          0          0          0     GICv2 123 Level     xilinx-dma-controller
 63:          9          0          0          0     GICv2 124 Level     xilinx-dma-controller

How many interrupts occurred can be monitored here on the CPU0 column.

Two AXI DMA Loopback Zynq UltraScale+

Hardware Design

Create hardware design that consists of two AXI DMA and one AXIS FIFO. The M_AXIS_MM2S of AXI DMA 0 is looped back to S_AXIS_S2MM of AXI DMA 1. Connect the interrupt via the concat IP.

In Zynq configuration, enable the AXI_HP0 and AXI_HP2. Set the port to 128. Don't change it to 64.

In AXI DMA configuration, change the settings to the following.

Devicetree Settings

Create a devicetree overlay file named dma.dts.

  • The clocks parameter should be set with clock value of fclk0 <0x3 0x47>. Failing to set this up can cause error -517.

  • The interrupt-parent should be the same as the rest of devicetree.

  • Set the xlnx,addrwidth to 0x40.

  • Make sure the DMA address is the same as in Vivado, or there will be error -19 (device not found).

/dts-v1/;
/plugin/;

/ {
	fragment@0 {
		target-path="/fpga-axi@0";
		__overlay__ {
			sdr {
				compatible = "sdr,sdr";
				dmas = <&rx_dma 1 &tx_dma 0>;
				dma-names = "rx_dma_s2mm", "tx_dma_mm2s";
				interrupt-names = "rx_itrpt", "tx_itrpt";
				interrupt-parent = <0x4>;
				interrupts = <0x0 0x5A 0x1 0x0 0x5E 0x1>;
			};

			tx_dma: dma@a0000000 {
				#dma-cells = <0x1>;
				clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
				clocks = <0x3 0x47>, <0x3 0x47>, <0x3 0x47>, <0x3 0x47>;
				compatible = "xlnx,axi-dma-1.00.a";
				interrupt-names = "mm2s_introut", "s2mm_introut";
				interrupt-parent = <0x4>;
				interrupts = <0x0 0x5F 0x4 0x0 0x60 0x4>;
				reg = <0xa0000000 0x10000>;
				xlnx,addrwidth = <0x40>;
				xlnx,include-sg;
				xlnx,sg-length-width = <0xe>;

				dma-channel@a0000000 {
					compatible = "xlnx,axi-dma-mm2s-channel";
					dma-channels = <0x1>;
					interrupts = <0x0 0x5F 0x4>;
					xlnx,datawidth = <0x20>;
					xlnx,device-id = <0x0>;
				};

				dma-channel@a0000030 {
					compatible = "xlnx,axi-dma-s2mm-channel";
					dma-channels = <0x1>;
					interrupts = <0x0 0x60 0x4>;
					xlnx,datawidth = <0x20>;
					xlnx,device-id = <0x0>;
				};
			};

			rx_dma: dma@a0010000 {
				#dma-cells = <0x1>;
				clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
				clocks = <0x3 0x47>, <0x3 0x47>, <0x3 0x47>, <0x3 0x47>;
				compatible = "xlnx,axi-dma-1.00.a";
				interrupt-names = "mm2s_introut", "s2mm_introut";
				interrupt-parent = <0x4>;
				interrupts = <0x0 0x5B 0x4 0x0 0x5C 0x4>;
				reg = <0xa0010000 0x10000>;
				xlnx,addrwidth = <0x40>;
				xlnx,include-sg;
				xlnx,sg-length-width = <0xe>;

				dma-channel@a0010000 {
					compatible = "xlnx,axi-dma-mm2s-channel";
					dma-channels = <0x1>;
					interrupts = <0x0 0x5B 0x4>;
					xlnx,datawidth = <0x20>;
					xlnx,device-id = <0x1>;
				};

				dma-channel@a0010030 {
					compatible = "xlnx,axi-dma-s2mm-channel";
					dma-channels = <0x1>;
					interrupts = <0x0 0x5C 0x4>;
					xlnx,datawidth = <0x20>;
					xlnx,device-id = <0x1>;
				};
			};
		};
	};
};

Compile it using the following command.

dtc -@ -I dts -O dtb -o dma.dtbo dma.dts

Kernel Module

Create the source code dma.c. The of_device_id compatible should be the same as in devicetree.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/dmaengine.h>
#include <linux/of_dma.h>
#include <linux/dma/xilinx_dma.h>
#include <linux/slab.h> // for kmalloc
#include <linux/random.h> // for get_random_bytes

MODULE_LICENSE("GPL");
MODULE_AUTHOR("deadbeef");

// ### Funtion prototypes ######################################################
static int hello_dma_probe(struct platform_device *pdev);
static int hello_dma_remove(struct platform_device *pdev);
static int __init init_hello_dma(void);
static void __exit cleanup_hello_dma(void);

// ### Variable declarations ###################################################
// For platform driver
static const struct of_device_id hello_dma_of_ids[] = {
	{ .compatible = "sdr,sdr",},
	{}
};
// For platform driver
static struct platform_driver hello_dma_driver = {
	.driver = {
		.name = "sdr,sdr",
		.of_match_table = hello_dma_of_ids,
	},
	.probe = hello_dma_probe,
	.remove = hello_dma_remove,
};

// ### Functions declarations ##################################################
static int hello_dma_probe(struct platform_device *pdev)
{
	int err;
	struct dma_chan *tx_chan, *rx_chan;
	struct dma_device *tx_dev;
	struct dma_device *rx_dev;
	u32 rand_byte;
	u16 len = 16;
	u32 *data_src;
	u32 *data_dst;
	u16 i;
	dma_addr_t dma_mapping_addr_src;
	dma_addr_t dma_mapping_addr_dst;
	int bd_cnt = 1;
	struct scatterlist tx_sg;
	struct scatterlist rx_sg;
	struct dma_async_tx_descriptor *txd = NULL;
	struct dma_async_tx_descriptor *rxd = NULL;
	dma_cookie_t tx_cookie;
	dma_cookie_t rx_cookie;

	printk(KERN_INFO "hello_dma_probe() called\n");

	tx_chan = dma_request_chan(&pdev->dev, "tx_dma_mm2s");
	if (IS_ERR(tx_chan))
	{
		err = PTR_ERR(tx_chan);
		printk("Error DMA: %d\n", err);
		return err;
	}
	rx_chan = dma_request_chan(&pdev->dev, "rx_dma_s2mm");
	if (IS_ERR(rx_chan))
	{
		err = PTR_ERR(rx_chan);
		printk("Error: %d\n", err);
		return err;
	}

	rx_dev = rx_chan->device;
	tx_dev = tx_chan->device;

	data_src = kmalloc(len*sizeof(u32), GFP_KERNEL);
	data_dst = kmalloc(len*sizeof(u32), GFP_KERNEL);

	for (i = 0; i < len; i++)
	{
		// get_random_bytes(&rand_byte, sizeof(rand_byte));
		*(data_src+i) = 0xF0000000 + i;
	}
	for (i = 0; i < len; i++)
		*(data_dst+i) = 0;

	printk(KERN_CONT "Data Src (before): ");
	for (i = 0; i < len; i++)
		printk(KERN_CONT "%08X,", *(data_src+i));
	printk(KERN_CONT "\n");
	printk(KERN_CONT "Data Dst (before): ");
	for (i = 0; i < len; i++)
		printk(KERN_CONT "%08X,", *(data_dst+i));
	printk(KERN_CONT "\n");

	dma_mapping_addr_src = dma_map_single(tx_dev->dev, data_src, len*sizeof(u32), DMA_MEM_TO_DEV);
	dma_mapping_addr_dst = dma_map_single(rx_dev->dev, data_dst, len*sizeof(u32), DMA_DEV_TO_MEM);

	sg_init_table(&tx_sg, bd_cnt);
	sg_init_table(&rx_sg, bd_cnt);
	sg_dma_address(&tx_sg) = dma_mapping_addr_src;
	sg_dma_address(&rx_sg) = dma_mapping_addr_dst;
	sg_dma_len(&tx_sg) = len*sizeof(u32);
	sg_dma_len(&rx_sg) = len*sizeof(u32);

	rxd = rx_dev->device_prep_slave_sg(rx_chan, &rx_sg, bd_cnt,
			DMA_DEV_TO_MEM, DMA_CTRL_ACK | DMA_PREP_INTERRUPT, NULL);
	txd = tx_dev->device_prep_slave_sg(tx_chan, &tx_sg, bd_cnt,
			DMA_MEM_TO_DEV, DMA_CTRL_ACK | DMA_PREP_INTERRUPT, NULL);

	if (!rxd || !txd)
	{
		printk("Error: device_prep_slave_sg\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	}

	rx_cookie = rxd->tx_submit(rxd);
	tx_cookie = txd->tx_submit(txd);

	if (dma_submit_error(rx_cookie) || dma_submit_error(tx_cookie))
	{
		printk("Error: dma_submit_error\n");
		dma_unmap_single(tx_dev->dev, dma_mapping_addr_src, len*sizeof(u32), DMA_MEM_TO_DEV);
		dma_unmap_single(rx_dev->dev, dma_mapping_addr_dst, len*sizeof(u32), DMA_DEV_TO_MEM);
	}

	dma_async_issue_pending(rx_chan);
	dma_async_issue_pending(tx_chan);

	for (i = 0; i < 1000; i++);

	printk(KERN_CONT "Data Src (after): ");
	for (i = 0; i < len; i++)
		printk(KERN_CONT "%08X,", *(data_src+i));
	printk(KERN_CONT "\n");
	printk(KERN_CONT "Data Dst (after): ");
	for (i = 0; i < len; i++)
		printk(KERN_CONT "%08X,", *(data_dst+i));
	printk(KERN_CONT "\n");

	kfree(data_src);
	kfree(data_dst);

	pr_info("xilinx_dmatest: dropped channel %s\n", dma_chan_name(rx_chan));
	dmaengine_terminate_all(rx_chan);
	dma_release_channel(rx_chan);

	pr_info("xilinx_dmatest: dropped channel %s\n", dma_chan_name(tx_chan));
	dmaengine_terminate_all(tx_chan);
	dma_release_channel(tx_chan);

	return 0;
}

static int hello_dma_remove(struct platform_device *pdev)
{
	printk(KERN_INFO "hello_dma_remove() called\n");
	return 0;
}

static int __init init_hello_dma(void)
{
	printk(KERN_INFO "init_hello_dma() called\n");
	return platform_driver_register(&hello_dma_driver);
}

static void __exit cleanup_hello_dma(void)
{
	printk(KERN_INFO "cleanup_hello_dma() called\n");
	platform_driver_unregister(&hello_dma_driver);
}

// ### Entry and exit points ###################################################
late_initcall(init_hello_dma);
module_exit(cleanup_hello_dma);

This is the Makefile.

ARCH = arm64
COMPILER = aarch64-linux-gnu-
obj-m := dma.o
KERNELDIR := /home/erwin/Workspace/kernel/linux-xlnx-xlnx_rebase_v5.15_LTS_2022.1_update
PWD := $(shell pwd)
EXTRA_CFLAGS += -fstack-protector -D__KERNEL__

default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(COMPILER) modules EXTRA_CFLAGS="$(EXTRA_CFLAGS)"

clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) clean

Testing

Program the bitstream and devicetree from PYNQ Overlay.

from pynq import Overlay
overlay = Overlay('/home/xilinx/workspace/dma.bit', dtbo='/home/xilinx/workspace/dma.dtbo')

Load the kernel module.

insmod dma.ko

Check the kernel log.

root@pynq:/home/xilinx/workspace# dmesg
[  427.117920] init_hello_dma() called
[  427.118164] hello_dma_probe() called
[  427.118452] Data Src (before): F0000000,F0000001,F0000002,F0000003,F0000004,F0000005,F0000006,F0000007,F0000008,F0000009,F000000A,F000000B,F000000C,F000000D,F000000E,F000000F,
[  427.118498] Data Dst (before): 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,
[  427.118552] Data Src (after): F0000000,F0000001,F0000002,F0000003,F0000004,F0000005,F0000006,F0000007,F0000008,F0000009,F000000A,F000000B,F000000C,F000000D,F000000E,F000000F,
[  427.118594] Data Dst (after): F0000000,F0000001,F0000002,F0000003,F0000004,F0000005,F0000006,F0000007,F0000008,F0000009,F000000A,F000000B,F000000C,F000000D,F000000E,F000000F,
[  427.118636] xilinx_dmatest: dropped channel dma18chan0
[  427.118660] xilinx_dmatest: dropped channel dma17chan1

Check the DMA interrupt (0x5B, 91+32=123; 0x5C, 92+32=124; 0x5F, 95+32=127; and 0x60, 96+32=128).

root@pynq:/home/xilinx/workspace# cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3
 11:      27428      16769      15454      16768     GICv2  30 Level     arch_timer
 14:          0          0          0          0     GICv2  67 Level     zynqmp_ipi
 15:          0          0          0          0     GICv2 175 Level     arm-pmu
 16:          0          0          0          0     GICv2 176 Level     arm-pmu
 17:          0          0          0          0     GICv2 177 Level     arm-pmu
 18:          0          0          0          0     GICv2 178 Level     arm-pmu
 19:          0          0          0          0     GICv2 155 Level     axi-pmon, axi-pmon
 20:          0          0          0          0     GICv2 156 Level     zynqmp-dma
 21:          0          0          0          0     GICv2 157 Level     zynqmp-dma
 22:          0          0          0          0     GICv2 158 Level     zynqmp-dma
 23:          0          0          0          0     GICv2 159 Level     zynqmp-dma
 24:          0          0          0          0     GICv2 160 Level     zynqmp-dma
 25:          0          0          0          0     GICv2 161 Level     zynqmp-dma
 26:          0          0          0          0     GICv2 162 Level     zynqmp-dma
 27:          0          0          0          0     GICv2 163 Level     zynqmp-dma
 28:          0          0          0          0     GICv2 109 Level     zynqmp-dma
 29:          0          0          0          0     GICv2 110 Level     zynqmp-dma
 30:          0          0          0          0     GICv2 111 Level     zynqmp-dma
 31:          0          0          0          0     GICv2 112 Level     zynqmp-dma
 32:          0          0          0          0     GICv2 113 Level     zynqmp-dma
 33:          0          0          0          0     GICv2 114 Level     zynqmp-dma
 34:          0          0          0          0     GICv2 115 Level     zynqmp-dma
 35:          0          0          0          0     GICv2 116 Level     zynqmp-dma
 37:       2432          0          0          0     GICv2  91 Level     eth0, eth0
 39:         36          0          0          0     GICv2  49 Level     cdns-i2c
 40:          0          0          0          0     GICv2  50 Level     cdns-i2c
 41:          0          0          0          0     GICv2  42 Level     ff960000.memory-controller
 42:          0          0          0          0     GICv2  57 Level     axi-pmon, axi-pmon
 43:          0          0          0          0     GICv2  58 Level     ffa60000.rtc
 44:          0          0          0          0     GICv2  59 Level     ffa60000.rtc
 45:      30636          0          0          0     GICv2  80 Level     mmc0
 46:        368          0          0          0     GICv2  51 Level     ff040000.spi
 47:         83          0          0          0     GICv2  52 Level     ff050000.spi
 48:       1618          0          0          0     GICv2  54 Level     xuartps
 49:          0          0          0          0     GICv2  88 Level     ams-irq
 50:          0          0          0          0     GICv2 154 Level     fd4c0000.dma-controller
 51:          0          0          0          0     GICv2 151 Level     fd4a0000.display
 53:          0          0          0          0     GICv2  97 Level     dwc3
 55:          0          0          0          0     GICv2 107 Level     usb-wakeup, usb-wakeup
 56:         71          0          0          0     GICv2 102 Level     xhci-hcd:usb1
 64:          3          0          0          0     GICv2 127 Level     xilinx-dma-controller
 65:          0          0          0          0     GICv2 128 Level     xilinx-dma-controller
 66:          0          0          0          0     GICv2 123 Level     xilinx-dma-controller
 67:          3          0          0          0     GICv2 124 Level     xilinx-dma-controller
IPI0:       336        610        634        501       Rescheduling interrupts
IPI1:      7031      12775       9743      14395       Function call interrupts
IPI2:         0          0          0          0       CPU stop interrupts
IPI3:         0          0          0          0       CPU stop (for crash dump) interrupts
IPI4:         0          0          0          0       Timer broadcast interrupts
IPI5:         0          0          0          0       IRQ work interrupts
IPI6:         0          0          0          0       CPU wake-up interrupts

How many interrupts occurred can be monitored here on the CPU0 column.

📻