Linux驱动开发之按键驱动(1)--------- 阻塞和非阻塞模型

中断框架模型
在这里插入图片描述

一,按键驱动的初级编写框架


1,获取中断号


[ 1 ] 宏定义:(在2.6内核或者之前的内核没有支持设备树的情况下使用的)
在没有设备树的内核中,中断号定义为宏IRQ_EINT (中断号)
在这里插入图片描述
[ 2 ] 在设备树文件中添加结点信息
设备树文件通常放在目录/arch/arm/boot/dts/中,看soc的型号区分,这里不做详细介绍,主要是作者用的是2.6的内核,详细可参考博主后续的设备树教程。


2,申请中断


1.申请中断函数

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

头文件包含:#include <linux/interrupt.h>

参数
参数1:设备对应的中断号
参数2:中断对应的处理函数,其类型为typedef irqreturn_t (*irq_handler_t)(int, void *);
参数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:自定义的中断描述,主要是给用户查看的, cat /proc/interrupts
参数5:传递给参数2中函数指针的值

返回值:成功返回0,失败返回非0

2.释放中断请求
释放中断请求函数:

void free_irq(unsigned int irq, void *dev_id)

参数1:设备对应的中断号

参数2:与request_irq中第五个参数保持一致。


3,实现字符设备驱动框架


    // 1,设定一个全局的设备对象
	key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
	
	// 2,申请主设备号
	key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
	
	// 3,创建设备结点
	key_dev->cls = class_create(THIS_MODULE, "key_cls");
	key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major ,0), NULL, "key0");
	
	// 4,硬件初始化:--地址映射和中断申请
	key_dev->irqno = IRQ_EINT2; 
	
	ret = request_irq(key_dev->irqno, key_irq_handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "key5_eint2", NULL);
	if(ret != 0)
	{
		printk("request_irq error.\n");
	}
	key_dev->reg_base = ioremap(GPH0DATA, 8);  //#define GPH0DATA  0xE0200C04     //GHH0_2 (eint2data寄存器的物理地址)

4,驱动中将硬件所产生的数据传递给用户


【1】硬件如何获取数据
在这里插入图片描述

  • 读取GPH0_2的状态,即可判断按键是按下还是抬起,即读取GPH0DATA数据寄存器的值。
  • int value = readl(key_dev->reg_base) & (1<<2);

【2】驱动如何传递数据给用户

  • 在中断处理函数中根据读取到的按键引脚的数据去填充数据:
irqreturn_t key_irq_handler (int irq, void * dev_id)
{
	int value = readl(key_dev->reg_base) & (1<<2);
	if(value)
	{
		//按键抬起
		printk("key up\n");
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 0;
	}
	else
	{
		//按键按下
		printk("key press\n");
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 1;
	}
	return IRQ_HANDLED;
}
  • 然后在xxx_read()中将数据传递给用户
	ret = copy_to_user(buf, &key_dev->event, count);
	if(ret > 0)
	{
		printk("copy_to_user error.\n");
		return -EFAULT;
	}
	//传递给用户之后将数据清除
	memset(&key_dev->event, 0, sizeof(key_dev->event));

【3】用户如何拿到:编写应用程序调用read()函数即可。

利用阻塞和非阻塞IO模型实现按键驱动


1,阻塞


阻塞:当进程在读取外部设备的资源(数据),资源没有准备好,进程就会休眠。在Linux应用中,大部分函数接口都是阻塞型的,比如:

scanf();
read();
write();
accept();
.......

要实现休眠,驱动内部要实现三件事:
【1】将当前进程加入到等待队列头中:

add_wait_queue(wait_queue_head_t * q, wait_queue_t * wait);

【2】将当前进程状态设置成可中断模式

set_current_state(TASK_INTERRUPTIBLE);

【3】让出调度----->休眠

schedule(void);

更加智能的接口:等同于上面三个接口之和。

wait_event_interruptible(wq, condition);

如何去写代码
【1】等待队列头wait_queue_head_t
初始化等待队列头:init_waitqueue_head(wait_queue_head_t *q); //wait_queue_head_t为定义的等待队列头

【2】在需要等待(没有数据)的时候进行休眠

wait_event_interruptible(wq, condition);    //内部会构建一个等待队列项wait_queue_t

参数1:wq为等待队列头
参数2:条件,如果条件为真就不会等待,如果为假就会等待,可用标志位实现

