LINUX下ZYNQ自定义IP中断驱动要点

中断驱动基础及常用API

设备树表示

需要在vivado中将PL的部分引入IRQ_F2P连号即可。
自定义IP的设备树可以很容易通过HDF文件和PETALINUX自动生成,具体命令为:
先配置下环境:

source *petalinux目录*/setting.sh
hsi

hsi后:

open_hw_design *hdf文件目录*
set_repo_path *device-tree-xlnx-xilinx-v2018.3库文件目录*
create_sw_design device-tree -os device_tree -proc ps7_cortexa9_0
generate_target -dir *存放目录*

自动生成之后可以看到一个名曰pl.dtsi的文件,里面放着自定义IP核的相关信息:

                  breath_led_ip_0: breath_led_ip@43c20000 {
                         clock-names = "s0_axi_aclk";
                         clocks = <&clkc 15>;
                         compatible = "xlnx,breath-led-ip-1.0";
                         interrupt-names = "breath_intr";
                         interrupt-parent = <&intc>;
                         interrupts = <0 29 4>;
                         reg = <0x43c20000 0x10000>;
                         xlnx,s0-axi-addr-width = <0x4>;
                         xlnx,s0-axi-data-width = <0x20>;
                };

interrupt-parent = <&intc>;
interrupts = <0 29 4>;

0代表中断域为SPI中断,29代表硬件中断号为29+32=61。4代表触发方式,但具体触发方式貌似可以在自己写的LINUX驱动里修改。

驱动中每个中断都有一个中断号,通过中断号即可区分不同的中断。注意这里的中断号不是硬件的中断号,例如PL-PS的中断是从61开始的,但是这个61并不是LINUX的对应的中断号,我们需要通过irq_of_parse_and_map进行中断号进行申请。

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

dev :设备节点。
index:索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。只要一个的话就写0。

linux中断API

int request_irq(unsigned int irq,
			irq_handler_t handler,
			unsigned long flags,
			const char *name,
			void *dev)

函数参数和返回值含义如下:
irq:要申请中断的中断号,是通过irq_of_parse_and_map返回的中断号,不是硬件终端号61。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志,里面有一堆标志,标志间可以通过或运算同时选择。这里可以设置触发方式。
name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev :如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。笔者喜欢设置成NULL。
返回值:0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。

释放中断

void free_irq(unsigned int irq, void *dev)

函数参数和返回值含义如下:
irq :要释放的中断。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断
只有在释放最后中断处理函数的时候才会被禁止掉。

使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数格式如下所示:

irqreturn_t (*irq_handler_t) (int, void *)

返回值要设置为:

return IRQ_RETVAL(IRQ_HANDLED)

一般通过下面两个函数对中断进行全局的关闭和开启:

local_irq_save(flags) 
local_irq_restore(flags)

中断下半部

tasklet的套路:

/* 定义 taselet */
struct tasklet_struct testtasklet;

/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
}

/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
	......
	tasklet_schedule(&testtasklet);
	......
}

/* 驱动入口函数 */
static int __init xxxx_init(void)
{
	......
	/* 初始化 tasklet */
	tasklet_init(&testtasklet, testtasklet_func, data);
	......
}

工作队列的套路:

/* 定义工作(work) */
struct work_struct testwork;

/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
	/* work 具体处理内容 */
}

/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
	......
	/* 调度 work */
	schedule_work(&testwork);
	......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
	......
	/* 初始化 work */
	INIT_WORK(&testwork, testwork_func_t);
	......
}

工作队列允许所需要的任务进入睡眠,不需要的话就就选择 tasklet。

程序设计

自定义IP每隔一段时间生成一个中断信号,驱动收到中断后将变量val置位,如果被测试程序读取,则将val复位。测试程序一直轮询驱动中val的值,当值为1时输出。
驱动程序:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 



#define NEWCHRLED_CNT 1 //the number of the devids
#define NEWCHRLED_NAME		"intr_ip_test"

