Alex Falkovich
Published © GPL3+

AXI DMA interrupt with UIO driver in Embedded Linux

The project addresses AXI DMA IP interrupt related details with simple example using UIO driver to handle the interrupt via "blocking read"

IntermediateFull instructions provided2 hours186
AXI DMA interrupt with UIO driver in Embedded Linux

Things used in this project

Hardware components

MicroZed
Avnet MicroZed
×1
AMD Platform Cable USB II
×1
USB-A to Micro-USB Cable
USB-A to Micro-USB Cable
×1
MicroSD Card with Adapter
Digilent MicroSD Card with Adapter
×1

Software apps and online services

Vivado Design Suite
AMD Vivado Design Suite
PetaLinux
AMD PetaLinux
Petalinux 2023.1

Story

Read more

Schematics

mz7010_axi_dma.xpr.zip

Vivado project for microzed 7010 with AXI DMA loopback to the Zynq PS

Code

dmatest.c

C/C++
AXI DMA interrupts generating application with thread handling the interrupts
/*
* Copyright (C) 2013 - 2016  Xilinx, Inc.  All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL XILINX  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name of the Xilinx shall not be used
* in advertising or otherwise to promote the sale, use or other dealings in this
* Software without prior written authorization from Xilinx.
*
*/

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <termios.h>
#include <sys/mman.h>
#include <pthread.h>


#define MM2S_CONTROL_REGISTER       0x00
#define MM2S_STATUS_REGISTER        0x04
#define MM2S_SRC_ADDRESS_REGISTER   0x18
#define MM2S_TRNSFR_LENGTH_REGISTER 0x28

#define S2MM_CONTROL_REGISTER       0x30
#define S2MM_STATUS_REGISTER        0x34
#define S2MM_DST_ADDRESS_REGISTER   0x48
#define S2MM_BUFF_LENGTH_REGISTER   0x58

#define IOC_IRQ_FLAG                1<<12
#define IDLE_FLAG                   1<<1

#define STATUS_HALTED               0x00000001
#define STATUS_IDLE                 0x00000002
#define STATUS_SG_INCLDED           0x00000008
#define STATUS_DMA_INTERNAL_ERR     0x00000010
#define STATUS_DMA_SLAVE_ERR        0x00000020
#define STATUS_DMA_DECODE_ERR       0x00000040
#define STATUS_SG_INTERNAL_ERR      0x00000100
#define STATUS_SG_SLAVE_ERR         0x00000200
#define STATUS_SG_DECODE_ERR        0x00000400
#define STATUS_IOC_IRQ              0x00001000
#define STATUS_DELAY_IRQ            0x00002000
#define STATUS_ERR_IRQ              0x00004000

#define HALT_DMA                    0x00000000
#define RUN_DMA                     0x00000001
#define RESET_DMA                   0x00000004
#define ENABLE_IOC_IRQ              0x00001000
#define ENABLE_DELAY_IRQ            0x00002000
#define ENABLE_ERR_IRQ              0x00004000
#define ENABLE_ALL_IRQ              0x00007000