【3】在有数据来的时候会将进程唤醒

wake_up_interruptible(wait_queue_head_t *q);

阻塞式按键驱动实现完整代码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <mach/irqs.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/poll.h>  

#define GPH0DATA  0xE0200C04     //GHH0_2 (eint2data寄存器的物理地址)
#define KEY_ENTER 28

//描述按键数据的对象
struct key_event{
	int code;  //表示按键的类型
	int value; //表示按键的按下还是抬起
};

//描述按键的信息
struct key_desc{
	unsigned int dev_major;
	struct class* cls;
	struct device* dev;
	int irqno;
	void *reg_base;
	struct key_event event;
	wait_queue_head_t wq_head;
	int key_state;   //标志位表示是否有数据
};
struct key_desc* key_dev;

irqreturn_t key_irq_handler (int irq, void * dev_id)
{
	printk("------%s----\n",__FUNCTION__);
	int value = readl(key_dev->reg_base) & (1<<2);
	if(value)
	{
		//按键抬起
		printk("key up\n");
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 0;
	}
	else
	{
		//按键按下
		printk("key press\n");
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 1;

	}
	//表示有数据,需要去唤醒等待队列,同时设置标志位
	wake_up_interruptible(&key_dev->wq_head);
	 key_dev->key_state = 1;
	 
	return IRQ_HANDLED;
}

ssize_t  key_drv_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
	int ret;
	//在等待数据的时候进行休眠
	wait_event_interruptible(key_dev->wq_head, key_dev->key_state);

	//表示有数据了
	ret = copy_to_user(buf, &key_dev->event, count);
	if(ret > 0)
	{
		printk("copy_to_user error.\n");
		return -EFAULT;
	}
	//传递给用户之后将数据清除
	memset(&key_dev->event, 0, sizeof(key_dev->event));

	//数据传递完成把标志位置回0
	 key_dev->key_state = 0;
	 
	return count;
}

ssize_t key_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	printk("-----%s----\n",__FUNCTION__);
	return 0;
}

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

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

const struct file_operations key_fops = {
	.open = key_drv_open,
	.read = key_drv_read,
	.write = key_drv_write,
	.release = key_drv_close,
};

static int __init key_drv_init(void)
{
	int ret;
	// 1,设定一个全局的设备对象
	key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
	
	// 2,申请主设备号
	key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
	
	// 3,创建设备结点
	key_dev->cls = class_create(THIS_MODULE, "key_cls");
	key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major ,0), NULL, "key0");
	
	// 4,硬件初始化:--地址映射和中断申请
	key_dev->irqno = IRQ_EINT2; 
	
	ret = request_irq(key_dev->irqno, key_irq_handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "key5_eint2", NULL);
	if(ret != 0)
	{
		printk("request_irq error.\n");
	}

	//硬件如何获取数据---读取gph0data寄存器
	key_dev->reg_base = ioremap(GPH0DATA, 8);
 
	//初始化等待队列头
	init_waitqueue_head(&key_dev->wq_head);
	return 0;
}

static void __exit key_drv_exit(void)
{
	iounmap(key_dev->reg_base);
	free_irq(key_dev->irqno, NULL);
	device_destroy(key_dev->cls, MKDEV(key_dev->dev_major,0));
	class_destroy(key_dev->cls);
	unregister_chrdev(key_dev->dev_major, "key_drv");
	kfree(key_dev);
}

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

2,非阻塞


非阻塞:在读写数据的时候,如果没有数据即立刻返回,并且返回一个错误码,类似于轮询,比较耗资源,用的比较少。

在这里插入图片描述
实现代码:只需要在上面的key_drv_read()函数中加两行代码即可

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 && !key_dev->key_state)
		return -EAGAIN;

	//在等待数据的时候进行休眠
	wait_event_interruptible(key_dev->wq_head, key_dev->key_state);

	//表示有数据了
	ret = copy_to_user(buf, &key_dev->event, count);
	if(ret > 0)
	{
		printk("copy_to_user error.\n");
		return -EFAULT;
	}
	//传递给用户之后将数据清除
	memset(&key_dev->event, 0, sizeof(key_dev->event));

	//数据传递完成把标志位置回0
	 key_dev->key_state = 0;
	return count;
}

可以继续参考下一篇博客:
Linux驱动开发之按键驱动(2)--------- 多路复用和异步信号通知模型

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值