Xilinx-Axi-dma驱动
前言
本篇文章基于赛灵思的xczu7ev核心作为实验对象,由于项目要求只做从PL端自定义的逻辑IP核把数据通过DMA搬运到PS端的内存。只有接收通道,当然也发送通道与接收的通道原理一致。文章前面对赛灵思的DMA控制器原理进行简单的介绍,后面为Xlinx Linux DMA的函数使用进行讲解。
本文涉及一点FPGA基础和一点点Linux的知识。
Author:Swing
Date:2023/12/23
参考
参考链接:
https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/1027702787/Linux+DMA+From+User+Space+2.0
https://www.kernel.org/doc/Documentation/DMA-API.txt
本地文本为参考内核文档:
./Documentation/driver-api/dmaengine/client.rst
./Documentation/driver-api/dmaengine/provider.rst
1. DMA控制器
1.1. DMA控制器介绍
描述:这个没什么好说的,网上的相关介绍太多。简单的一句话,DMA控制器不占用CPU的资源,可以直接从相关的外设控制器搬运到内存或者内存的数据搬运到相关外设控制器。例如常见的:SPI DMA,UART DMA,VIDIO DMA,Camera DMA。
1.2. DMA控制数据传输原理
描述:以Vivado2022.1版本,基于AXI DMA的IP核原型进行原理讲解。
1.2.1. AXI总线
描述:可以看到1.1的图控制器之间通过AXI进行连接通讯。AXI是一种通讯的接口,类似与SPI,I2C,UART,MIPI,HDMI。但是与他们不同的是AXI是在芯片内部进行通讯,所以你想看到他的通讯时序只能通过仿真的方式进行查看。这里不讲解AXI总线的通讯时序,有兴趣可以看Xilinx官网对AXI总线时序的讲解文章。
用我的一句话总结:AXI分为Master_AXI和Slave_AXI两种,Master_AXI能传输数据的同时具有寻址能力。是AXI总线是一个同步通信接口。如何寻址呢?C语言void * point = 0x …;寻已经映射出来的物理地址,当然要AXI有接到的器件并支持可映射。
链接:https://docs.xilinx.com/v/u/en-US/ug1037-vivado-axi-reference-guide
1.2.2. Slave DMA控制器数据传输
描述:DMA作为slave端,也就是使用发送通道为例,是通过AXI总线进行寻址,寻址后待握手成功后发送数据。
1.2.3. Master DMA控制器数据传输
描述:DMA作为Master端,往PS端(Slave)传输数据。
2. Vivado部分
2.1. 简单介绍
本文章使用DMA Master往PS端的内存进行写数据。首先是自定义的IP核通过AXI往DMA控制传输数据。
然后DMA通过AXI对PS端传输数据。
2.2. PS端如何对DMA控制器进行配置(额外篇)
描述:通过AXI LITE总线
2.2. 注意事项
注意1:PS端到DMA控制器的传输不管是发送还是接收,都是由PS端的AXI LITE对DMA控制器发起的传输通知。
注意2:描述因为PS端跑的Linux系统是具备64位寻址,寻址范围能力大于32位的4G。
所以DMA控制器的寻址宽度需要注意,DMA寻址是寻真实的物理地址,设置32 Bit宽度的话寻址范围只有0~4G。
3. Petalinux部分
3.1. Linux的DMA子系统
描述:这里是Linux的DMA控制器子系统的框图。对Linux的DMA控制器更多的介绍建议参考Linux的说明文档。
说明文档位于内核源码,路径:./Documentation/driver-api/dmaengine/client.rst 和 ./Documentation/driver-api/dmaengine/provider.rst
在Linux的DMA子系统中,是一个发起一个异步传输的方式,类似于SPI的。
在发起传输完成后通过自己设置的回调函数,回调函数触发则完成。一般使用
3.2. Xilinx DMA核心层
源码路径位于:drivers/dma/xilinx/xilinx_dma.c
可以看到核心层对DMA控制器的进行配置的函数封装。
例如在我们调用相关结构体内的函数(device_alloc_chan_resources)指针时候,函数指针指向了xilinx_dma_alloc_chan_resources。
由于API众多,这里核心层是xilinx芯片官方适配的,我们不需要去改动什么。只是进行简单的介绍。
DMA的模式分为三种:
- dma_cyclic
- slave_sg
- interleaved_dma
对于这三种模式的函数适配在xilinx_dma.c内
对于不同传输类型的DMA(AXI DMA ,CMDA,AXI MCMDA)的传输适配做成了不同的函数。
对于这三种函数在内核文档有详细的描述
vi ./Documentation/driver-api/dmaengine/client.rst +76
赛灵思的发起通讯传输只能调用device_prep_slave_sg或device_prep_dma_cyclic。
3.3. Xlinx DMA Client
描述:DMA的传输在单通道模式下(非SG模式),需要一段连续的物理内存。
Linux下使用DMA的流程如下:
如果不使用SG模式的话,只使用一个传输通道,则在提交传输地址时,第三个参数设置为1.
Client端代码需要自己完成,因为赛灵思对Client端好像比较自由?因为不同的方案对Client端的设计不一致,所以没有统一的client端?我也不是特别清楚,但是可以参考wiki xilinx axi DMA用户空间的说明。
参考网址:https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842418/Linux+DMA+From+User+Space
3.4. 相关API参考
static inline void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp)
/*
dev:是指向设备结构的指针,用于指定DMA的上下文信息。
size:是要分配的内存区域的大小(以字节为单位)。
dma_handle:是一个输出参数,用于获取分配内存区域的DMA物理地址。
flag:GFP(Get Free Pages)标志,用于内存分配时的行为控制。
*/
void sg_init_one(struct scatterlist *sg, const void *buf, unsigned int buflen);
/*
sg:需要初始化的 scatterlist 已经分配的结构体指针。
buf: 当前 scatterlist 描述的内存块的虚拟地址。
buflen: 当前 scatterlist 描述的内存块的大小(字节数)。
*/
dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx);
/*
这里建议看结构体dma_async_tx_descriptor
*/
struct dma_async_tx_descriptor {
struct list_head tx_list;
struct dmaengine_slave *chan;
dma_cookie_t cookie;
enum dma_async_tx_type tx_type;
enum dma_transfer_direction direction;
unsigned long flags;
void *callback_param;
dma_async_tx_callback callback;
};
/*
tx_list:用来将描述符与其他描述符连接在一起,以便进行链式操作。
chan:指向 DMA 通道的指针,表示异步传输将在该通道上执行。
cookie:用于标识异步传输操作的唯一 cookie 值。
tx_type:描述异步传输类型的枚举值,如 memcpy、xor、crc 等。
direction:描述数据传输方向的枚举值,如从内存到设备、从设备到内存等。
flags:用来设置一些标志位,以控制传输的特性。
callback_param:传递给回调函数的参数。
callback:回调函数,用于在传输完成时通知调用者。
*/
static inline void dma_async_issue_pending(struct dma_chan *chan)
/*
chan:指定 DMA 通道,用于向 DMA 引擎提交异步传输操作。
*/
3.5. 相关代码参考
编写Client端代码
参考1:
https://www.kernel.org/doc/Documentation/DMA-API.txt
参考2:
https://github.com/Xilinx-Wiki-Projects/software-prototypes
4. 调试手法
我建议从vivado设计完后,先使用vitis的仿真模式在裸机下验证一下。不然在Linux调不通找问题找半天。