Linux驱动开发_内核中断注册与工作队列
任务1: 完成上一次的作业
作业:
1. 编写LED灯驱动,在应用层控制LED灯。
2. 编写按键驱动,在应用层打印出按键值。
3. 在应用层实现按键控制LED灯。(程序里需要安装两个驱动)
任务2: 内核中断处理(按键为例)
裸机的中断(STM32为例):
1. 中断线
2. 中断号(设置优先级、使能)
3. 中断服务函数
4. 建立中断向量表(是中断入口函数地址)
Linux操作系统下中断:
1. 编写中断服务函数
2. 注册中断 request_irq
3. 注销中断 free_irq
4. 获取中断号(通过GPIO口编号进行获取)
5. 产生中断的属性: 边沿触发。
6. 中断向量表(在Linux内核不需要写,它是在UBOOT阶段建立的)
Linux系统下中断处理
1. 内核中断服务函数里不能出现会导致系统休眠的函数。比如: mdelay
2. 内核中断处理分为上文和下文(真正做事情的地方)。
3. 内核中断下文一般使用工作队列或者内核的微线程实现。
4. 内核中断上文一般就调度工作队列或者内核微线程。
注明:工作队列和内核微线程里可以使用休眠函数。
查看内核所有中断的信息: [root@tiny4412 ]#cat /proc/interrupts
任务3: 工作队列
工作队列是将任务推后执行的一种机制,什么时候执行? CPU空闲的时候执行。
工作: 就是一个结构体,结构体里保存了工作对应的函数(函数指针)。
队列: 是一个链表形式,每一个链表的节点就是一个工作结构体。
这个队列由内核的一个线程轮询执行,当内核执行了工作之后,该工作结构体就会从链表节点里删除掉。
注意事项: 如果之前加入的工作已经在工作队列里,还没有被执行,这个时候不能再重复添加。
在Linux内核里有默认的工作队列: 共享工作队列。 也可以自己去创建自己的队列。
练习:
1. 将上课代码全部写一遍: 作业、中断、工作队列
2. 试着使用内核定时器给按键进行消抖。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
static struct work_struct key_work; /*工作结构体*/
static struct KEY_INFO *key_p=NULL;
/*定义结构体,保存一个按键的所有信息*/
static struct KEY_INFO
{
int gpio; /*GPIO口编号*/
int flag; /*触发边沿标志*/
int key; /*按键值*/
char *name;/*中断的名称*/
int irq; /*中断号*/
};
/*初始化按键信息*/
static struct KEY_INFO key_buff[]=
{
{EXYNOS4_GPX3(2),IRQ_TYPE_EDGE_BOTH,0x1,"tiny4412_key1"},
{EXYNOS4_GPX3(3),IRQ_TYPE_EDGE_BOTH,0x2,"tiny4412_key2"},
{EXYNOS4_GPX3(4),IRQ_TYPE_EDGE_BOTH,0x3,"tiny4412_key3"},
{EXYNOS4_GPX3(5),IRQ_TYPE_EDGE_BOTH,0x4,"tiny4412_key4"},
};
/*工作处理函数*/
static void tiny4412_work_func(struct work_struct *work)
{
static int key_state=0;
if(key_p==NULL)return;
msleep(50); /*消抖50ms*/
if(!gpio_get_value(key_p->gpio))
{
key_state=1; /*表示按键已经按下*/
printk("按键值:0x%x\n",key_p->key);
}
else
{
if(key_state)
{
key_state=0;/*表示按键已经松开*/
printk("按键值:0x%x\n",key_p->key|0x80);
}
}
}
/*按键的中断服务函数*/
static irqreturn_t tiny4412_keyirq_handler(int irq, void *dev)
{
key_p=(struct KEY_INFO *)dev;
/*调度工作队列(系统共享工作队列)*/
schedule_work(&key_work);
return IRQ_HANDLED;
}
static int __init tiny4412_interrupt_dev_init(void)
{
int i;
/*初始化工作*/
INIT_WORK(&key_work,tiny4412_work_func);
for(i=0;i<sizeof(key_buff)/sizeof(key_buff[0]);i++)
{
/*获取中断编号*/
key_buff[i].irq=gpio_to_irq(key_buff[i].gpio);
/*注册中断*/
request_irq(key_buff[i].irq,tiny4412_keyirq_handler,key_buff[i].flag,key_buff[i].name,&key_buff[i]);
printk("%s的中断号:%d\n",key_buff[i].name,key_buff[i].irq);
}
printk("中断:驱动安装成功!\n");
return 0;
}
static void __exit tiny4412_interrupt_dev_exit(void)
{
int i;
for(i=0;i<sizeof(key_buff)/sizeof(key_buff[0]);i++)
{
/*释放中断号*/
free_irq(key_buff[i].irq,&key_buff[i]);
}
/*销毁工作队列*/
destroy_workqueue(&key_work);
printk("中断:驱动卸载成功!\n");
}
module_init(tiny4412_interrupt_dev_init);
module_exit(tiny4412_interrupt_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("tiny4412 wbyq");
- 点赞
- 收藏
- 关注作者
评论(0)