static void __iomem *LED_CTR_REG_MAP;
static void __iomem *LED_FRE_REG_MAP;


struct intr_ip_test_dev{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    spinlock_t lock;
    struct device_node *nd;
    unsigned int irq;  //the interrupt number of breath_led_ip
    unsigned int regdata[2];   //the two control registers made by ourselves
    int val;                //the interrupt test flag
    struct tasklet_struct testtasklet;    
};


static struct intr_ip_test_dev intr_ip_test;



static irqreturn_t ip_intr_handler(int irq, void *dev){
    
    //printk("intr detected!\r\n");

    /* awaken the tasklet function*/
    tasklet_schedule(&intr_ip_test.testtasklet);
    return IRQ_RETVAL(IRQ_HANDLED);
}

        /* tasklet function */
void testtasklet_func(unsigned long data){
    intr_ip_test.val = 1;
    // this val will be cleared as the application APP reads the data from the driver. 
}


static int chrdev_open(struct inode *inode, struct file *filp)
{   
    
	filp->private_data = &intr_ip_test;	/* set our own structure as the private_data */


    printk("intr_ip_test open\r\n");
	return 0;
}



static ssize_t chrdev_read(struct file *filp, char __user *buf,
			size_t cnt, loff_t *offt)
{
    unsigned long flags;
    int ret;
    spin_lock_irqsave(&intr_ip_test.lock,flags);
    
    ret = copy_to_user(buf,&intr_ip_test.val,sizeof(intr_ip_test.val));

    intr_ip_test.val = 0; //clear val

    spin_unlock_irqrestore(&intr_ip_test.lock,flags);

    return 0;
}


static ssize_t chrdev_write(struct file *filp, const char __user *buf,
			size_t cnt, loff_t *offt)
{

     return 0;
}


static int chrdev_release(struct inode *inode, struct file *filp)
{
	printk("intr_ip_test release!\r\n");

    return 0;
}

static struct file_operations chrdev_fops = {
	.owner		= THIS_MODULE,
	.open		= chrdev_open,
	.read		= chrdev_read,
	.write		= chrdev_write,
	.release	= chrdev_release,
};


void inline open_led(void){
    writel(1,LED_CTR_REG_MAP);
}

void inline close_led(void){
    writel(0,LED_CTR_REG_MAP);
}

void inline set_led_g1(void){
    writel(0x8000002f,LED_FRE_REG_MAP);
}

void inline set_led_g2(void){
    writel(0x800000ef,LED_FRE_REG_MAP);
}

void inline reg_map (void){
    LED_CTR_REG_MAP = ioremap(intr_ip_test.regdata[0], 4);
	LED_FRE_REG_MAP = ioremap(intr_ip_test.regdata[0]+4, 4);
} 

void inline reg_unmap (void){
    iounmap(LED_CTR_REG_MAP);
	iounmap(LED_FRE_REG_MAP);
} 


