中断框架模型
一,按键驱动的初级编写框架
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)--------- 多路复用和异步信号通知模型