内核驱动篇八--中断处理
一、什么是中断
一种硬件上的通知机制,用来通知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在共享中断表示不处理
*/
四、按键驱动
按键原理图:(基于华清远见的FS4412开发板)
exynos4412-fs4412.dts中增加节点
mykey2_node {
compatible = "mykey2,key2";
key2-gpio = <&gpx1 1 0>;
interrupt-parent = <&gpx1>;
interrupts = <1 3>;
};
例子代码:
dev_key.h:
#ifndef DEVKEY_H
#define DEVKEY_H
enum KEYCODE
{
KEY2 = 1002,
KEY3,
KEY4,
};
enum KEY_STATUS
{
KEY_DOWN = 0,
KEY_UP,
};
struct key_value
{
int code; //哪一个按键
int status;
};
#endif
dev_key.c:
/*************************************************************************
> File Name: mykey.c
> Author: xiuchengzhen
> CSDN: xiuchengzhen.blog.csdn.net
> Created Time: Tue 10 May 2022 07:29:52 PM PDT
************************************************************************/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include "dev_key.h"
#define BUFSIZE 100 //模拟内核空间大小
int major = 11; //主设备号
int minor = 0; //次设备号
int dev_num = 1; //设备数量
struct pdev
{
struct cdev keydev; //设备结构体
int gpio; //使用的引脚号
int irq; //使用的中断号
struct key_value data; //记录按键状态
int newflag; //触发中断置1,没有置0
wait_queue_head_t dr; //等待读队列
spinlock_t lock; //自旋琐
};
struct pdev *pkeydev = NULL; //设备结构体
int keydev_open(struct inode *pnode, struct file *pfile) //打开设备
{
pfile->private_data = (void *)(container_of(pnode->i_cdev,struct pdev,keydev)); //获取设备结构体地址
return 0;
}
int keydev_release(struct inode *pnode, struct file *pfile) //关闭设备
{
return 0;
}
ssize_t keydev_read(struct file *pfile, char __user *user_buf, size_t user_size, loff_t *seek) //读设备
{
int size; //读取的数据量
int ret;
struct pdev *gkeydev = (struct pdev *)pfile->private_data;
if(user_size < sizeof(struct key_value))
{
printk("expect read size is invalid\n");
return -1;
}
spin_lock(&gkeydev->lock);
if(!gkeydev->newflag)
{
if(pfile->f_flags & O_NONBLOCK) //判断是否为阻塞模式
{
spin_unlock(&gkeydev->lock);
printk("no data read!\n");
return -1;
}
else
{
ret = wait_event_interruptible(gkeydev->dr,gkeydev->newflag == 1); //将信号加入等待队列
if(ret) //判断是否为信号唤醒
{
spin_unlock(&pkeydev->lock);
printk("sigal wake!\n");
return -ERESTARTSYS;
}
}
}
if(user_size > sizeof(struct key_value)) //量定读取多少
{
size = sizeof(struct key_value);
}
else
{
size = user_size;
}
ret = copy_to_user(user_buf, &gkeydev->data, size); //读取数据
if(ret)
{
spin_unlock(&pkeydev->lock);
printk("copy_to_user1 fail!");
return -1;
}
pkeydev->newflag = 0;
spin_unlock(&pkeydev->lock);
return size;
}
unsigned int keydev_poll(struct file *pfile, poll_table *wait)
{
struct pdev *gkeydev = (struct pdev *)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile, &gkeydev->dr, wait); //将读等待与写等待加入表内
spin_lock(&gkeydev->lock);
if(gkeydev->newflag) //判断是否可读
{
mask |= POLLIN | POLLRDNORM;
}
spin_unlock(&pkeydev->lock);
return mask;
}
struct file_operations fops ={
.owner = THIS_MODULE,
.open = keydev_open,
.release = keydev_release,
.read = keydev_read,
.poll = keydev_poll,
};
irqreturn_t key_handler(int nu, void *arg)
{
struct pdev *gkeydev = (struct pdev *)arg;
int status = 0;
gkeydev->data.status = gpio_get_value(pkeydev->gpio);
mdelay(1);
status = gpio_get_value(pkeydev->gpio);
if(status != gkeydev->data.status)
{
return IRQ_NONE;
}
if(gkeydev->data.status != KEY_DOWN)
{
return IRQ_NONE;
}
spin_lock(&gkeydev->lock);
gkeydev->data.code = KEY3;
gkeydev->data.status = KEY_DOWN;
gkeydev->newflag = 1;
spin_unlock(&gkeydev->lock);
wake_up(&gkeydev->dr);
return IRQ_HANDLED;
}
int __init mykey_init(void)
{
dev_t devno = MKDEV(major, minor);
int ret = -1;
struct device_node *key_node = NULL;
/* 申请设备号 */
if((ret = register_chrdev_region(devno, dev_num, "mykey")) < 0)
{
ret = alloc_chrdev_region(&devno, minor, dev_num, "mykey");
if(ret < 0)
{
printk("alloc_chrdev_region fail!\n");
return -1;
}
major = MAJOR(devno);
}
/* 设备分配内存空间 */
pkeydev = (struct pdev *)kmalloc(sizeof(struct pdev), GFP_KERNEL);
if(pkeydev == NULL)
{
unregister_chrdev_region(devno, dev_num);
printk("pkeydev malloc fail!\n");
return -1;
}
/* 设备初始化 */
cdev_init(&pkeydev->keydev, &fops);
pkeydev->keydev.owner = THIS_MODULE;
/* 设备添加 */
cdev_add(&pkeydev->keydev, devno, dev_num);
/* 等待队列初始化 */
init_waitqueue_head(&pkeydev->dr); //初始化读等待队列头
/* 自旋琐初始化 */
spin_lock_init(&pkeydev->lock); //初始化自旋锁
/* 得到结点 */
key_node = of_find_node_by_path("/mykey3_node");
if(key_node == NULL)
{
unregister_chrdev_region(devno, dev_num);
cdev_del(&pkeydev->keydev);
kfree(pkeydev);
pkeydev = NULL;
return -1;
}
/* 获取按键引脚号*/
pkeydev->gpio = of_get_named_gpio(key_node, "key3-gpio", 0); //引脚号
pkeydev->irq = irq_of_parse_and_map(key_node, 0); //中断号
/* 按键中断初始化 */
ret = request_irq(pkeydev->irq, key_handler, IRQF_TRIGGER_FALLING,"key_irq", pkeydev);
if(ret < 0)
{
unregister_chrdev_region(devno, dev_num);
cdev_del(&pkeydev->keydev);
kfree(pkeydev);
pkeydev = NULL;
return -1;
}
return 0;
}
void __exit mykey_exit(void)
{
dev_t devno = MKDEV(major, minor);
/* 注销按键中断 */
free_irq(pkeydev->irq, pkeydev);
/* 注销设备 */
cdev_del(&pkeydev->keydev);
/* 注销设备号 */
unregister_chrdev_region(devno, dev_num);
/* 释放内存 */
kfree(pkeydev);
pkeydev = NULL;
}
MODULE_LICENSE("GPL");
module_init(mykey_init);
module_exit(mykey_exit);
key_app.c:
/*************************************************************************
> File Name: test_app.c
> Author: xiuchengzhen
> CSDN: xiuchengzhen.blog.csdn.net
> Created Time: Mon 16 May 2022 03:57:32 AM PDT
************************************************************************/
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
#include "dev_key.h"
int main(int argc, const char *argv[])
{
struct key_value key3;
int ret;
if(argc < 2)
{
printf("Run without file!\n");
return -1;
}
/* 打开设备文件 */
int fd = open("/dev/mykey", O_RDWR);
if(fd < 0)
{
perror("open");
return -1;
}
/* 向设备内核空间读数据 */
fd_set readfds;
while(1)
{
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
ret = select(fd+1, &readfds, NULL, NULL, NULL);
if(ret < 0)
{
if(errno == EINTR)
continue;
else
{
printf("select error!\n");
break;
}
}
if(FD_ISSET(fd, &readfds))
{
ret = read(fd, &key3, sizeof(struct key_value));
if(ret < 0)
{
perror("read");
close(fd);
return -1;
}
if(key3.status == 0)
{
printf("key3 on!\n");
}
else
{
printf("key3 off!\n");
}
}
}
/* 关闭设备 */
close(fd);
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
void tasklet_schedule(struct tasklet_struct *t)
//参数:t:tasklet的结构体
七、下半部机制之workqueue ----- 基于内核线程
7.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
};
7.2 定义工作队列底半部处理函数
void work_queue_func(struct work_struct *work);
7.3 初始化工作队列
struct work_struct work_queue;
- 初始化:绑定工作队列及工作队列的底半部处理函数
INIT_WORK(struct work_struct * pwork, _func) ;
- 参数:pwork:工作队列
- func:工作队列的底半部处理函数
7.4 工作队列的调度函数
bool schedule_work(struct work_struct *work);
八、按键驱动之workqueue版
dev_key_work.c
:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include "dev_key.h"
#define BUFSIZE 100 //模拟内核空间大小
int major = 11; //主设备号
int minor = 0; //次设备号
int dev_num = 1; //设备数量
struct pdev
{
struct cdev keydev; //设备结构体
int gpio; //使用的引脚号
int irq; //使用的中断号
struct key_value data; //记录按键状态
int newflag; //触发中断置1,没有置0
wait_queue_head_t dr; //等待读队列
spinlock_t lock; //自旋琐
struct work_struct work; //下半部机制处理
};
struct pdev *pkeydev = NULL; //设备结构体
int keydev_open(struct inode *pnode, struct file *pfile) //打开设备
{
pfile->private_data = (void *)(container_of(pnode->i_cdev,struct pdev,keydev)); //获取设备结构体地址
return 0;
}
int keydev_release(struct inode *pnode, struct file *pfile) //关闭设备
{
return 0;
}
ssize_t keydev_read(struct file *pfile, char __user *user_buf, size_t user_size, loff_t *seek) //读设备
{
int size; //读取的数据量
int ret;
struct pdev *gkeydev = (struct pdev *)pfile->private_data;
if(user_size < sizeof(struct key_value))
{
printk("expect read size is invalid\n");
return -1;
}
spin_lock(&gkeydev->lock);
if(!gkeydev->newflag)
{
if(pfile->f_flags & O_NONBLOCK) //判断是否为阻塞模式
{
spin_unlock(&gkeydev->lock);
printk("no data read!\n");
return -1;
}
else
{
ret = wait_event_interruptible(gkeydev->dr,gkeydev->newflag == 1); //将信号加入等待队列
if(ret) //判断是否为信号唤醒
{
spin_unlock(&pkeydev->lock);
printk("sigal wake!\n");
return -ERESTARTSYS;
}
}
}
if(user_size > sizeof(struct key_value)) //量定读取多少
{
size = sizeof(struct key_value);
}
else
{
size = user_size;
}
ret = copy_to_user(user_buf, &gkeydev->data, size); //读取数据
if(ret)
{
spin_unlock(&pkeydev->lock);
printk("copy_to_user1 fail!");
return -1;
}
pkeydev->newflag = 0;
spin_unlock(&pkeydev->lock);
return size;
}
unsigned int keydev_poll(struct file *pfile, poll_table *wait)
{
struct pdev *gkeydev = (struct pdev *)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile, &gkeydev->dr, wait); //将读等待与写等待加入表内
spin_lock(&gkeydev->lock);
if(gkeydev->newflag) //判断是否可读
{
mask |= POLLIN | POLLRDNORM;
}
spin_unlock(&pkeydev->lock);
return mask;
}
struct file_operations fops ={
.owner = THIS_MODULE,
.open = keydev_open,
.release = keydev_release,
.read = keydev_read,
.poll = keydev_poll,
};
void work_queue_func(struct work_struct *pwk)
{
struct pdev *gkeydev = (container_of(pwk,struct pdev,work));
int status = 0;
gkeydev->data.status = gpio_get_value(pkeydev->gpio);
mdelay(1);
status = gpio_get_value(pkeydev->gpio);
if(status != gkeydev->data.status)
{
return;
}
if(gkeydev->data.status != KEY_DOWN)
{
return;
}
spin_lock(&gkeydev->lock);
gkeydev->data.code = KEY3;
gkeydev->data.status = KEY_DOWN;
gkeydev->newflag = 1;
spin_unlock(&gkeydev->lock);
wake_up(&gkeydev->dr);
return;
}
irqreturn_t key_handler(int nu, void *arg)
{
struct pdev *gkeydev = (struct pdev *)arg;
schedule_work(&gkeydev->work);
return IRQ_HANDLED;
}
int __init mykey_init(void)
{
dev_t devno = MKDEV(major, minor);
int ret = -1;
struct device_node *key_node = NULL;
/* 申请设备号 */
if((ret = register_chrdev_region(devno, dev_num, "mykey")) < 0)
{
ret = alloc_chrdev_region(&devno, minor, dev_num, "mykey");
if(ret < 0)
{
printk("alloc_chrdev_region fail!\n");
return -1;
}
major = MAJOR(devno);
}
/* 设备分配内存空间 */
pkeydev = (struct pdev *)kmalloc(sizeof(struct pdev), GFP_KERNEL);
if(pkeydev == NULL)
{
unregister_chrdev_region(devno, dev_num);
printk("pkeydev malloc fail!\n");
return -1;
}
/* 设备初始化 */
cdev_init(&pkeydev->keydev, &fops);
pkeydev->keydev.owner = THIS_MODULE;
/* 设备添加 */
cdev_add(&pkeydev->keydev, devno, dev_num);
/* 等待队列初始化 */
init_waitqueue_head(&pkeydev->dr); //初始化读等待队列头
/* 自旋琐初始化 */
spin_lock_init(&pkeydev->lock); //初始化自旋锁
/* 得到结点 */
key_node = of_find_node_by_path("/mykey3_node");
if(key_node == NULL)
{
unregister_chrdev_region(devno, dev_num);
cdev_del(&pkeydev->keydev);
kfree(pkeydev);
pkeydev = NULL;
return -1;
}
/* 获取按键引脚号*/
pkeydev->gpio = of_get_named_gpio(key_node, "key3-gpio", 0); //引脚号
pkeydev->irq = irq_of_parse_and_map(key_node, 0); //中断号
/* 底部处理函数初始化 */
INIT_WORK(&pkeydev->work, work_queue_func);
/* 按键中断初始化 */
ret = request_irq(pkeydev->irq, key_handler, IRQF_TRIGGER_FALLING,"key_irq", pkeydev);
if(ret < 0)
{
unregister_chrdev_region(devno, dev_num);
cdev_del(&pkeydev->keydev);
kfree(pkeydev);
pkeydev = NULL;
return -1;
}
return 0;
}
void __exit mykey_exit(void)
{
dev_t devno = MKDEV(major, minor);
/* 注销按键中断 */
free_irq(pkeydev->irq, pkeydev);
/* 注销设备 */
cdev_del(&pkeydev->keydev);
/* 注销设备号 */
unregister_chrdev_region(devno, dev_num);
/* 释放内存 */
kfree(pkeydev);
pkeydev = NULL;
}
MODULE_LICENSE("GPL");
module_init(mykey_init);
module_exit(mykey_exit);
九、下半部机制比较
- 任务机制
- workqueue ----- 内核线程 能睡眠 运行时间无限制
- 异常机制 ------- 不能睡眠 下半部执行时间不宜太长( < 1s)
- 软中断 ---- 接口不方便
- tasklet ----- 无具体延后时间要求时
- 定时器 -----有具体延后时间要求时
到这里就结束啦!