前言
最近公司项目中使用到了赛灵思Xilinx zynq7000系列(7030)平台,产品中需要在arm(PS)与fpga(PL)之间进行大批量数据传输,还要考虑效率问题,因此使用了axi_dma模块,我用的xilinx工具(xilinx-sdk)以及组件(u-boot、kernel)都是2018.2版本。
驱动选择
方式一:
采用proxy-dma驱动,官方wiki代码链接如下:
不过没提供makefile来生成ko文件,我自己简单写了一个,供参考:
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
KERNEL_DIR=xxx/linux-xlnx(这里指定内核源码根目录路径)
all:
make -C $(KERNEL_DIR) M=`pwd` modules
clean:
rm -rf *.o *.cmd *.order Module.symvers *.ko *.mod* ./.* .tmp_versions
obj-m += dma-proxy.o
使用方法很简单,先修改设备树,注意,驱动代码中固定dma-names名称为dma_proxy_rx和dma_proxy_tx,因此不能随意修改,且驱动初始化中默认同时开启rx和tx通道,因为,你如果不是成对使用,需要注释掉驱动中多余通道的初始化代码:
dma_proxy {
compatible ="xlnx,dma_proxy";
dmas = <&S2MM_controller_axi_dma_0 0 &S2MM_controller_axi_dma_0 1>;
dma-names = "dma_proxy_rx dma_proxy_tx";
};
dma设备树相关(S2MM_controller_axi_dma_0)我就没写了,这个可以通过xilinx-sdk工具生成。
系统起来后insmod ko文件,能在/dev目录下得到dma_proxy_rx和dma_proxy_tx驱动设备文件,后面直接参考dma-proxy-test.c文件中的用法进行使用就行了。
方法一我已经用过了,可以用,不过这种方式对于大批量数据的搬运效率如何我没有测试。
方式二:
采用 github上大神开发的axidma驱动,链接如下:
https://github.com/bperez77/xilinx_axidma
公司产品最终使用了就是这个驱动,使用步骤如下:
1、修改设备树
axidma_chrdev: axidma_chrdev@0 {
compatible = "xlnx,axidma-chrdev";
dmas = <&MM2S_controller_axi_dma_1 0 &MM2S_controller_axi_dma_1 1>;
dma-names = "rx_channel", "tx_channel";
};
然后在axi_dma的设备树下面添加字段:
xlnx,device-id = <0x0>和xlnx,device-id = <0x1>,如果使用了多组dma,那么 其它device-id 依次为 0x2、0x3、0x4... ...
2、修改cma大小
由于该驱动在初始化时会malloc大量连续内存(大小依据业务需要确定),我在业务中dma_malloc了256MB,所以启动参数bootargs中添加的是cma=300M
3、驱动编译
注意,编译驱动前,需要先编译linux内核,此步骤省略。
然后进入axi_dma驱动源码目录编译ko:
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm KBUILD_DIR=../ driver
注意,我将axi_dma目录放在了kernel源码的根目录下,所以KBUILD_DIR指定的是 ../,KBUILD_DIR的意思就指定编译后的内核源码根目录在什么地方。
编译axi_dma动态库,该库在用户空间使用:
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm library
4、驱动使用
系统上电后,如果dts配置正确,内核启动会有如下类似打印:
dma-pl330 f8003000.dmac: DBUFF-128x8bytes Num_Chans-8 Num_Peri-4 Num_Events-16
xilinx-vdma 40400000.dma: Xilinx AXI DMA Engine Driver Probed!!
xilinx-vdma 40410000.dma: Xilinx AXI DMA Engine Driver Probed!!
insmod axidma驱动后,正常情况下能在/dev目录下看到axidma设备,且打印:
axidma: axidma_dma.c: axidma_dma_init: 721: DMA: Found 1 transmit channels and 1 receive channels.
axidma: axidma_dma.c: axidma_dma_init: 723: VDMA: Found 0 transmit channels and 0 receive channels.
没这个打印或者dev下没有设备,基本就是设备树配置问题了,设备树稍微配置错了一点就会导致这个问题,如果你出现了这个问题,请好好检查设备树文件。
驱动加载成功后就是使用库文件了,参考源码目录下的examples目录下的文件进行使用就好了,有阻塞方式和回调方式可选择,阻塞时给定超时时间,非阻塞时先调用axidma_set_callback设置回调并且超时时间给0,回调中可再次调用transfer接口,这样就能重复达到回调目的了(例如循环处理命令的模式,我最终用的就是这种方式)。
其它说明
1、transfer接口中给定的地址必须是先根据axidma_malloc或者axidma_register_buffer得到的,不能是其它形式;
2、transfer 接口中的channel值,是根据设备树配置来的,比如id依次是0(rx1) 1(tx1) 2(rx2) 3(tx2),那么,axidma_get_dma_tx和axidma_get_dma_rx返回的数组依次就是tx1、tx2和rx1、rx2。
3、如果正常使用axi_dma的用户库接口且数据收/发正常时,内核会有类似打印:
xilinx-vdma 40400000.dma: Channel ef18ae10 has errors 10, cdr 0 tdr 0
这个问题官方在某个issue中回复过了,可以忽略,暂未定位原因。
4、每次完成传输,linux系统上其实能查到中断次数:
root-~# cat /proc/interrupts
CPU0 CPU1
16: 1 0 GIC-0 27 Edge gt
17: 0 0 GIC-0 43 Level ttc_clockevent
18: 10542244 9262181 GIC-0 29 Edge twd
19: 0 0 GIC-0 37 Level arm-pmu
20: 0 0 GIC-0 38 Level arm-pmu
21: 43 0 GIC-0 39 Level f8007100.adc
23: 0 0 GIC-0 57 Level cdns-i2c
24: 84 0 GIC-0 80 Level cdns-i2c
26: 0 0 GIC-0 35 Level f800c000.ocmc
27: 42 0 GIC-0 82 Level xuartps
28: 9 0 GIC-0 51 Level e000d000.spi
29: 153829207 0 GIC-0 54 Level eth0
30: 0 0 GIC-0 45 Level f8003000.dmac
31: 0 0 GIC-0 46 Level f8003000.dmac
32: 0 0 GIC-0 47 Level f8003000.dmac
33: 0 0 GIC-0 48 Level f8003000.dmac
34: 0 0 GIC-0 49 Level f8003000.dmac
35: 0 0 GIC-0 72 Level f8003000.dmac
36: 0 0 GIC-0 73 Level f8003000.dmac
37: 0 0 GIC-0 74 Level f8003000.dmac
38: 0 0 GIC-0 75 Level f8003000.dmac
39: 0 0 GIC-0 40 Level f8007000.devcfg
45: 0 0 GIC-0 41 Edge f8005000.watchdog
47: 1 0 GIC-0 64 Edge uio0
48: 0 0 GIC-0 65 Edge uio1
49: 27267328 0 GIC-0 61 Level xilinx-dma-controller
50: 129024 0 GIC-0 66 Level xilinx-dma-controller
如上面的中断号为61和66的就是dma控制器收、发的中断次数,分别是 27267328次和 129024次。
5、也可以不成对使用dma通道,比如只是用1个rx或tx通道或者共使用2个tx和1个rx通道,总之,按需配置dts就行(已亲测可用)。
后续有想到的我再添加吧!