linux驱动学习笔记---多路复用实现所有按键中断以及非阻塞的实现(六)

1,文件io模型: 阻塞, 非阻塞, 多路复用
2,文件io中的ioctl和mmap接口
3, 中断下半部:softirq, tasklet, workqueue
4, 驱动中出错判断(驱动编写的规范)
----------------------------------------------
非阻塞: 因为linux文件io都是阻塞, 需要非阻塞的话,就需要进行
    
    
    应用:修改标志位
    方法1:
            int fd = open("/dev/xxx", O_RDWR|O_NONBLOCK);
            read(fd, buf, size); <==非阻塞 
            
    方法2 : 
            //int fd = open("/dev/xxx", O_RDWR);
            int fd = socket(AF_INET, SOCK_STREAM, 0);    
            
            int flag = fcntl(fd, F_GETFL, 0);
            flag |= NONBOLOCK 
            fcntl(fd, F_SETFL, flag);
            
            read(fd, buf, size); <==非阻塞 
            
    
    ----------------------------------------------
    驱动: 
        xxx_read()
        {
            //区分当前是阻塞还是非阻塞
            if((filp->f_flags & O_NONBLOCK) && !have_data)
            {
                return -EAGAIN;
            }
        }
===============================================================
多路复用:监控多个文件的io操作:读,写, 出错, 会利用select/poll来实现    


    select应用:
        int fd1<==3, int fd2 <==4;
        int max = fd2 + 1;
        fd_set rd_set;
        whlie(1)
        {
                FD_ZERO(&rd_set);
                FD_SET(fd1, &rd_set);
                FD_SET(fd2, &rd_set);
                
                ret = select(max, &rd_set, NULL, NULL, NULL);
                if(ret > 0)
                {
                    //判断是谁有数据
                     if(FD_ISSET(fd1, &rd_set))
                     {
                         //拷贝数据--此处不会阻塞
                         read()
                     }
                      if(FD_ISSET(fd2, &rd_set))
                     {
                         //拷贝数据--此处不会阻塞
                         read()
                     }
                }
                
        }        
        
    poll :
          #include <poll.h>

       int poll(struct pollfd *fds, nfds_t nfds, int timeout);


          struct pollfd {
               int   fd;       //你需要监控文件描述符
               short events;    //你需要监控的事件: 读(POLLIN),写(POLLOUT),出错(POLLERR)
               short revents;    /* returned events */ //用于检测是否发生了事件
                            //(如果有数据,内核会自动将POLLIN/POLLOUT|POLLERR设置到该变量上)
           };

        例子:
            struct pollfd pfd[2];

            pfd[0].fd = fd; //自己写的按键设备
            pfd[0].events = POLLIN; //监控读 (POLLIN|POLLOUT|POLLERR)

            pfd[1].fd = 0; //监控标准输入
            pfd[1].events = POLLIN;
            
            ret = poll(pfd, 2,  -1);
            
            //判断是哪个有数据
            if(pfd[0].revents & POLLIN)
            {
                 //拷贝数据--此处不会阻塞
                         read()
            }
            if(pfd[1].revents & POLLIN)
            {
                 //拷贝数据--此处不会阻塞
                         read()
            }
    -----------------------------------------------------
            
    应用: 
                select/poll
    -----------------------------------------
    驱动: 
            xxx_poll
            {
                //返回一个整数: 没有数据的时候返回0, 有数据的时候,返回POLLIN
    
                unsigned int mask = 0;

                // 将当前的等待队列头,注册到vfs层, 注意: poll_wait该函数不会导致休眠
                //参数1-文件对象--当前file
                //参数2--当前的等待队列头
                //参数3--当前传递过来的第三个参数
                poll_wait(filp, &wq_head, pts);

                if(have_data)
                    mask |= POLLIN;

                return mask;        
                
            }

poll_wait()是用在select系统调用中的. 

一般你的代码会有一个struct file_operations结构, 
其中fop->poll函数指针指向一个你自己的函数, 
在这个函数里应该调用poll_wait() 

