参考文章

  1. ZYNQ中的UIO驱动和中断程序学习【Xilinx-Petalinux学习】

  2. Testing UIO with Interrupt on Zynq Ultrascale

  3. 何晔: 当ZYNQ遇到Linux Userspace I/O(UIO)


一、开发环境

  • 软件平台:
    Vivado 2018.2
    Ubuntu18.04 虚拟机
  • 硬件平台:
    ZYNQ-MZ7100FA(米联客)
  • 编译环境(未使用PetaLinux):
    参考《ZYNQ 修炼秘籍 LINUX 篇 基于 debian9 系统》

二、问题与解决方案

  • 应用:
    在用户层使用 axi_gpio,gpio设置为input,中断设置为上升沿触发,通过按键触发中断。
    在这里插入图片描述

  • 遇到问题:
    按照参考文章中修改设备树后,/dev下无uio设备

  • 解决方案:

  1. 在kernel/drivers/uio/uio_pdrv_genirq.c中第255行添加{.compatible = “generic-uio”}, 如下:
static struct of_device_id uio_of_genirq_match[] = {
	{.compatible = "generic-uio"},
	{ /* This is filled with module_parm */ },
	{ /* Sentinel */ },
};
  1. 新建设备树文件system-user.dtsi(include SDK生成的设备树),内容如下:
    (该工程使用SDK自动生成的设备树)
    设备树中断号:uio0(29)和uio1(30)中断号是在axi_gpio中断号(31)基础上递减的(如果递增中断不会触发)。
/*
 * CAUTION: This file is automatically generated by Xilinx.
 * Version:  
 * Today is: Tue Sep 15 13:54:36 2020
 */

/include/ "system-top.dts"
/ {
    amba_pl {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";
        ranges ;

        axi_gpio_0: gpio@41200000 {
		#gpio-cells = <3>;
		#interrupt-cells = <2>;
		clock-names = "s_axi_aclk";
		clocks = <&clkc 15>;
		compatible = "generic-uio";
		gpio-controller ;
		interrupt-controller ;
		interrupt-names = "ip2intc_irpt";
		interrupt-parent = <&intc>;
		interrupts = <0 31 1>;
		reg = <0x41200000 0x10000>;
		xlnx,all-inputs = <0x1>;
		xlnx,all-inputs-2 = <0x0>;
		xlnx,all-outputs = <0x0>;
		xlnx,all-outputs-2 = <0x0>;
		xlnx,dout-default = <0x00000000>;
		xlnx,dout-default-2 = <0x00000000>;
		xlnx,gpio-width = <0x2>;
		xlnx,gpio2-width = <0x20>;
		xlnx,interrupt-present = <0x1>;
		xlnx,is-dual = <0x0>;
		xlnx,tri-default = <0xFFFFFFFF>;
		xlnx,tri-default-2 = <0xFFFFFFFF>;
		};
	uio@0 {
	    compatible = "generic-uio";
	    status = "okay";
	    interrupt-controller;
	    interrupt-parent = <&intc>;
	    interrupts = <0 29 1>;
        };
    uio@1 {
        compatible = "generic-uio";
        status = "okay";
        interrupt-controller;
        interrupt-parent = <&intc>;
        interrupts = <0 30 1>;
     	};
	};

	chosen {
   		bootargs = "uio_pdrv_genirq.of_id=generic-uio";
   		stdout-path = "serial0:115200n8";
	};
};


三、测试程序

pin-uio-test.c

/*
 * This application reads/writes GPIO devices with UIO.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

void usage(void)
{
    printf("*argv[0] -d <UIO_DEV_FILE>\n");
    printf("    -d               UIO device file. e.g. /dev/uio0");
    return;
}

int main(int argc, char *argv[])
{
    int c;
    int fd;
    char *uiod;
    unsigned i = 0;
    unsigned icount;
    int irq_on = 1;
    int err;

    printf("pin UIO test.\n");
    while((c = getopt(argc, argv, "d:io:h")) != -1) {
        switch(c) {
        case 'd':
            uiod=optarg;
            break;
        case 'h':
            usage();
            return 0;
        default:
            printf("invalid option: %c\n", (char)c);
            usage();
            return -1;
        }

    }

    /* Open the UIO device file */
    fd = open(uiod, O_RDWR);
    if (fd < 1) {
        perror(argv[0]);
        printf("Invalid UIO device file:%s.\n", uiod);
        usage();
        return -1;
    }

    for(i = 0; ; ++i) {
        /* Print out a message, for debugging. */
            if (i == 0)
                    fprintf(stderr, "Started uio test driver.\n");
            else
                    fprintf(stderr, "Interrupts: %d\n", icount);

        /* enable IRQ, trigger the irqcontrol of driver */
        write(fd, &irq_on, sizeof(irq_on));
            /* Here we got an interrupt from the
            device. Do something to it. */
        err = read(fd, &icount, 4);
        if (err != 4) {
            perror("uio read:");
            break;
        }
    }

    return 0;
}

