一、什么是中断
一种硬件上的通知机制,用来通知CPU发生了某种需要立即处理的事件
分为:
- 内部中断 CPU执行程序的过程中,发生的一些硬件出错、运算出错事件(如分母为0、溢出等等),不可屏蔽
- 外部中断 外设发生某种情况,通过一个引脚的高、低电平变化来通知CPU (如外设产生了数据、某种处理完毕等等)
二、中断处理原理
任何一种中断产生,CPU都会暂停当前执行的程序,跳转到内存固定位置执行一段程序,该程序被称为总的中断服务程序,在该程序中区分中断源,然后进一步调用该中断源对应的处理函数。
中断源对应的处理函数被称为分中断处理程序,一般每一个分中断处理程序对应一个外设产生的中断
写驱动时,如果外设有中断,则需要编写一个函数(分中断处理程序)来处理这种中断
三、中断接口
3.1 中断申请
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
/*
参数:
irq:所申请的中断号
handler:该中断号对应的中断处理函数
flags:中断触发方式或处理方式
触发方式:IRQF_TRIGGER_NONE //无触发
IRQF_TRIGGER_RISING //上升沿触发
IRQF_TRIGGER_FALLING //下降沿触发
IRQF_TRIGGER_HIGH //高电平触发
IRQF_TRIGGER_LOW //低电平触发
处理方式:
IRQF_DISABLED //用于快速中断,处理中屏蔽所有中断
IRQF_SHARED //共享中断
name:中断名 /proc/interrupts
dev:传递给中断例程的参数,共享中断时用于区分那个设备,一般为对应设备的结构体地址,无共享中断时写NULL
返回值:成功:0 失败:错误码
*/
3.2 中断释放
void free_irq(unsigned int irq, void *dev_id);
/*
功能:释放中断号
参数:
irq:设备号
dev_id:共享中断时用于区分那个设备一般强转成设备号,无共享中断时写NULL
*/
3.3 中断处理函数原型
typedef irqreturn_t (*irq_handler_t)(int, void *);
/*
参数:
int:中断号
void*:对应的申请中断时的dev_id
返回值:
typedef enum irqreturn irqreturn_t; //中断返回值类型
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
返回IRQ_HANDLED表示处理完了,返回IRQ_NONE在共享中断表示不处理
*/
四、按键驱动
按键原理图:
exynos4412-fs4412.dts中增加节点
mykey2_node {
compatible = "mykey2,key2";
key2-gpio = <&gpx1 1 0>;
interrupt-parent = <&gpx1>;
interrupts = <1 3>;
};
按键驱动函数
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include "fs4412_key.h" //自己写的.h用“”引用,库用<>引用
#include <linux/delay.h>
int major = 11; //主设备号
int minor = 0; //次设备号
int fs4412key2_num = 1; //设备数量
struct fs4412key2_dev //led设备结构体
{
struct cdev mydev; //设备结构体
int gpio; //设备gpio成员变量
int irqno; //中断
struct keyvalue data; //按键存储的数据
int newflag; //新数据到来的标志位
spinlock_t lock; //自旋锁
wait_queue_head_t rq; //读忙等待队列
};
struct fs4412key2_dev *pgmydev = NULL; //定义一个设备结构体变量,用于调用结构体成员
int fs4412key2_open(struct inode *pnode,struct file *pfile) //打开文件函数
{ //inode类型结构体中i_cdev是mydev的地址
pfile->private_data = (void *) (container_of(pnode->i_cdev,struct fs4412key2_dev,mydev));//知道成员地址可以得出结构体地址
return 0;
}
int fs4412key2_close(struct inode *pnode,struct file *pfile)
{
return 0;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;
int size = 0;
int ret = 0;
if(count < sizeof(struct keyvalue))
{
printk("expect read size is invalde\n");
return -1;
}
spin_lock(&pmydev->lock); //上锁
if(!pmydev->newflag) //无数据时进入
{
if(pfile->f_flags & O_NONBLOCK)
{ //非阻塞
spin_unlock(&pmydev->lock);
printk("O_NONBLOCK NO Data Read\n");
return -1;
}
else
{ //阻塞
spin_unlock(&pmydev->lock);
ret = wait_event_interruptible(pmydev->rq,pmydev->newflag == 1); //等待条件是pmydev->newflag == 1也就是有数据到来
if(ret)
{
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
spin_lock(&pmydev->lock);
}
}
if(count > sizeof(struct keyvalue)) //对读取数据的长度做一个限制
{
size = sizoef(struct keyvalue);
}
else
{
size = count;
}
ret = copy_to_user(puser,&pmydev->data,size); //将内核数据拷贝到用户
if(ret)
{
spin_unlock(&pmydev->lock);
printk("copy_to_user failed\n");
return -1;
}
pmydev->newflag = 0; //将数据标志物清零,为下次进入做准备
spin_unlock(&pmydev->lock); //开锁
return size;
}
unsigned int mychar_poll(struct file *pfile,poll_table *ptb) //决定什么时候能读数据,这儿好像没用着。app上没些poll函数
{
struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb); //将读队列加入到表里,但是未休眠
spin_lock(&pmydev->lock); //上锁
if(pmydev->newflag) //当newflag为真是表示有数据,可读打开
{
mask |= POLLIN | POLLRDNORM; //读打开
}
spin_unlock(&pmydev->lock);
return mask;
}
struct file_operations myops = { //设备的操作函数,自己写的子函数必须在这儿与内核函数关联起来才能被调用
.owner = THIS_MODULE,
.open = fs4412key2_open,
.release = fs4412key2_close,
.read = mychar_read,
.poll = mychar_poll,
};
irqreturn_t key2_irq_handle(int no,void *arg) //按键中断服务函数
{
struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;
int status1 = 0;
int status2 = 0;
int status = 0;
status1 = gpio_get_value(pmydev->gpio); //消抖
mdelay(1);
status2 = gpio_get_value(pmydev->gpio);
if(status1 != status2)
{
return IRQ_NONE;
}
status = status1;
spin_lock(&pmydev->lock); //上锁
if(status == pmydev->data.status)
{
spin_unlock(&pmydev->lock);
return IRQ_NONE;
}
pmydev->data.code = KEY2;
pmydev->data.status = status;
pmydev->newflag = 1;
spin_unlock(&pmydev->lock);
wake_up(&pmydev->rq);
return IRQ_HANDLED;
}
int __init fs4412key2_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor); //将主次设备号合成一个32位的设备号
int ret = 0;
struct device_node *pnode = NULL; //定义一个变量用于存储设备树中的一个节点
pnode = of_find_node_by_path ("/mykey2_node"); //从设备树获得key2节点
if(NULL == pnode)
{
printk("fialed of_find_node_by_path\n");
return -1;
}
pgmydev = (struct fs4412key2_dev *)kmalloc(sizeof(struct fs4412key2_dev),GFP_KERNEL);//给设备结构体申请一块内存,kmalloc是申请小内存效率高。GFP_KERNEL是可以进行忙等待,因为这个是任务上下文
if(NULL == pgmydev) //申请失败
{
printk("kmalloc failed\n");
return -1;
}
pgmydev->gpio = of_get_named_gpio(pnode,"key2-gpio",0); //从设备树中提取gpio口
pgmydev->irqno = irq_of_parse_and_map(pnode,0); //获得设备树中的中断号并进行映射
/*申请设备号*/
ret = register_chrdev_region(devno,fs4412key2_num,"fs4412key2");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,fs4412key2_num,"fs4412key2"); //手动申请失败时自动申请
if(ret)
{
printk("get devno failed\n");
kfree(pgmydev); //申请失败时释放掉设备结构体
return -1;
}
major = MAJOR(devno);
}
/*给struct cdev对象指定操作函数集*/
cdev_init(&pgmydev->mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
pgmydev->mydev.owner = THIS_MODULE;
cdev_add(&pgmydev->mydev,devno,fs4412key2_num);
init_waitqueue_head(&pgmydev->rq); //对等待队列头做初始化
spin_lock_init(&pgmydev->lock); //对自旋锁做初始化,因为是异常上下文所以用自旋锁,他可以忙等待
ret = request_irq(pgmydev->irqno,key2_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"fs4412key2",pgmydev);//中断申请函数,初始化要放在锁初始化后面,要不然进入中断服务程序锁没初始化会出问题,
if(ret)
{
printk("request_irq failed\n");
kfree(pgmydev);
pgmaydev = NULL;
return -1;
}
return 0;
}
void __exit fs4412key2_exit(void)
{
dev_t devno = MKDEV(major,minor);
free_irq(pgmydev->irqno,pgmydev);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,fs4412key2_num);
kfree(pgmydev);
pgmydev = NULL;
}
MODULE_LICENSE("GPL");
module_init(fs4412key2_init);
module_exit(fs4412key2_exit);
fs4412_key.h
#ifndef FS4412_KEY_H
#define FS4412_KEY_H
enum KEYCODE
{
KEY2 = 1002,
KEY3,
KEY4,
};
enum KEY_STATUS
{
KEY_DOWN = 0,
KEY_UP,
};
struct keyvalue
{
int code;
int status;
};
#endif
app
#include <stdio.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <unistd.h>
#include "leddrv.h"
#include <sys/stat.h>
#include <sys/ioctl.h>
int main(int argc,char *argv[])
{
int fd = -1;
if(argc < 2)
{
printf("The argument is too few\n");
return -1;
}
fd = open(argv[1],O_RDONLY); //
if(fd < 0)
{
printf("open %s failed\n",argv[1]);
return 3;
}
while((ret = read(fd,&keydata,sizeof(keydata))) == sizeof(keydata))
{
if(keydata.status == KEY_DOWN)
{
printf("Key2 is down!\n");
}
else
{
printf("Key2 is up!\n");
}
}
close(fd);
fd = -1;
return 0;
}
一、上半部与下半部
起源:
- 中断处理程序执行时间过长引起的问题
- 有些设备的中断处理程序必须要处理一些耗时操作
二、下半部机制之tasklet ---- 基于软中断(异常上下文)
6.1 结构体
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long); //重点关注
unsigned long data; //重点关注
};
6.2 定义tasklet的中断底半部处理函数
void tasklet_func(unsigned long data);
6.3 初始化tasklet
DECLARE_TASKLET(name, func, data);
/*
定义变量并初始化
参数:name:中断底半部tasklet的名称
Func:中断底半部处理函数的名字
data:给中断底半部处理函数传递的参数
*/
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data)
6.4 调度tasklet(上半部运行完后调用下面这个函数,运行上面那个*func函数指针指向的函数 )
void tasklet_schedule(struct tasklet_struct *t)
//参数:t:tasklet的结构体
三、按键驱动之tasklet版
struct fs4412key2_dev //led设备结构体
{
struct cdev mydev; //设备结构体
int gpio; //设备gpio成员变量
int irqno; //中断
struct keyvalue data; //按键存储的数据
int newflag; //新数据到来的标志位
spinlock_t lock; //自旋锁
wait_queue_head_t rq; //读忙等待队列
struct tasklet_struct tsk;
};
在init函添加
int __init fs4412key2_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor); //将主次设备号合成一个32位的设备号
int ret = 0;
struct device_node *pnode = NULL; //定义一个变量用于存储设备树中的一个节点
pnode = of_find_node_by_path ("/mykey2_node"); //从设备树获得key2节点
if(NULL == pnode)
{
printk("fialed of_find_node_by_path\n");
return -1;
}
pgmydev = (struct fs4412key2_dev *)kmalloc(sizeof(struct fs4412key2_dev),GFP_KERNEL);//给设备结构体申请一块内存,kmalloc是申请小内存效率高。GFP_KERNEL是可以进行忙等待,因为这个是任务上下文
if(NULL == pgmydev) //申请失败
{
printk("kmalloc failed\n");
return -1;
}
pgmydev->gpio = of_get_named_gpio(pnode,"key2-gpio",0); //从设备树中提取gpio口
pgmydev->irqno = irq_of_parse_and_map(pnode,0); //获得设备树中的中断号并进行映射
/*申请设备号*/
ret = register_chrdev_region(devno,fs4412key2_num,"fs4412key2");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,fs4412key2_num,"fs4412key2"); //手动申请失败时自动申请
if(ret)
{
printk("get devno failed\n");
kfree(pgmydev); //申请失败时释放掉设备结构体
return -1;
}
major = MAJOR(devno);
}
/*给struct cdev对象指定操作函数集*/
cdev_init(&pgmydev->mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
pgmydev->mydev.owner = THIS_MODULE;
cdev_add(&pgmydev->mydev,devno,fs4412key2_num);
init_waitqueue_head(&pgmydev->rq); //对等待队列头做初始化
spin_lock_init(&pgmydev->lock); //对自旋锁做初始化,因为是异常上下文所以用自旋锁,他可以忙等待
tasklet_init(&pgmydev->tsk,bottom_irq_func,(unsigned long)pgmydev);//
ret = request_irq(pgmydev->irqno,key2_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"fs4412key2",pgmydev);//中断申请函数,初始化要放在锁初始化后面,要不然进入中断服务程序锁没初始化会出问题,
if(ret)
{
printk("request_irq failed\n");
kfree(pgmydev);
pgmaydev = NULL;
return -1;
}
return 0;
}
上下中断函数
irqreturn_t key2_irq_handle(int no,void *arg) //上
{
struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;
tasklet_schedule(&pmydev->tsk);//进入下半部
}
void bottom_irq_func(unsigned long *arg) //下半部:一般处理一些耗时操作
{
struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;
int status1 = 0;
int status2 = 0;
int status = 0;
status1 = gpio_get_value(pmydev->gpio); //消抖
mdelay(1);
status2 = gpio_get_value(pmydev->gpio);
if(status1 != status2)
{
return;
}
status = status1;
spin_lock(&pmydev->lock); //上锁
if(status == pmydev->data.status)
{
spin_unlock(&pmydev->lock);
return;
}
pmydev->data.code = KEY2;
pmydev->data.status = status;
pmydev->newflag = 1;
spin_unlock(&pmydev->lock);
wake_up(&pmydev->rq);
return;
}
四、下半部机制之workqueue ----- 基于内核线程(任务上下文)
8.1 工作队列结构体:
typedef void (*work_func_t)(struct work_struct *work)
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
8.2 定义工作队列底半部处理函数
void work_queue_func(struct work_struct *work);
8.3 初始化工作队列
struct work_struct work_queue;
初始化:绑定工作队列及工作队列的底半部处理函数
INIT_WORK(struct work_struct * pwork, _func) ;
参数:pwork:工作队列
func:工作队列的底半部处理函数
8.4 工作队列的调度函数
bool schedule_work(struct work_struct *work);
五、按键驱动之workqueue版
struct fs4412key2_dev //led设备结构体
{
struct cdev mydev; //设备结构体
int gpio; //设备gpio成员变量
int irqno; //中断
struct keyvalue data; //按键存储的数据
int newflag; //新数据到来的标志位
spinlock_t lock; //自旋锁
wait_queue_head_t rq; //读忙等待队列
// struct tasklet_struct tsk;
struct work_struct wk;
};
int __init fs4412key2_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor); //将主次设备号合成一个32位的设备号
int ret = 0;
struct device_node *pnode = NULL; //定义一个变量用于存储设备树中的一个节点
pnode = of_find_node_by_path ("/mykey2_node"); //从设备树获得key2节点
if(NULL == pnode)
{
printk("fialed of_find_node_by_path\n");
return -1;
}
pgmydev = (struct fs4412key2_dev *)kmalloc(sizeof(struct fs4412key2_dev),GFP_KERNEL);//给设备结构体申请一块内存,kmalloc是申请小内存效率高。GFP_KERNEL是可以进行忙等待,因为这个是任务上下文
if(NULL == pgmydev) //申请失败
{
printk("kmalloc failed\n");
return -1;
}
pgmydev->gpio = of_get_named_gpio(pnode,"key2-gpio",0); //从设备树中提取gpio口
pgmydev->irqno = irq_of_parse_and_map(pnode,0); //获得设备树中的中断号并进行映射
/*申请设备号*/
ret = register_chrdev_region(devno,fs4412key2_num,"fs4412key2");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,fs4412key2_num,"fs4412key2"); //手动申请失败时自动申请
if(ret)
{
printk("get devno failed\n");
kfree(pgmydev); //申请失败时释放掉设备结构体
return -1;
}
major = MAJOR(devno);
}
/*给struct cdev对象指定操作函数集*/
cdev_init(&pgmydev->mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
pgmydev->mydev.owner = THIS_MODULE;
cdev_add(&pgmydev->mydev,devno,fs4412key2_num);
init_waitqueue_head(&pgmydev->rq); //对等待队列头做初始化
spin_lock_init(&pgmydev->lock); //对自旋锁做初始化,因为是异常上下文所以用自旋锁,他可以忙等待
// tasklet_init(&pgmydev->tsk,bottom_irq_func,(unsigned long)pgmydev);//tasklet的
INIT_WORK(&pgmydev->wk,);
ret = request_irq(pgmydev->irqno,key2_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"fs4412key2",pgmydev);//中断申请函数,初始化要放在锁初始化后面,要不然进入中断服务程序锁没初始化会出问题,
if(ret)
{
printk("request_irq failed\n");
kfree(pgmydev);
pgmaydev = NULL;
return -1;
}
return 0;
}
irqreturn_t key2_irq_handle(int no,void *arg) //上
{
struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;
// tasklet_schedule(&pmydev->tsk);//进入下半部
schedule_work(&pmydev->wk);
return IRQ_HANDLED;
}
//void bottom_irq_func(unsigned long *arg) //tasklet的
void bottom_irq_func(struct work_struct *pwk) //下半部:一般处理一些耗时操作 ,参数改
{
//struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg; //tasklet的
struct fs4412key2_dev *pmydev = container_of(pwk,struct fs4412key2_dev,wk); //改
int status1 = 0;
int status2 = 0;
int status = 0;
status1 = gpio_get_value(pmydev->gpio); //消抖
mdelay(1);
status2 = gpio_get_value(pmydev->gpio);
if(status1 != status2)
{
return;
}
status = status1;
spin_lock(&pmydev->lock); //上锁
if(status == pmydev->data.status)
{
spin_unlock(&pmydev->lock);
return;
}
pmydev->data.code = KEY2;
pmydev->data.status = status;
pmydev->newflag = 1;
spin_unlock(&pmydev->lock);
wake_up(&pmydev->rq);
return;
}
六、下半部机制比较
任务机制
workqueue ----- 内核线程 能睡眠 运行时间无限制
异常机制 ------- 不能睡眠 下半部执行时间不宜太长( < 1s)
软中断 ---- 接口不方便
tasklet ----- 无具体延后时间要求时
定时器 -----有具体延后时间要求时