当用户调用select系统调用时,select系统调用会 
先调用 
poll_initwait(&table); 
然后调用你的 
fop->poll(); 
从而将current加到某个等待队列(这里调用poll_wait()), 
并检查是否有效   //这里会做判断,实际上poll_wait函数本身没有休眠功能
如果无效就调用 
schedule_timeout(); 
去睡眠. 

事件发生后,schedule_timeout()回来,调用 
fop->poll(); 
检查到可以运行,就调用 
poll_freewait(&table); 

从而完成select系统调用. 

重要的是fop->poll()里面要检查是否就绪, 
如果是,要返回相应标志 

参见内核的函数: 
fs/select.c/do_select() 

引用: https://blog.csdn.net/gtkknd/article/details/7229527       
==============================================================
需要驱动所有按键:

struct key_desc all_keys[] = {
        [0] = {
            .name = "key1_up_eint0", 
            .irqno = IRQ_EINT(0),
            .gpio = S5PV210_GPH0(0),
            .code = KEY_UP,
            .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

        },
        [1] = {
            .name = "key2_down_eint1", 
            .irqno = IRQ_EINT(1),
            .gpio = S5PV210_GPH0(1),
            .code = KEY_DOWN,
            .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

        },
        [2] = {
            .name = "key3_left_eint2", 
            .irqno = IRQ_EINT(2),
            .gpio = S5PV210_GPH0(2),
            .code = KEY_LEFT,
            .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

        },
        [3] = {
            .name = "key4_right_eint3", 
            .irqno = IRQ_EINT(3),
            .gpio = S5PV210_GPH0(3),
            .code = KEY_LEFT,
            .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

        },
        
};

//在申请中断的时候,传递不同的值
ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]);

 完整代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>

#include <asm/uaccess.h>

// 设计一个对象--描述按键产生的数据包
struct key_event{
	int code; //什么按键: 左键, 回车键
	int value; // 按键的状态: 按下/抬起 (1/0)
};

//设计一个描述按键的对象: 名字, irqno, gpio, 按键值,触发方式
struct key_desc{
	char *name;
	int irqno;
	int gpio;
	int code;
	int flags;// 触发方式
};


struct key_desc all_keys[] = {
		[0] = {
			.name = "key1_up_eint0", 
			.irqno = IRQ_EINT(0),
			.gpio = S5PV210_GPH0(0),
			.code = KEY_UP,
			.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

		},
		[1] = {
			.name = "key2_down_eint1", 
			.irqno = IRQ_EINT(1),
			.gpio = S5PV210_GPH0(1),
			.code = KEY_DOWN,
			.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

		},
		[2] = {
			.name = "key3_left_eint2", 
			.irqno = IRQ_EINT(2),
			.gpio = S5PV210_GPH0(2),
			.code = KEY_LEFT,
			.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

		},
		[3] = {
			.name = "key4_right_eint3", 
			.irqno = IRQ_EINT(3),
			.gpio = S5PV210_GPH0(3),
			.code = KEY_LEFT,
			.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

		},
		
};



static int key_major = 0;
static struct class *key_cls;
static int testdata = 0x12345;

static wait_queue_head_t wq_head;
static int have_data; //表示当前是否有按键标志,


//相当于一个数据包--给用户的
static struct key_event event;


int key_drv_open (struct inode *inode, struct file *filp)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);

	// 通过文件路径可以得到inode
	// 通过得到次设备号可以区分不同的设备
	//int major = 	MAJOR(filp->f_path.dentry->d_inode->i_rdev);
	int major = imajor(filp->f_path.dentry->d_inode);
	int major2 = imajor(inode);

	int minor = iminor(filp->f_path.dentry->d_inode);
	int minor2 = iminor(inode);

	printk("major = %d, minor = %d\n", major, minor);
	printk("major2 = %d, minor2 = %d\n", major2, minor2);
	
	 memset(&event, 0, sizeof(struct key_event));
	have_data = 0; //为假--一开始都没有按键按下或者抬起


	
	return 0;
}

