【JokerのZYNQ7020】LINUX_AXI_DMA。
软件环境:vivado 2017.4 硬件平台:XC7Z035
这篇吧主要哔哔一下在linux下,怎么使用axi_dma,这里说的使用,是指的应用层面的使用,而不是驱动层的axi_dma在linux下的驱动,至于为什么不按照一般流程,把vivado底层模块写成linux下的驱动,一方面水平还不够,另一方面,直接在应用层上用能达到一样的目的,方法还简单粗暴。
这里需要注意一下,在这个底图上,我用来做axi_dma实验的axi_dma_0的两根中断线mm2s_introut和s2mm_introut是没接的,为什么不接,先看device tree,打开pl.dtsi,看axi_dma_0的那部分。
axi_dma_0: [email protected] {
#dma-cells = <1>;
clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
clocks = <&clkc 15>, <&clkc 15>, <&clkc 15>;
compatible = "xlnx,axi-dma-1.00.a";
reg = <0x40400000 0x10000>;
xlnx,addrwidth = <0x20>;
xlnx,sg-length-width = <0x17>;
[email protected] {
compatible = "xlnx,axi-dma-mm2s-channel";
dma-channels = <0x1>;
xlnx,datawidth = <0x20>;
xlnx,device-id = <0x0>;
};
[email protected] {
compatible = "xlnx,axi-dma-s2mm-channel";
dma-channels = <0x1>;
xlnx,datawidth = <0x20>;
xlnx,device-id = <0x0>;
};
};
注意这里的compatible = “xlx,axi-dma-1.00.a”,这句话的意思是在内核加载驱动的时候,会给axi_dma_0加载名为xlx,axi-dma-1.00.a的驱动,再看下内核。在Device Drivers----->DMA Engine support下面可以看到,Xilinx相关的DMA驱动默认是使能的。
至于驱动文件具体的位置,在内核包kernel/drivers/dma/xilinx/xilinx_dma.c
这里的.compatible = "xlnx,axi-dma-1.00.a"就是和设备树里的.compatible = "xlnx,axi-dma-1.00.a"相互对应的,在编译内核时候,将xilinx dma驱动勾选,系统加载启动以后设备树中axi_dma_0使用的就是kernel中的xilinx_dma.c生成的驱动。
这里有必要再细说一下,按正常的路数,应该是在vivado中将axi_dma_0中的两根中断线连上,然后device tree中正常包含.compatible = "xlnx,axi-dma-1.00.a",kernel配置的时候正常勾选xilinx dma驱动,系统启动以后,axi_dma驱动正常加载,然后编写上层应用调用底层axi_dma驱动,以中断的方式触发dma传输。但这种在linux下调用axi_dma驱动的资料比较少,像我这种底子不扎实的开发有难度,而且周期有点长,所以这种就不说了,下面说下我前面说的应用层直接使用是怎么弄的。
应用层直接使用的话,中断用的是寄存器的轮询方式,不是触发中断然后调用中断处理函数的方式,这是首先需要说明的。其次,为了保证中断寄存器可以正确的轮询,所以就要避免调用内核预先配置好的xilinx_dma驱动,这里避免的方法主要有三种,其一是去掉axi_dma_0的两根中断线,其二是device tree中去掉.compatible = "xlnx,axi-dma-1.00.a",其三是配置kernel的时候去掉xilinx dma驱动的使能,这三种任意一种都可以,如果不这样做的话,系统启动后加载xilinx dma的驱动以后,只要dma中断触发,中断寄存器就会自动清零,换句话说,中断寄存器都自动清零了,你还轮询个啥子?
接下来贴一下测试代码,使用mmap方式,做内存映射,将axi_dma的地址映射为虚拟地址,通过操作虚拟地址和寄存器,来达到控制axi_dma通信的目的。测试时先将0x11223344写入源地址0x0e000000,而后将源地址的值通过axi_dma发送出去,经过loop再接收到目的地址0x0f000000,最后比较发送的数据和接收的数据是否一致。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/mman.h>
#define MM2S_CONTROL_REGISTER 0x00
#define MM2S_STATUS_REGISTER 0x04
#define MM2S_START_ADDRESS 0x18
#define MM2S_LENGTH 0x28
#define S2MM_CONTROL_REGISTER 0x30
#define S2MM_STATUS_REGISTER 0x34
#define S2MM_DESTINATION_ADDRESS 0x48
#define S2MM_LENGTH 0x58
unsigned int dma_set(unsigned int* dma_virtual_address, int offset, unsigned int value) {
dma_virtual_address[offset>>2] = value;
}
unsigned int dma_get(unsigned int* dma_virtual_address, int offset) {
return dma_virtual_address[offset>>2];
}
int dma_mm2s_sync(unsigned int* dma_virtual_address) {
unsigned int mm2s_status = dma_get(dma_virtual_address, MM2S_STATUS_REGISTER);
while(!(mm2s_status & 1<<12) || !(mm2s_status & 1<<1) ){
dma_s2mm_status(dma_virtual_address);
dma_mm2s_status(dma_virtual_address);
mm2s_status = dma_get(dma_virtual_address, MM2S_STATUS_REGISTER);
}
}
int dma_s2mm_sync(unsigned int* dma_virtual_address) {
unsigned int s2mm_status = dma_get(dma_virtual_address, S2MM_STATUS_REGISTER);
while(!(s2mm_status & 1<<12) || !(s2mm_status & 1<<1)){
dma_s2mm_status(dma_virtual_address);
dma_mm2s_status(dma_virtual_address);
s2mm_status = dma_get(dma_virtual_address, S2MM_STATUS_REGISTER);
}
}
void dma_s2mm_status(unsigned int* dma_virtual_address) {
unsigned int status = dma_get(dma_virtual_address, S2MM_STATUS_REGISTER);
printf("Stream to memory-mapped status (0x%[email protected]%02x):", status, S2MM_STATUS_REGISTER);
if (status & 0x00000001) printf(" halted"); else printf(" running");
if (status & 0x00000002) printf(" idle");
if (status & 0x00000008) printf(" SGIncld");
if (status & 0x00000010) printf(" DMAIntErr");
if (status & 0x00000020) printf(" DMASlvErr");
if (status & 0x00000040) printf(" DMADecErr");
if (status & 0x00000100) printf(" SGIntErr");
if (status & 0x00000200) printf(" SGSlvErr");
if (status & 0x00000400) printf(" SGDecErr");
if (status & 0x00001000) printf(" IOC_Irq");
if (status & 0x00002000) printf(" Dly_Irq");
if (status & 0x00004000) printf(" Err_Irq");
printf("\n");
}
void dma_mm2s_status(unsigned int* dma_virtual_address) {
unsigned int status = dma_get(dma_virtual_address, MM2S_STATUS_REGISTER);
printf("Memory-mapped to stream status (0x%[email protected]%02x):", status, MM2S_STATUS_REGISTER);
if (status & 0x00000001) printf(" halted"); else printf(" running");
if (status & 0x00000002) printf(" idle");
if (status & 0x00000008) printf(" SGIncld");
if (status & 0x00000010) printf(" DMAIntErr");
if (status & 0x00000020) printf(" DMASlvErr");
if (status & 0x00000040) printf(" DMADecErr");
if (status & 0x00000100) printf(" SGIntErr");
if (status & 0x00000200) printf(" SGSlvErr");
if (status & 0x00000400) printf(" SGDecErr");
if (status & 0x00001000) printf(" IOC_Irq");
if (status & 0x00002000) printf(" Dly_Irq");
if (status & 0x00004000) printf(" Err_Irq");
printf("\n");
}
void memdump(void* virtual_address, int byte_count) {
char *p = virtual_address;
int offset;
for (offset = 0; offset < byte_count; offset++) {
printf("%02x", p[offset]);
if (offset % 4 == 3) { printf(" "); }
}
printf("\n");
}
int main() {
int dh = open("/dev/mem", O_RDWR | O_SYNC); // Open /dev/mem which represents the whole physical memory
unsigned int* virtual_address = mmap(NULL, 65535, PROT_READ | PROT_WRITE, MAP_SHARED, dh, 0x40400000); // Memory map AXI Lite register block
unsigned int* virtual_source_address = mmap(NULL, 65535, PROT_READ | PROT_WRITE, MAP_SHARED, dh, 0x0e000000); // Memory map source address
unsigned int* virtual_destination_address = mmap(NULL, 65535, PROT_READ | PROT_WRITE, MAP_SHARED, dh, 0x0f000000); // Memory map destination address
virtual_source_address[0]= 0x11223344; // Write random stuff to source block
memset(virtual_destination_address, 0, 32); // Clear destination block
printf("Source memory block: "); memdump(virtual_source_address, 32);
printf("Destination memory block: "); memdump(virtual_destination_address, 32);
printf("Resetting DMA\n");
dma_set(virtual_address, S2MM_CONTROL_REGISTER, 4);
dma_set(virtual_address, MM2S_CONTROL_REGISTER, 4);
dma_s2mm_status(virtual_address);
dma_mm2s_status(virtual_address);
printf("Halting DMA\n");
dma_set(virtual_address, S2MM_CONTROL_REGISTER, 0);
dma_set(virtual_address, MM2S_CONTROL_REGISTER, 0);
dma_s2mm_status(virtual_address);
dma_mm2s_status(virtual_address);
printf("Writing destination address\n");
dma_set(virtual_address, S2MM_DESTINATION_ADDRESS, 0x0f000000); // Write destination address
dma_s2mm_status(virtual_address);
printf("Writing source address...\n");
dma_set(virtual_address, MM2S_START_ADDRESS, 0x0e000000); // Write source address
dma_mm2s_status(virtual_address);
printf("Starting S2MM channel with all interrupts masked...\n");
dma_set(virtual_address, S2MM_CONTROL_REGISTER, 0xf001);
dma_s2mm_status(virtual_address);
printf("Starting MM2S channel with all interrupts masked...\n");
dma_set(virtual_address, MM2S_CONTROL_REGISTER, 0xf001);
dma_mm2s_status(virtual_address);
printf("Writing S2MM transfer length...\n");
dma_set(virtual_address, S2MM_LENGTH, 32);
dma_s2mm_status(virtual_address);
printf("Writing MM2S transfer length...\n");
dma_set(virtual_address, MM2S_LENGTH, 32);
dma_mm2s_status(virtual_address);
printf("Waiting for MM2S synchronization...\n");
dma_mm2s_sync(virtual_address);
printf("Waiting for S2MM sychronization...\n");
dma_s2mm_sync(virtual_address); // If this locks up make sure all memory ranges are assigned under Address Editor!
dma_s2mm_status(virtual_address);
dma_mm2s_status(virtual_address);
printf("Destination memory block: "); memdump(virtual_destination_address, 32);
}
测试程序中有几点需要说明:
第一,axi_dma的寄存器有哪些,偏移量各是多少,写入值的意义是啥,查手册!!!或者看我之前写的AXI_DMA_LOOP那篇,我有贴。
第二,只要axi_dma的length寄存器中写入大于0的数,则就立即启动了axi_dma的发送/接收,所以在这之前,一定要将发送数据的地址和接收数据的地址写入好,最后在给length寄存器写入数据的长度。
第三,这里的中断轮训是通过dma_mm2s_sync()和dma_s2mm_sync()这两个函数完成的,通过不断while寄存器标志位来查看当前是否有中断触发,不可避免的,这种方式效率比较低,但是方法比较简单,操作比较直接。
没错,上面就是测试结果啦。