当前位置:网站首页>驱动程序开发:按键中断之异步通知

驱动程序开发:按键中断之异步通知

2022-08-10 02:38:00 邓家文007

一、对事件检测触发响应任务的一些描述

  上一篇驱动程序讲述的是为了解决应用程序为了获取驱动程序按键触发的信息,一直在循环获取按键值的最新信息而消耗了系统的大部分运行资源,因此通过阻塞或非阻塞这两中方式访问驱动驱动设备,通过阻塞方式访问的话应用程序会处于休眠态,等待驱动设备可以使用,非阻塞方式的话会通过 poll 函数来不断的轮询,查看驱动设备文件是否可以使用。这两种方式都需要应用程序主动的去查询设备的使用情况。
  异步通知这种“信号”为此应运而生,信号类似于我们硬件上使用的“中断”,只不过信号是软件层次上的。算是在软件层次上对中断的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。

二、驱动程序编写步骤

/* 驱动程序设备结构体 */
1 struct xxx_dev {
    
2 ......
3 	struct fasync_struct *async_queue; /* 异步相关结构体 */
4 };
5 /* 相应的队列添加异步通知信号 */
6 static int xxx_fasync(int fd, struct file *filp, int on)
7 {
    
8 	struct xxx_dev *dev = (xxx_dev)filp->private_data;
9
10 	if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
11 		return -EIO;
12 	return 0;
13 }
14	/* 加上的函数加入操作集 */
15 static struct file_operations xxx_ops = {
    
16 ......
17 .fasync = xxx_fasync,
18 ......
19 };
20	/* 相应的队列删除异步通知信号 */
21 static int xxx_release(struct inode *inode, struct file *filp)
22 {
    
23	 return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
24 }
25	/* 某些条件达到了,驱动程序向应用程序发送异步通知信号“软中断信号” */
26  static ...... xxx(......) {
    
27    	kill_fasync(&dev->fasync_queue, SIGIO, POLL_IN);     //发送异步通知信号
28  }

三、应用程序编写步骤

1	/* 异步通知信号处理函数,函数原型 typedef void (*sighandler_t)(int) */
2	static void xxx_sighandler(int xxx) {
    
3		......
4	}
5	
6	int main(int argc, char *argv[]) {
    
7		......
8		/* 根据驱动程序所使用的信号与信号处理函数连接在一起 ,形成驱动程序与应用程序之间的软中断关系*/
9		signal(xxx,xxx_sighandler);
10		fcntl(fd,F_SETOWN,getpid());    /* 当前获取进程号,将进程号告知内核。设置将要在文件描述词fd上接收SIGIO 或 SIGURG事件信号的进程或进程组标识 。 */
11    	flag = fcntl(fd,F_GETFL);		/* 读取文件状态,这里是获取当前的进程状态 */
12    	fcntl(fd,F_SETFL,flag | FASYNC);	/* 开启当前当前进程异步通知功能 */
13		while(1) {
    
14			sleep(x);
15		}
16		......
17	}	