ssize_t key_drv_read(struct file *filp, char __user *buf,  size_t count, loff_t *fpos)
{
	
	int ret;
	//区分当前是阻塞还是非阻塞
	if((filp->f_flags & O_NONBLOCK) && !have_data)
	{
		return -EAGAIN;
	}

	// 在资源不可达的时候,进行休眠
	// 参数1---当前驱动中的等待队列头
	// 参数2--休眠的条件: 假的话就休眠,真就不休眠
	wait_event_interruptible(wq_head, have_data);

	ret  = copy_to_user(buf, &event, count);
	if(ret > 0)
	{
		printk("copy_to_user error\n");
		return -EFAULT;
	}
	//清零,以备下次充值
	memset(&event, 0, sizeof(struct key_event));
	have_data = 0; //没有数据了,等待下一次数据
	
	return count;
}

int key_drv_close(struct inode *inode, struct file *filp)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);

	
	return 0;
}

unsigned int key_drv_poll (struct file *filp, struct poll_table_struct *pts)
{
	//返回一个整数: 没有数据的时候返回0, 有数据的时候,返回POLLIN
	
	unsigned int mask = 0;

	// 将当前的等待队列头,注册到vfs层, 注意: poll_wait该函数不会导致休眠
	//参数1-文件对象--当前file
	//参数2--当前的等待队列头
	//参数3--当前传递过来的第三个参数
	poll_wait(filp, &wq_head, pts);

	/*select
		poll_initwait
	poll_wait  y/正常返回ret值  N/schedule_timeout(休眠)   		
	*/
	
	if(have_data)
		mask |= POLLIN;

	return mask;

}

// 4, 实现fops
const struct file_operations key_fops = {
	.owner = THIS_MODULE,
	.open = key_drv_open,
	.read = key_drv_read,
	.poll = key_drv_poll,
	.release =key_drv_close,
};

//中断处理方法
//参数1--当前产生的中断的号码
//参数2--request_irq传递过来的参数
irqreturn_t key_irq_handler(int irqno, void *dev_id)
{
	int *p = (int *)dev_id;
	//printk("-----------%s-------0x%x-----\n", __FUNCTION__, *p);
	//区分当前是哪个按键
	struct key_desc *pdesc = (struct key_desc *)dev_id;

	//区分按下还是抬起
	int value = gpio_get_value(pdesc->gpio);
	if(value)
	{
		//抬起
		printk("<kernel>--%s : release\n", pdesc->name);
		//填充值
		event.code = pdesc->code;
		event.value = 0;
		
	}else
	{
		//按下
		 printk("<kernel>--%s : pressed\n", pdesc->name);
		event.code = pdesc->code;
		event.value = 1;
	}

	have_data = 1;//表示有数据了
	wake_up_interruptible(&wq_head);
	
	
	
	return IRQ_HANDLED;
}
static int __init key_drv_init(void)
{

	int ret;
	// 1, 申请主设备号--动态申请主设备号
	key_major = register_chrdev(0, "key_dev", &key_fops);

	// 2, 创建设备节点
	key_cls = class_create(THIS_MODULE, "key_cls");
	device_create(key_cls, NULL,  MKDEV(key_major, 0),  NULL, "key0");

	// 3,  硬件初始化-- 映射地址或者中断申请
	
	//参数1--中断号码
			//获取中断号码的方法: 1,外部中断IRQ_EINT(x)  
			// 2, 头文件 #include <mach/irqs.h>  #include <plat/irqs.h>
	//参数2--中断的处理方法
	//参数3--中断触发方式:  高电平,低电平,上升沿,下降沿,双边沿
	/*
		#define IRQF_TRIGGER_NONE	0x00000000
		#define IRQF_TRIGGER_RISING	0x00000001
		#define IRQF_TRIGGER_FALLING	0x00000002
		#define IRQF_TRIGGER_HIGH	0x00000004
		#define IRQF_TRIGGER_LOW	0x00000008
	*/
	//参数4--中断的描述-自定义字符串
	//参数5--传递个参数2的任意数据
	//返回值0表示正确
	int i;
	int irqno;
	int flags;
	char *name;
	for(i=0; i<ARRAY_SIZE(all_keys); i++)
	{
		name =  all_keys[i].name;
		irqno = all_keys[i].irqno;
		flags = all_keys[i].flags;
		ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]);
		if(ret != 0)
		{
			printk("request_irq error\n");
			return ret;
		}
	}
	
	
	// 定义一个等待队列头,并且初始化
	init_waitqueue_head(&wq_head);
	
	
	return 0;
}