gpio-uio-test.c

/*
 * This application reads/writes GPIO devices with UIO.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>

#define IN 0
#define OUT 1

#define GPIO_MAP_SIZE 0x10000

#define GPIO_DATA_OFFSET 0x00
#define GPIO_TRI_OFFSET 0x04
#define GPIO2_DATA_OFFSET 0x08
#define GPIO2_TRI_OFFSET 0x0C

#define GIER 0x011C
#define IP_IER 0x0128
#define IP_ISR 0x0120

void usage(void)
{
    printf("*argv[0] -d <UIO_DEV_FILE> -i|-o <VALUE>\n");
    printf("    -d               UIO device file. e.g. /dev/uio0");
    printf("    -i               Input from GPIO\n");
    printf("    -o <VALUE>       Output to GPIO\n");
    return;
}

int main(int argc, char *argv[])
{
    int c;
    int fd;
    int direction=IN;
    char *uiod;
    int value = 0;
    int valued = 0;
    int irq_on = 1;
    unsigned int icount;
    int err;

    void *ptr;

    printf("GPIO UIO test.\n");
    while((c = getopt(argc, argv, "d:io:h")) != -1) {
        switch(c) {
        case 'd':
            uiod=optarg;
            break;
        case 'i':
            direction=IN;
            break;
        case 'o':
            direction=OUT;
            valued=atoi(optarg);
            break;
        case 'h':
            usage();
            return 0;
        default:
            printf("invalid option: %c\n", (char)c);
            usage();
            return -1;
        }

    }

    /* Open the UIO device file */
    fd = open(uiod, O_RDWR);
    if (fd < 1) {
        perror(argv[0]);
        printf("Invalid UIO device file:%s.\n", uiod);
        usage();
        return -1;
    }

    /* mmap the UIO device */
    ptr = mmap(NULL, GPIO_MAP_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

  	/* Print Interrupt Registers */
    value = *((unsigned *) (ptr + GIER));
    printf("%s: GIER: %08x\n",argv[0], value);
    value = *((unsigned *) (ptr + IP_IER));
    printf("%s: IP_IER: %08x\n",argv[0], value);
    value = *((unsigned *) (ptr + IP_ISR));
    printf("%s: IP_ISR: %08x\n",argv[0], value);

    /* Enable All Interrupts */
    printf("%s: Enable All Interrupts in Regs\n", argv[0]);
    /*AXI GPIO中断:只要有电平变化就会触发中断*/
    *((unsigned *)(ptr + GIER)) = 0x80000000;//使能AXI GPIO IP核全局中断
    *((unsigned *)(ptr + IP_IER)) = 0x1;//使能通道1中断
    *((unsigned *)(ptr + IP_ISR)) = 0x1;//清中断
    
    while(1){

	    /* Enable UIO interrupt */
	    write(fd, &irq_on, sizeof(irq_on));

	    if (direction == IN) {
			/* Read from GPIO */
	    	*((unsigned *)(ptr + GPIO_TRI_OFFSET)) = 255;
	      	err = read(fd, &icount, 4);
          	if (err != 4) {
            	perror("uio read:");
            	break;
          	}
	      	value = *((unsigned *) (ptr + GPIO_DATA_OFFSET));
	    	if(value){
	    		printf("\n%s: Interrupts: %d\n", argv[0], icount);
		      	printf("%s: input: %08x\n",argv[0], value);
		      	/* Print Interrupt Registers */
			    value = *((unsigned *) (ptr + GIER));
			    printf("%s: GIER: %08x\n",argv[0], value);
			    value = *((unsigned *) (ptr + IP_IER));
			    printf("%s: IP_IER: %08x\n",argv[0], value);
			    value = *((unsigned *) (ptr + IP_ISR));
			    printf("%s: IP_ISR: %08x\n",argv[0], value);
		   }
		   *((unsigned *)(ptr + IP_ISR)) = 0x1;//清中断
	    }
    }
    
    munmap(ptr, GPIO_MAP_SIZE);

    return 0;
}

四、运行结果

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

说明:由于AXI_GPIO 输入电平变化一次就触发一次中断,所以按键按下和松开都会触发一次中断,测试程序中检测输入电平为1才打印一次,所以中断号是间隔的
说明:由于AXI_GPIO 输入电平变化一次就触发一次中断,所以按键按下和松开都会触发一次中断,测试程序中检测输入电平为1才打印一次,所以中断号是间隔的

Logo

更多推荐