Configure AXI DMA in Kernel Module
Last updated
Last updated
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.
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
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
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.
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.
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
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
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.
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.
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
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
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.