void *uio_thread(void *data)
{
     int fd = open("/dev/uio0",O_RDWR);

     int int32_tmp=1;

	if(fd){
		unsigned int uio_virtual_address = (unsigned int*) mmap(NULL, 0x10000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

		int32_tmp=1;
		write(fd, (void*)&int32_tmp, sizeof(int));
		while(1){
			if(read(fd,&int32_tmp,sizeof(int32_tmp))>=sizeof(int32_tmp)){
				printf("\n\n\n--------------------------UIO interrupt occcured %d-----------------!!!\n\n\n",int32_tmp);
			}
			int32_tmp=1;
			usleep(1000);
			write(fd, (void*)&int32_tmp, sizeof(int));
		}	
		munmap((unsigned int*)uio_virtual_address,0x10000);
     		close(fd);
	}
	
	return NULL;
}

unsigned int write_dma(unsigned int *virtual_addr, int offset, unsigned int value)
{
    virtual_addr[offset>>2] = value;

    return 0;
}

unsigned int read_dma(unsigned int *virtual_addr, int offset)
{
    return virtual_addr[offset>>2];
}

void dma_s2mm_status(unsigned int *virtual_addr)
{
    unsigned int status = read_dma(virtual_addr, S2MM_STATUS_REGISTER);

    printf("Stream to memory-mapped status (0x%08x@0x%02x):", status, S2MM_STATUS_REGISTER);

    if (status & STATUS_HALTED) {
		printf(" Halted.\n");
	} else {
		printf(" Running.\n");
	}

    if (status & STATUS_IDLE) {
		printf(" Idle.\n");
	}

    if (status & STATUS_SG_INCLDED) {
		printf(" SG is included.\n");
	}

    if (status & STATUS_DMA_INTERNAL_ERR) {
		printf(" DMA internal error.\n");
	}

    if (status & STATUS_DMA_SLAVE_ERR) {
		printf(" DMA slave error.\n");
	}

    if (status & STATUS_DMA_DECODE_ERR) {
		printf(" DMA decode error.\n");
	}

    if (status & STATUS_SG_INTERNAL_ERR) {
		printf(" SG internal error.\n");
	}

    if (status & STATUS_SG_SLAVE_ERR) {
		printf(" SG slave error.\n");
	}

    if (status & STATUS_SG_DECODE_ERR) {
		printf(" SG decode error.\n");
	}

    if (status & STATUS_IOC_IRQ) {
		printf(" IOC interrupt occurred.\n");
	}

    if (status & STATUS_DELAY_IRQ) {
		printf(" Interrupt on delay occurred.\n");
	}

    if (status & STATUS_ERR_IRQ) {
		printf(" Error interrupt occurred.\n");
	}
}

void dma_mm2s_status(unsigned int *virtual_addr)
{
    unsigned int status = read_dma(virtual_addr, MM2S_STATUS_REGISTER);

    printf("Memory-mapped to stream status (0x%08x@0x%02x):", status, MM2S_STATUS_REGISTER);

    if (status & STATUS_HALTED) {
		printf(" Halted.\n");
	} else {
		printf(" Running.\n");
	}

    if (status & STATUS_IDLE) {
		printf(" Idle.\n");
	}

    if (status & STATUS_SG_INCLDED) {
		printf(" SG is included.\n");
	}

    if (status & STATUS_DMA_INTERNAL_ERR) {
		printf(" DMA internal error.\n");
	}

    if (status & STATUS_DMA_SLAVE_ERR) {
		printf(" DMA slave error.\n");
	}

    if (status & STATUS_DMA_DECODE_ERR) {
		printf(" DMA decode error.\n");
	}

    if (status & STATUS_SG_INTERNAL_ERR) {
		printf(" SG internal error.\n");
	}

    if (status & STATUS_SG_SLAVE_ERR) {
		printf(" SG slave error.\n");
	}

    if (status & STATUS_SG_DECODE_ERR) {
		printf(" SG decode error.\n");
	}

    if (status & STATUS_IOC_IRQ) {
		printf(" IOC interrupt occurred.\n");
	}

    if (status & STATUS_DELAY_IRQ) {
		printf(" Interrupt on delay occurred.\n");
	}

    if (status & STATUS_ERR_IRQ) {
		printf(" Error interrupt occurred.\n");
	}
}

int dma_mm2s_sync(unsigned int *virtual_addr)
{
    unsigned int mm2s_status =  read_dma(virtual_addr, MM2S_STATUS_REGISTER);

	// sit in this while loop as long as the status does not read back 0x00001002 (4098)
	// 0x00001002 = IOC interrupt has occured and DMA is idle
	while(!(mm2s_status & IOC_IRQ_FLAG) || !(mm2s_status & IDLE_FLAG))
	{
        dma_s2mm_status(virtual_addr);
        dma_mm2s_status(virtual_addr);

        mm2s_status =  read_dma(virtual_addr, MM2S_STATUS_REGISTER);
    }

	return 0;
}

int dma_s2mm_sync(unsigned int *virtual_addr)
{
    unsigned int s2mm_status = read_dma(virtual_addr, S2MM_STATUS_REGISTER);

	// sit in this while loop as long as the status does not read back 0x00001002 (4098)
	// 0x00001002 = IOC interrupt has occured and DMA is idle
	while(!(s2mm_status & IOC_IRQ_FLAG) || !(s2mm_status & IDLE_FLAG))
	{
        dma_s2mm_status(virtual_addr);
        dma_mm2s_status(virtual_addr);

        s2mm_status = read_dma(virtual_addr, S2MM_STATUS_REGISTER);
    }

	return 0;
}

void print_mem(void *virtual_address, int byte_count)
{
	char *data_ptr = virtual_address;

	for(int i=0;i<byte_count;i++){
		printf("%02X", data_ptr[i]);

		// print a space every 4 bytes (0 indexed)
		if(i%4==3){
			printf(" ");
		}
	}

	printf("\n");
}

int main()
{
    pthread_t h_uio_thread;
    
    printf("Hello World! - Running DMA transfer test application.\n");

	printf("Opening a character device file of the Arty's DDR memeory...\n");
	int ddr_memory = open("/dev/mem", O_RDWR | O_SYNC);

	printf("Memory map the address of the DMA AXI IP via its AXI lite control interface register block.\n");
    unsigned int *dma_virtual_addr = mmap(NULL, 65535, PROT_READ | PROT_WRITE, MAP_SHARED, ddr_memory, 0x40400000);

	printf("Memory map the MM2S source address register block.\n");
    unsigned int *virtual_src_addr  = mmap(NULL, 65535, PROT_READ | PROT_WRITE, MAP_SHARED, ddr_memory, 0x0e000000);

	printf("Memory map the S2MM destination address register block.\n");
    unsigned int *virtual_dst_addr = mmap(NULL, 65535, PROT_READ | PROT_WRITE, MAP_SHARED, ddr_memory, 0x0f000000);

	printf("Writing random data to source register block...\n");
	virtual_src_addr[0]= 0xEFBEADDE;
	virtual_src_addr[1]= 0x11223344;
	virtual_src_addr[2]= 0xABABABAB;
	virtual_src_addr[3]= 0xCDCDCDCD;
	virtual_src_addr[4]= 0x00001111;
	virtual_src_addr[5]= 0x22223333;
	virtual_src_addr[6]= 0x44445555;
	virtual_src_addr[7]= 0x66667777;

	pthread_create(&h_uio_thread, NULL, uio_thread, NULL);

	while(1){
		printf("Clearing the destination register block...\n");
	    memset(virtual_dst_addr, 0, 32);


	    printf("Source memory block data:      ");
	    print_mem(virtual_src_addr, 32);
	
	    printf("Reset the DMA.\n");
	    write_dma(dma_virtual_addr, S2MM_CONTROL_REGISTER, RESET_DMA);
	    write_dma(dma_virtual_addr, MM2S_CONTROL_REGISTER, RESET_DMA);
	    dma_s2mm_status(dma_virtual_addr);
	    dma_mm2s_status(dma_virtual_addr);

		printf("Halt the DMA.\n");
	    write_dma(dma_virtual_addr, S2MM_CONTROL_REGISTER, HALT_DMA);
	    write_dma(dma_virtual_addr, MM2S_CONTROL_REGISTER, HALT_DMA);
	    dma_s2mm_status(dma_virtual_addr);
	    dma_mm2s_status(dma_virtual_addr);

	    printf("Destination memory block data: ");
		print_mem(virtual_dst_addr, 32);

	    printf("Writing source address of the data from MM2S in DDR...\n");
	    write_dma(dma_virtual_addr, MM2S_SRC_ADDRESS_REGISTER, 0x0e000000);
	    dma_mm2s_status(dma_virtual_addr);

	    printf("Writing the destination address for the data from S2MM in DDR...\n");
	    write_dma(dma_virtual_addr, S2MM_DST_ADDRESS_REGISTER, 0x0f000000);
	    dma_s2mm_status(dma_virtual_addr);

		printf("Run the MM2S channel.\n");
	    write_dma(dma_virtual_addr, MM2S_CONTROL_REGISTER, RUN_DMA);
	    dma_mm2s_status(dma_virtual_addr);

		printf("Run the S2MM channel.\n");
	    write_dma(dma_virtual_addr, S2MM_CONTROL_REGISTER, RUN_DMA);
	    dma_s2mm_status(dma_virtual_addr);

		printf("Enable all interrupts.\n");
	    write_dma(dma_virtual_addr, S2MM_CONTROL_REGISTER, ENABLE_ALL_IRQ|RUN_DMA);
	    write_dma(dma_virtual_addr, MM2S_CONTROL_REGISTER, ENABLE_ALL_IRQ|RUN_DMA);
	    dma_s2mm_status(dma_virtual_addr);
	    dma_mm2s_status(dma_virtual_addr);

	    printf("Writing MM2S transfer length of 32 bytes...\n");
	    write_dma(dma_virtual_addr, MM2S_TRNSFR_LENGTH_REGISTER, 32);
	    dma_mm2s_status(dma_virtual_addr);

	    printf("Writing S2MM transfer length of 32 bytes...\n");
	    write_dma(dma_virtual_addr, S2MM_BUFF_LENGTH_REGISTER, 32);
	    dma_s2mm_status(dma_virtual_addr);

	    printf("Waiting for MM2S synchronization...\n");
	    dma_mm2s_sync(dma_virtual_addr);

	    printf("Waiting for S2MM sychronization...\n");
	    dma_s2mm_sync(dma_virtual_addr);

	    dma_s2mm_status(dma_virtual_addr);
	    dma_mm2s_status(dma_virtual_addr);

		printf("Halt the DMA.\n");
	    write_dma(dma_virtual_addr, S2MM_CONTROL_REGISTER, HALT_DMA);
	    write_dma(dma_virtual_addr, MM2S_CONTROL_REGISTER, HALT_DMA);
	    dma_s2mm_status(dma_virtual_addr);
	    dma_mm2s_status(dma_virtual_addr);

	    printf("Destination memory block: ");
		print_mem(virtual_dst_addr, 32);

		printf("\n");
	usleep(5000000);
    }
    pthread_join(h_uio_thread, NULL);
    return 0;
}

system-user.dtsi

Tcl
Linux Device Tree correction file introducing UIO driver for the AXI DMA IP
/include/ "system-conf.dtsi"
/ {
	chosen {
	   bootargs = "earlycon clk_ignore_unused uio_pdrv_genirq.of_id=generic-uio rootwait quiet loglevel=0";
	   stdout-path = "serial0:115200n8";
	};
};
 
&amba_pl {
	loopback_dma: dma@40400000 {
		compatible = "generic-uio";
            	reg = <0x40400000 0x10000>;	
	};
};

Credits

Alex Falkovich

Alex Falkovich

6 projects • 15 followers
Embedded Specialist at Avnet ANZ
Thanks to Whitney Knitter.

Comments