【JokerのZYNQ7020】LINUX_AXI_DMA。

软件环境:vivado 2017.4        硬件平台:XC7Z035


这篇吧主要哔哔一下在linux下,怎么使用axi_dma,这里说的使用,是指的应用层面的使用,而不是驱动层的axi_dma在linux下的驱动,至于为什么不按照一般流程,把vivado底层模块写成linux下的驱动,一方面水平还不够,另一方面,直接在应用层上用能达到一样的目的,方法还简单粗暴。

【JokerのZYNQ7020】LINUX_AXI_DMA。

这里需要注意一下,在这个底图上,我用来做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驱动默认是使能的。

【JokerのZYNQ7020】LINUX_AXI_DMA。

至于驱动文件具体的位置,在内核包kernel/drivers/dma/xilinx/xilinx_dma.c

【JokerのZYNQ7020】LINUX_AXI_DMA。

这里的.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寄存器标志位来查看当前是否有中断触发,不可避免的,这种方式效率比较低,但是方法比较简单,操作比较直接。

【JokerのZYNQ7020】LINUX_AXI_DMA。

没错,上面就是测试结果啦。