四、实验编程

  要求:加入异步通知相关内容即可,当按键按下以后驱动程序向应用程序发送 SIGIO 信号,应用程序获取到 SIGIO 信号以后 读取并且打印出按键值。
  `其设备树是和上一篇博客的阻塞与非阻塞驱动程序一样的,

1、完整的驱动程序(keyirq.c)

/* * 根据linux内核的程序查找所使用函数的对应头文件。 */  
#include <linux/module.h> //MODULE_LICENSE,MODULE_AUTHOR 
#include <linux/init.h> //module_init,module_exit 
#include <linux/kernel.h> //printk 
#include <linux/fs.h> //struct file_operations 
#include <linux/slab.h> //kmalloc, kfree 
#include <linux/uaccess.h> //copy_to_user,copy_from_user 
#include <linux/io.h> //ioremap,iounmap 
#include <linux/cdev.h> //struct cdev,cdev_init,cdev_add,cdev_del 
#include <linux/device.h> //class 
#include <linux/of.h> //of_find_node_by_path 
#include <linux/of_gpio.h> //of_get_named_gpio 
#include <linux/gpio.h> //gpio_request,gpio_direction_output,gpio_set_value 
#include <linux/atomic.h> //atomic_t 
#include <linux/of_irq.h> //irq_of_parse_and_map
#include <linux/interrupt.h> //request_irq
#include <linux/timer.h> //timer_list
#include <linux/jiffies.h> //jiffies
#include <linux/atomic.h> //atomic_set
#include <linux/signal.h> //相关的驱动程序上发给应用程序的信号

#define KEY_NUM 1 //按键个数
#define KEYVALUE 0X01 //按键值
#define INVAKEY 0XFF //无效的按键值

static int keyirq_fasync(int fd, struct file *file, int on);

/* 4.1.1 按键key行为结构体 */
struct irq_keydescri{
    
    int gpio;                               /* io编号 */
    int irqnum;                             /* 中断号 */
    unsigned char value;                    /* 按键值 */
    char name[10];                          /* 名字 */
    irqreturn_t (*handler)(int,void *);     /* 中断处理函数 */
};

/* 1.5 keyirq设备结构体 */  
struct keyirq_dev {
      
    dev_t                   devid;          /* 设备号 */  
    int                     major;          /* 主设备号 */  
    int                     minor;          /* 次设备号 */  
    char                    *name;          /* 设备名称 */  
    int                     dev_count;      /* 设备个数 */  
    struct  cdev            cdev;           /* 注册设备结构体 */  
    struct  class           *class;         /* 类 */  
    struct  device          *device;        /* 设备 */  
    struct  device_node     *nd;            /* 设备节点 */  
    struct  irq_keydescri   irqkey[KEY_NUM];/* 按键key行为结构体 */
    struct  timer_list      timer;          /* 定时器结构体 */
    atomic_t                keyvalue;       /* 按键值 */
    atomic_t                release;        /* 按键释放标志 */
    struct  fasync_struct   *fasync_queue;  /* 异步通知机构体 */
};  
struct keyirq_dev keyirq;           /* 定义keyirq设备结构体 */  
  
/* * inode: 传递给驱动的inode * filp : 要进行操作的设备文件(文件描述符) * buf : 数据缓冲区 * cnt : 数据长度 * ppos : 相对于文件首地址的偏移 */  
/* 2.1 打开字符设备文件 */  
static int keyirq_open(struct inode *inode, struct file *filp) {
      
	int ret = 0;  
	/* 设置私有类数据 */  
	filp->private_data = &keyirq;  
	return ret;  
}  
	  
/* 2.2 关闭字符设备文件 */  
static int keyirq_release(struct inode *inode, struct file *filp) {
       
    return keyirq_fasync(-1, filp, 0); //删除异步通知 
    return 0; 
}  
  
/* 2.3 向字符设备文件读出数据 */  
static ssize_t keyirq_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {
      
    int ret = 0;  
    unsigned char keyvalue;
    unsigned char release;
    /* 提取私有属性 */  
    struct keyirq_dev *dev = filp->private_data;  

    keyvalue = atomic_read(&dev->keyvalue);
    release = atomic_read(&dev->release);

    if(release) {
    
        if(keyvalue & 0X80) {
    
            keyvalue &= ~0X80;
            ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
        } else {
    
            goto data_error;
        }
        atomic_set(&dev->release,0);    // 按下标志清零
    } else {
    
        goto data_error;
    }

    return ret; 
data_error:
    return -EINVAL;
}  
  
/* 2.4 向字符设备文件写入数据 */  
static ssize_t keyirq_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {
      
    int ret = 0;  
    return ret;  
}  

/* 5.1 异步通知添加或删除函数 */
static int keyirq_fasync(int fd, struct file *filp, int on)
{
    
	/* 提取私有属性 */  
    struct keyirq_dev *dev = filp->private_data;  
    /* 函数原型(异步通知助手),它是一个异步通知信号加入队列的API函数,0表示删除该异步通知 int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) { if (!on) return fasync_remove_entry(filp, fapp); return fasync_add_entry(fd, filp, fapp); } */
	return fasync_helper(fd, filp, on, &dev->fasync_queue);     
}  

/* 2.5 keyirq设备操作集 */  
static const struct file_operations keyirq_fops = {
      
    .owner      =   THIS_MODULE,  
    .open       =   keyirq_open,  
    .release    =   keyirq_release,  
    .read       =   keyirq_read,  
    .write      =   keyirq_write,  
    .fasync     =   keyirq_fasync,
};  

/* 4.4.1 定时器处理函数,定时器10ms溢出触发定时器中断,进入定时器处理函数 */
static void timer_func(unsigned long arg) {
    
    int value = 0;
    struct keyirq_dev *dev = (struct keyirq_dev*)arg;

    value = gpio_get_value(dev->irqkey[0].gpio);
    if(value == 0) {
        /* 按下 */
        atomic_set(&dev->keyvalue,dev->irqkey[0].value);    /* 哪个按键被按下了 */
        printk("KEY0 press!\r\n");
    } else if(value == 1) {
     /* 释放 */
        atomic_set(&dev->keyvalue,0X80 | dev->irqkey[0].value); /* 按键激活 */
        atomic_set(&dev->release,1);    /* 完整的按键过程 */
        printk("KEY0 release!\r\n");
    }
    /* 5.2 有效的按键过程 */
    if(atomic_read(&dev->release)) {
    
        kill_fasync(&dev->fasync_queue, SIGIO, POLL_IN);     //发送异步
    }
}

/* 4.2.1 按键中断处理函数,param传参是keyirq */
static irqreturn_t key_handler(int irq, void *param)
{
    
    struct keyirq_dev *dev = param;

    dev->timer.data = (volatile unsigned long)param;    //传递值,会变的
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));  //20ms定时

	return IRQ_HANDLED;
}

/* 4. 按键初始化,这里分别 初始化IO,初始化中断,初始化定时器 */
static int key_init(struct keyirq_dev *dev) {
    
    int ret = 0;
    int i;
    /* 4.1.2 按键IO初始化 */
    dev->nd = of_find_node_by_path("/key");   //获取设备节点
    if(dev->nd == NULL) {
    
        ret = -EINVAL;
        goto fail_nd;
    }
    for(i=0; i<KEY_NUM; i++) {
    
        dev->irqkey[i].gpio = of_get_named_gpio(dev->nd,"key-gpio",i);  //获取GPIO编号
        if(dev->irqkey[i].gpio < 0) {
    
            ret = -EINVAL;
            goto fail_gpio;
        }
        memset(dev->irqkey[i].name,0,sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name,"KEY%d",i); 
        ret = gpio_request(dev->irqkey[i].gpio,dev->irqkey[i].name);    //请求GPIO
        if(ret) {
    
            ret = -EBUSY;
            printk("IO %d can't request!\r\n",dev->irqkey[i].gpio);
            goto fail_request;
        }
        ret = gpio_direction_input(dev->irqkey[i].gpio);                //设置GPIO
        if(ret < 0) {
    
            ret = -EINVAL;
            goto fail_input;
        }     
    }

    /* 4.2 按键中断初始化 */
    dev->irqkey[0].handler = key_handler;       //初始化中断处理函数
    dev->irqkey[0].value = KEYVALUE;            //按键值设置0X01
    for (i = 0; i < KEY_NUM; i++) {
    
        // dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio); //获取中断号

        dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd,i);        //通用的获取中断号方法
        if (dev->irqkey[i].irqnum < 0) {
    
		    printk("%s: Cannot find interrupt.\n", dev->irqkey[i].name);
		    goto fail_irq;
        }

        ret = request_irq(dev->irqkey[i].irqnum,dev->irqkey[0].handler,
                            IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                            dev->irqkey[i].name,&keyirq);
        if(ret) {
    
            printk("irq %d request failed!\r\n",dev->irqkey[i].irqnum);
            goto fail_irq;
        }
    }

    /* 4.4 初始化定时器 */
    init_timer(&keyirq.timer);  //初始化定时器
    keyirq.timer.function = timer_func; //添加定时器处理函数
    
    return 0;

fail_irq:
fail_input:
    gpio_free(dev->irqkey[i].gpio);
fail_request:
fail_gpio:
fail_nd:
    return ret;
}


/* 1.1 驱动模块入口函数 */  
static int __init keyirq_init(void) {
      
    int ret = 0;  
    printk("keyirq_init!\r\n");  
    /***********************************************************************************************/  
    /* 3.1 配置keyirq结构体参数 */  
    keyirq.dev_count = 1;                  //设置设备号个数 
    keyirq.name  = "keyirq";                  //设置设备名字 
    keyirq.major = 0;                      //0为系统分配设备号, 1为自定义设备号 
    if(keyirq.major) {
      
        keyirq.devid = MKDEV(keyirq.major,0); //自定义主设备号和次设备号,并整合为设备号结构体中 
        ret = register_chrdev_region(keyirq.devid, keyirq.dev_count, keyirq.name);   //注册设备号 
    } else {
      
        alloc_chrdev_region(&keyirq.devid, 0, keyirq.dev_count, keyirq.name);    //注册设备号,系统自动分配 
        keyirq.major = MAJOR(keyirq.devid);  
        keyirq.minor = MINOR(keyirq.devid);  
    }  
    if(ret < 0) {
      
	    goto fail_devid;                //注册设备号失败 
	}  
    printk("keyirq major = %d, minor = %d\r\n",keyirq.major,keyirq.minor);   //注册设备号成功了,就打印主次设备号 
  
	/* 3.2 注册或者叫添加字符设备 */  
	cdev_init(&keyirq.cdev, &keyirq_fops);    //初始化cdev结构体 
	ret = cdev_add(&keyirq.cdev, keyirq.devid, keyirq.dev_count);    //添加字符设备 
	if(ret < 0) {
      
	    goto fail_cdev;                 //注册或者叫添加设备失败 
	}  
	  
	/* 3.3 自动创建设备节点 */  
	keyirq.class = class_create(THIS_MODULE, keyirq.name);        //创建类 
	if(IS_ERR(keyirq.class)) {
      
	    ret = PTR_ERR(keyirq.class);  
	    goto fail_class;  
    }  
    keyirq.device = device_create(keyirq.class, NULL, keyirq.devid, NULL, keyirq.name); //创建设备 
    if(IS_ERR(keyirq.device)) {
      
        ret = PTR_ERR(keyirq.device);  
        goto fail_device;  
    }  
    /***********************************************************************************************/  
    
    /* 5. 初始化原子变量 */
    atomic_set(&keyirq.keyvalue,INVAKEY);       //设置为无效的按键值
    atomic_set(&keyirq.release,0);              //0表示按键没有被释放

    /* 4.3 调用KEY IO初始化函数初始化IO */
    ret = key_init(&keyirq);
    if(ret < 0) {
    
        goto fail_keyinit;
    }

    return ret;  

fail_keyinit:
fail_device:  
    class_destroy(keyirq.class);  
fail_class:  
    cdev_del(&keyirq.cdev);  
fail_cdev:  
    unregister_chrdev_region(keyirq.devid, keyirq.dev_count);  
fail_devid:  
    return ret;  
}  
	  
/* 1.2 驱动模块出口函数 */  
static void __exit keyirq_exit(void) {
      
    int i;
    del_timer_sync(&keyirq.timer);                                  //删除定时器
    /* 最后一一添加 , 注销字符设备驱动 */  
    for(i=0;i<KEY_NUM;i++) {
    
        free_irq(keyirq.irqkey[i].irqnum,&keyirq);
        gpio_free(keyirq.irqkey[i].gpio);
    }
    device_destroy(keyirq.class, keyirq.devid);                //摧毁设备 
    class_destroy(keyirq.class);                               //摧毁类 
    cdev_del(&keyirq.cdev);                                    //注销字符设备结构体 
    unregister_chrdev_region(keyirq.devid, keyirq.dev_count);  //注销设备号 
}  
	  
/* 1.3 注册驱动模块 */  
module_init(keyirq_init);  
module_exit(keyirq_exit);  
	  
/* 1.4 驱动许可和个人信息 */  
MODULE_LICENSE("GPL");  
MODULE_AUTHOR("djw");  

2、完整的应用程序(asyncnotiAPP.c)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <fcntl.h>

/* * argc:应用程序参数个数 * argv[]:具体打参数内容,字符串形式 * ./asyncnotiAPP <filename> * ./asyncnotiAPP /dev/keyirq */
int fd;

static void sigio_signal_func(int num) {
    
    int err;
    unsigned int keyvalue = 0;
    err = read(fd,&keyvalue,sizeof(&keyvalue));
    if(err < 0) {
    

    } else {
    
        printf("sigio signal!key value = %d\r\n",keyvalue);
    }
}


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


    /* 判断输入的元素个数 */
    if(argc != 2) {
    
        printf("ERROR USAGE!\r\n");
        return -1;
    }

    filename = argv[1];     //获取驱动文件的路径
    fd = open(filename,O_RDWR); //根据文件路径以读写方式打开文件
    if(fd < 0) {
    
        printf("file %s open failed!\r\n",filename);
        return -1;
    }

    /* 设置信号处理函数 */
    signal(SIGIO,sigio_signal_func);

    fcntl(fd,F_SETOWN,getpid());    /* 设置但前进程接收SIGIO信号 */
    flag = fcntl(fd,F_GETFL);
    fcntl(fd,F_SETFL,flag | FASYNC);    /* 开启异步通知 */

    while(1) {
    
        sleep(2);
    }

    close(fd);
    return 0;
}

3、实验现象
在这里插入图片描述

原网站

版权声明
本文为[邓家文007]所创,转载请带上原文链接,感谢
https://blog.csdn.net/morecrazylove/article/details/126203518