文章目录
之前,我在博客中分享了如何zynq7020采用GitHub的开源linux DMA驱动的移植,并完成了示例程序的测试。但其中主要为单次DMA的传输,即传输完成一次后需要手动再次配置并启动传输,但在一些特定场景下,我们需要连续的DMA传输。比如你要通过天线发射连续的脉冲,或者后续FPGA需要进行实时的处理,这自然是不允许数据流出现间隙的。这就需要采用我们的DMA循环传输的功能。
PL端
需要勾选Enable Scatter Gather Engine。
循环模式需要硬件DMA的SG模式配合
先暂时不要回环
正常的回环是PS DDR --> DMA --> PL FIFO -->DMA --> PS DDR
如果正常使用该回环,对后续CPU接收数据等等操作的调试要求较高,处理不好可能会引发内核的抱怨还是啥(笔者尚且没有解决)
因此我们从中打断这个回环,FIFO的输出端写一个程序专门用来接收数据,READY信号始终拉高,接收数据后直接丢弃数据
添加中断
记得加上concast,要有中断才行,参考Zynq linux加载axi_dma驱动报错 axidma: axidma_dma.c: axidma_request_channels: 651: Unable to get slave chan
软件源码分析与修改
axidma_video_transfer
实际上,软件源码中给出了video循环传输的功能,如果你是视频、图像传输,理论上可以直接调用这个,按照其注释的功能说明,其可以实现循环传输。但可能需要你把硬件端配置为VDMA,且传输的数据有图像那样子的三维数据。
笔者需要做的是天线信号的循环传输, 用的主要还是DMA。所以决定自己修改源码来适配DMA的循环传输功能。
应用层源码修改
后续的内容都将以后缀 circle 来表征我们修改的部分,为了不破坏原本的结构,都将原函数复制一个。
library/libaxidma.c
这里的ioctl的命令也要加上这个后缀,我们后续对让底层文件也支持这个命令
include/libaxidma.h
在这个文件下也要添加对这个函数的声明,这样子其他函数才能调用这个
include/axidma_ioctl.h
这个文件下 AXIDMA_NUM_IOCTLS定义了驱动向上提供的IOCTL命令的个数,因为我们多加了一个命令,所以需要增加这个宏定义的值,让他支持更多的IOCTL命令。
如果不添加支持的IOCTL命令个数,报错如下:“IOCTL command is out of range for device”
因为我们这个命令是参照着AXI_DMA_READWRITE写的,可以直接把对这个命令的定义放在其下面,编号要顺着最后的写,这里为“11”.
驱动层源码修改
driver/axidma_chrdev.c
这里面接收前文提到的 library/libaxidma.c 的命令,在其中对ioctl命令的switch case语句下添加对我们新增的命令的支持
(library/libaxidma.c是应用层的程序,其发送的命令会通过内核给到驱动程序)
driver/axidma_dma.c
(driver/axidma_chrdev.c 主要是判断命令类别,更底层的函数实现封装在 driver/axidma_dma.c 中)
同样的,也是添加后缀
备份一份axidma_prep_transfer函数,修改其中一份为axidma_prep_transfer_circle
这里需要调用另外一个api,dmaengine_prep_dma_cyclic
内核源码程序中我们可以找到这个API函数
上图截图自DMA engine 笔记
接下来,我们了解一下这个API需要的参数
dmaengine_prep_slave_sg
- struct dma_chan *chan :DMA 通道结构体指针。
- struct scatterlist *sgl :分散列表结构体指针。
- unsigned int sg_len :分散列表的长度。
- enum dma_transfer_direction dir :DMA 传输方向枚举值。
- unsigned long flags :标志位。
dmaengine_prep_dma_cyclic
- struct dma_chan *chan :DMA 通道结构体指针。
- dma_addr_t buf_addr :缓冲区地址。
- size_t buf_len :缓冲区长度。
- size_t period_len :周期长度。每隔多久(单位为byte)调用一次回调函数。需要注意的是,buf_len应该是period_len的整数倍。
- enum dma_transfer_direction dir :DMA 传输方向枚举值。
- unsigned long flags :标志位。
根据参数定义对源码进行修改,
// 计算缓冲区地址,这里简单假设从第一个scatterlist元素获取
dma_addr_t buf_addr = sg_dma_address(&sg_list[0]);
// 假设缓冲区长度为所有scatterlist元素长度之和,这里简单示意
size_t buf_len = 0;
size_t period_len = 0;
int i = 0;
for (i = 0; i < sg_len; i++) {
buf_len += sg_dma_len(&sg_list[i]);
}
// 假设周期长度为缓冲区长度,实际需根据业务确定
size_t period_len = buf_len;
dma_txnd = dmaengine_prep_dma_cyclic(chan, buf_addr, buf_len, period_len, dma_dir, dma_flags);
中断的修改(如果有必要)
driver/axidma.h
与应用层的操作一样,我们添加了一个函数,需要在头文件中添加其声明
应用层APP
改为调用该函数
修改Makefile文件
实验现象
单次传输,开源代码写的完全没有间隙,牛
我们修改的循环传输(存在间隙)
要实现真正的数据连续,还需要适当降低FIFO输出端的时钟信号,以此消除间隙。
其他注意事项
PL的数据位宽,尽量不要有带宽合成
拉低时钟信号用cloking wizard配置,不要用PS端IP那个PL clock直接输出(笔者测试出来这个时钟好像不能用)