static void __exit key_drv_exit(void)
{
	// 释放中断
	//参数1--中断号码
	//参数5--和request_irq第5个参数保持一致
	int i;
	int irqno;
	for(i=0; i<ARRAY_SIZE(all_keys); i++)
	{
		irqno = all_keys[i].irqno;
		free_irq(irqno, &all_keys[i]);
	}
	
	device_destroy(key_cls, MKDEV(key_major, 0));
	class_destroy(key_cls);
	unregister_chrdev(key_major, "key_dev");
}


module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");








应用层app代码 


#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <poll.h>

#include <linux/input.h>

// 设计一个对象--描述按键产生的数据包
struct key_event{
	int code; //什么按键: 左键, 回车键
	int value; // 按键的状态: 按下/抬起 (1/0)
};


int main(int argc, char *argv[])
{

	int on;
	int ret;
	char kbd_buf[128];
	struct key_event data;
	
	//直接将驱动模块当做文件来操作
	int fd = open("/dev/key0", O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}

	struct pollfd pfd[2];

	pfd[0].fd = fd; //自己写的按键设备
	pfd[0].events = POLLIN; //监控读 (POLLIN|POLLOUT|POLLERR)

	pfd[1].fd = 0; //监控标准输入
	pfd[1].events = POLLIN;

	while(1)
	{
		//参数1--你需要监控的文件描述符的集合
		//参数2--监控的文件的个数
		//参数3--监控的时间--毫秒为单位,如果是负数,永久监控
		ret = poll(pfd, 2,  -1);
		if(ret < 0)
		{
			perror("poll");
			exit(1);
		}
		if(ret > 0)
		{

			//判断是哪个有数据
			if(pfd[1].revents & POLLIN)
			{
				//表示键盘是输入
				ret = read(0, kbd_buf, 128);
				//fgets(kbd_buf, 128,  stdin);
				kbd_buf[ret] = '\0';
				printf("kbd_buf = %s\n", kbd_buf);
				
			}
			if(pfd[0].revents & POLLIN)
			{
				//获取数据--不会阻塞
				ret  = read(pfd[0].fd, &data, sizeof(struct key_event));
				//解析包
				switch(data.code)
				{
					case KEY_UP:
						if(data.value)
						{
							printf("<app>---KEY_UP pressed\n");
						}else
						{
							printf("<app>---KEY_UP release\n");	
						}
						break;
					case KEY_DOWN:
						if(data.value)
						{
							printf("<app>---KEY_DOWN pressed\n");
						}else
						{
							printf("<app>---KEY_DOWN release\n");	
						}
						break;
					case KEY_LEFT:
						if(data.value)
						{
							printf("<app>---KEY_LEFT pressed\n");
						}else
						{
							printf("<app>---KEY_LEFT release\n");	
						}
						break;
					case KEY_RIGHT:
						if(data.value)
						{
							printf("<app>---KEY_RIGHT pressed\n");
						}else
						{
							printf("<app>---KEY_RIGHT release\n");	
						}
						break;

				}
			
			}
		}

	}	

	
	

	close(fd);


}

 if(pfd[1].revents & POLLIN) 中的&符号为位运算,实际上POLLIN是一个宏定义

其中的数刚好可以用&运算之后做判断是否为真

猜你喜欢

转载自blog.csdn.net/weixin_42471952/article/details/81607612