目录

  • 概括
  • 一、FPGA部分关键点
  • 二、ARM部分的关键配置
    • 1.设备树
    • 2.AXI-DMA 应用层 驱动
    • 3.应用层编程
  • 总结


概括

平台为 ZYNQ MPSOC
项目使用到AXI-DMA ,ADC模块传输数据到DDR,应用层进行数据的读取。在此做些记录
用到了AXI-Stream , IP核用的 米联客的ui_axisbufw,可以把流数据转为AXI-Stream 接口

比较重要的参考链接
1.UltraScale+MPSoC+Cache+Coherency
https://xilinx-wiki.atlassian/wiki/spaces/A/pages/18842098/Zynq+UltraScale+MPSoC+Cache+Coherency
2.MPSoC逻辑加速模块数据通道快速设计
https://cloud.tencent/developer/article/1663578
3.git上开源的 axi-dma Linux 驱动, 实现零拷贝
https://github/bperez77/xilinx_axidma
4.
Petalinux 加速axi-dma内核驱动缓冲区读过程

一、FPGA部分关键点

1.AXI-DMA IP核的配置,配置好一次最大传输字节数,这里没有使用SG模式,一次最大传输字节数设置为了2的26次方也就是一次DMA最大传输64MB数据,地址宽度设置为64bit,如果设置为32bit 可能会出现DMA 访问不了 ARM 的 另一块内存的问题。(当然Linux cma内存预留的时候 可以指定到32位地址空间)

测试发现使用 bperez77/xilinx_axidma 的内核驱动, 开启SG模式,会有接收错误,关闭SG模式,正常,问题还待解决(当然不解决也能用哈哈)。
这里只使能了 DMA 写入通道,也就是从PL发送到PS的DDR, 也就是S2MM(stream to mem map)。

2.AXI-DMA 与 ARM 连接

这里最好是使用AXI SmartConnect 进行连接, 以减少出错的可能。ARM端使用HPC0进行连接
3.AXCACHE 与AXPROT
这一部分涉及 Cache 同步问题, 也就是FPGA 刷新 应用DDR内存的问题, FPGA 刷新完 内存, CPU可能不知道,CPU再次读的时候有可能Cache命中 读取到了旧数据。
这部分主要是为了cache 同步,测试发现这个最好是做,当然不做cache 同步 也好像没啥问题, 但是 可能会突然给你点儿小惊喜。
cache 同步需要 比较繁杂的配置,参考官方wiki 的8.2


主要是配置这4个
①PL部分配置 AXCACHE 设置 0xF
②AxPORT 配置为 0x02,也就是第2bit 为1 ,不安全模式
③是能cache侦听, 需要配合vitis,在 FSBL部分进行处理
④设备树增加 dma-coherent
这几步可以参考 一开始提到的 MPSoC逻辑加速模块数据通道快速设计

二、ARM部分的关键配置

1.设备树

设备(示例):
这里需要注意 dma-coherent; 这个属性得加一下,然后axi_dma 部分参考 vitis 生成的就行。
a0000000 为 ip和分配的地址,需要对应起来。

	/* AXI-DMA */
	axidma_chrdev: axidma_chrdev@0 {
		#dma-cells = <1>;
		compatible = "xlnx,axidma-chrdev";
		dmas       =  <&axi_dma_0 0 &axi_dma_0 1>;
		dma-names  = "tx_channel", "rx_channel";
		dma-coherent;
	};

	axi_dma_0: dma@a0000000 {
		status = "okay";
		#dma-cells = <1>;
		clock-names = "s_axi_lite_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk", "m_axi_sg_aclk";
		clocks = <&zynqmp_clk 71>, <&zynqmp_clk 71>, <&zynqmp_clk 71>, <&zynqmp_clk 71>;
		compatible = "xlnx,axi-dma-1.00.a";
		interrupt-names = "mm2s_introut", "s2mm_introut";
		interrupt-parent = <&gic>;
		interrupts = <0 91 4>, <0 92 4>;
		reg = <0x0 0xa0000000 0x0 0x10000>;
		xlnx,addrwidth = <0x40>;//64bit addr 
		xlnx,sg-length-width = <0x1A>;//buffer len    26 bit 64M  <0x17>;//buffer len  23 bit 8M
		dma-coherent;
		dma-channel@a0000000 {
			compatible = "xlnx,axi-dma-mm2s-channel";
			dma-channels = <0x1>;
			interrupt-parent = <&gic>;
			interrupts = <0 91 4>;
			xlnx,datawidth = <0x80>;//data width 128
			xlnx,device-id = <0x0>;
		};
		
		dma-channel@a0000030 {
			compatible = "xlnx,axi-dma-s2mm-channel";
			dma-channels = <0x1>;
			interrupt-parent = <&gic>;
			interrupts = <0 92 4>;
			xlnx,datawidth = <0x80>;//data width 128
			xlnx,device-id = <0x1>;
		};
		
	};

2.AXI-DMA 应用层 驱动

git 上 大佬做的 xilinx_axidma 驱动 , 以下可能需要修改的地方,因为我的内核是4.19。
of_dma_configure的第一个参数 不要搞错了, 不然 会出现 应用层读buf 缓慢的现象,当时博主也是找问题找了好久。
剩下的就是 可能要包含以下 of 的头文件了。


注意!!!不要按上述的patch 去修改 ,否则会有问题!!!!DMA mask not set 这个log
好像没啥影响。

3.应用层编程

应用层主要使用的api 可以参考 xilinx_axidma 的demo,主要用到了以下的API

    axidma_init(); //初始化 axi dma 设备,获取fd
    axidma_malloc(axidma_dev, tx_size);//这里会做内存映射,映射cma 内存到用户空间
    axidma_oneway_transfer(dev, rx_channel, rx_buf, rx_size, true);//单向传输,可以发送、接收
    axidma_free(axidma_dev, tx_buf, tx_size);//释放内存映射,也就是cma 内存
    axidma_destroy(axidma_dev);//关闭设备

总结

1.AXI-DMA 个人理解为PL到PS交互提供了一个桥梁,如果只是控制寄存器的话,使用AXI-Lite就可以,AXI-Stream 适合数据流交互(PL2PS PS2PL 都是互通的)。

2.xilinx 虽然做了axi-dma 的驱动,但是为应用层提供接口的驱动还不是很完善(虽然wiki上有xilinx官方开源的)。

3.使用 https://github/bperez77/xilinx_axidma 这个axidma 可以很方便的实现 应用层的dma 数据零拷贝,直接dma 到 内核的CMA 内存区域,其实这个CMA内存也是在DDR上,零拷贝的实现是通过内核空间内存映射实现的,所以是0拷贝,最终测试下来 内存的读写速度和读写DDR基本一致。

在其中mmap 函数在其中发挥了很大作用,用户层调用mmap,转到驱动的 mmap,驱动的mmap 拿到用户空间的地址和大小之后 对内核的 cma 内存进行dma 映射。

4.Cache一致 配置的可能稍微复杂一点儿,但是 最终做下来 也能达到相应的效果,并且能对 ZYNQ这个 芯片架构更了解一些。关于cache 同步可以参考这个
关于linux内存管理中DMA ZONE和dma_alloc_coherent若干误解的澄清

更多推荐

ZYNQ AXI-DMA Linux Cache 一致