static int __init intr_ip_test_init(void)
{
    int ret;

    spin_lock_init(&intr_ip_test.lock);

    /* the initialization of the tasklet*/
    tasklet_init(&intr_ip_test.testtasklet, testtasklet_func, NULL);


    intr_ip_test.nd = of_find_node_by_path("/amba_pl/breath_led_ip@43c20000");
    if(intr_ip_test.nd == NULL){
        printk("no node from dts found!\r\n");
        goto out0;
    }

    ret = of_property_read_u32_array(intr_ip_test.nd, "reg", intr_ip_test.regdata, 2);
    if(ret < 0){
		printk("reg read failed!\r\n");
	} else {
		u8 i = 0;
		printk("reg data:\r\n");
		for(i = 0; i < 2; i++)
		    printk("%#X ", intr_ip_test.regdata[i]);
		printk("\r\n");
	}

    /*ip interrupt*/

    intr_ip_test.irq = irq_of_parse_and_map(intr_ip_test.nd,0);
    
    ret = request_irq(intr_ip_test.irq,
                        ip_intr_handler,
                        IRQF_TRIGGER_RISING,
                        "BREATH_LED",
                        NULL);
    if(ret < 0 )
    {
        printk("irq %d request failed\r\n", intr_ip_test.irq);
        ret = -EFAULT;
        goto out0;
    }
    printk("irq %d request done\r\n", intr_ip_test.irq);

    /* major minor and devid */
    ret = alloc_chrdev_region(&intr_ip_test.devid,0,NEWCHRLED_CNT,NEWCHRLED_NAME);
    if(ret)
        goto out1;
    intr_ip_test.major = MAJOR(intr_ip_test.devid);
    intr_ip_test.major = MINOR(intr_ip_test.devid);

    printk("intr_ip_test major=%d,minor=%d\r\n",intr_ip_test.major, intr_ip_test.minor);

    /*initailization of cdev*/
    intr_ip_test.cdev.owner = THIS_MODULE;
    cdev_init(&intr_ip_test.cdev,&chrdev_fops);

    ret = cdev_add(&intr_ip_test.cdev, intr_ip_test.devid,NEWCHRLED_CNT);
    if(ret)
        goto out2;

    /* create a class*/
    intr_ip_test.class = class_create(THIS_MODULE,NEWCHRLED_NAME);
    	if (IS_ERR(intr_ip_test.class)) {
		ret = PTR_ERR(intr_ip_test.class);
		goto out3;
	}
    
    intr_ip_test.device = device_create(intr_ip_test.class, NULL,
				intr_ip_test.devid, NULL, NEWCHRLED_NAME);
	if (IS_ERR(intr_ip_test.device)) {
		ret = PTR_ERR(intr_ip_test.device);
		goto out4;
	}

    /*map the addresses of the my_ip*/
    reg_map();
    
    open_led();
    
    set_led_g1();
    
    return 0;
    
out4:
	class_destroy(intr_ip_test.class);
out3:
	cdev_del(&intr_ip_test.cdev);
out2:
    unregister_chrdev_region(intr_ip_test.devid,NEWCHRLED_CNT);
out1:
    free_irq(intr_ip_test.irq, NULL);
out0:
    return ret;
}

static void __exit intr_ip_test_exit(void)
{   
   // printk("Closing the driver!");
    close_led();
    reg_unmap();
    disable_irq_nosync(intr_ip_test.irq);
    
    device_destroy(intr_ip_test.class, intr_ip_test.devid);

    class_destroy(intr_ip_test.class);

    cdev_del(&intr_ip_test.cdev);

    unregister_chrdev_region(intr_ip_test.devid,NEWCHRLED_CNT);

    free_irq(intr_ip_test.irq, NULL);
}

module_init(intr_ip_test_init);
module_exit(intr_ip_test_exit);

MODULE_AUTHOR("zangyujiagood");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("intr_ip_test");

测试程序:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*
 * @description		: main主程序
 * @param - argc	: argv数组元素个数
 * @param - argv	: 具体参数
 * @return			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, ret;
	int val;

	if(2 != argc) {
		printf("Usage:\n"
		"\t./App /dev/intr_ip_test\n"
		);
		return -1;
	}

	fd = open(argv[1], O_RDWR);
	if(0 > fd) {
		printf("ERROR: %s file open failed!\n", argv[1]);
		return -1;
	}


	for ( ; ; ) {
		read(fd, &val, sizeof(int));
		if (0x1 == val)
			printf("PS received the intr signal, the 'val' = 0x%x\n", val);
	}

	close(fd);
	return 0;
}

depmod,modprobe加载驱动:
LINUX下ZYNQ自定义IP中断驱动要点_第1张图片
可以看到中断号是50,不是硬件设备号61。
测试APP
LINUX下ZYNQ自定义IP中断驱动要点_第2张图片
完美。

你可能感兴趣的:(LINUX下ZYNQ自定义IP中断驱动要点)