1、裸机中的中断处理方法:
(1)使能中断、初始化相应寄存器
(2)注册中断服务函数,向irqTable数组的指定标号处写入中断服务函数
(3)中断发生后进入IRQ中断服务函数,执行对应中断处理函数
2、Linux中断API函数
(1)中断号
每个中断都有一个中断号,通过中断号可区分不同中断。
(2)Linux中断申请
Linux内核中想使用一个中断是需要申请的,request_irq函数用于申请中断,request_irq可能会导致睡眠,不能在中断上下文中申请。
参数
- irq:要申请中断的中断号
- handler:中断处理函数
- flags:中断标志位,定义在include/linux/interrupt.h
- name:中断名字,可在/proc/interrupts中看到
- dev:若将flags设置为IRQF_SHARED(该中断线被多个设备共享),dev用来区分不同中断,一般情况下,dev设置为设备结构体,dev会传递给handler第二个参数。
Request_irq会激活(使能)中断。
包括上升沿、下降沿等触发方式,可以用|来实现多种组合。
(3)释放中断
free_irq函数。中断完成后要释放掉响应的中断
- 第一个参数irq:要释放的中断
- 第二个参数dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数时才被禁止。
(4)中断处理函数
- 第一个参数int:中断处理函数要处理的中断号
- 第二个参数void*:通用型指针,用于区分共享中断的不同设备,与request_irq函数的dev参数保持一致。
(5)中断使能和禁止函数
关闭指定中断:
- irq:要禁止的中断号
关闭整个中断系统:
3、上半部与下半部
Linux中中断分为上半部和下半部
上半部:就是中断处理函数,执行速度快的操作放在上半部
下半部:耗时长的放在下半部,以免阻塞系统运行时间过长。
4、下半部的三种实现机制
(1)软中断
(软中断和硬中断相比,就是触发方式不同,由硬件触发变为软件触发,同样会去打断当前进程的执行)
(Linux内核中,软中断被设计为在所有硬件中断处理完后才执行。)
(Linux中硬中断上下文要关中断,无法被其他硬中断打断(禁止中断嵌套)。软中断不关中断,可以被打断。)
- Linux中使用softirq_action表示软中断
- /kernel/softirq.c文件中一共定义了10软中断
Softirq_action结构体中的action成员变量就是软中断的服务函数
- 注册软中断处理函数
使用软中断要先注册对应的软中断处理函数,open_softirq
nr:要开启的软终端
action:软中断对应的处理函数
- 触发软中断
软中断注册好后要通过raise_softirq函数触发
nr:要触发的软中断
软中断流程:
a、定义软中断处理函数
b、注册软中断处理函数
c、触发软中断
(2)tasklet
tasklet是利用软中断实现的另一种下半部机制。
- tasklet结构体:
Linux 内核使用 tasklet_struct 结构体来表示 tasklet
func是tasklet要执行的处理函数,用户定义的函数内容,相当于中断处理函数。使用tasklet前,必须先定义初始化一个tasklet
- 初始化tasklet
方式一:
t:要初始化的tasklet
func:tasklet的处理函数
data:要传递给func函数的参数
方式二:
也可以使用宏 DECLARE_TASKLET 来一次性完成 tasklet 的定义和初始化,
name:定义的tasklet名字
func:tasklet处理函数
data:传递给func函数的参数
- 调用tasklet
在中断上半部中调用tasklet_schedule函数就能使得tasklet在合适的时间运行
t:要调度的tasklet
tasklet流程
a、定义tasklet结构体
b、定义tasklet处理函数(一个普通的函数)
c、初始化tasklet
d、注册中断服务函数
e、中断服务函数中调度tasklet
(3)工作队列:
另一种下半部执行方式,在进程上下文执行,工作队列将要推后的工作交给一个内核线程执行,工作在进程上下文中
工作
内核中用work_struct结构体表示一个工作
工作队列
这些工作会组织成一个工作队列,使用workqueue_struct结构体表示
工作线程worker thread
Linux内核中会使用工作者线程处理工作队列中的各个工作,用worker结构体表示工作者线程。
工作、工作队列、工作线程(工作者)关系
每个worker都有一个工作队列,工作者线程处理自己工作队列中的所有工作(每个CPU都有个工作者线程,并行处理多个工作)。
实际开发中,我们只需要定义工作(work_struct)即可,工作队列和工作线程基本不用管。
工作创建
1)定义一个work_strcut结构体变量
2)初始化工作
方式一:
#define INIT_WORK(_work, _func)
INIT_WORK宏初始化工作
- _work:要初始化的工作
- _func:工作对应的处理函数
方式二:
#define DECLARE_WORK(n, f)
DECLARE_WORK 宏一次性完成工作的创建和初始化
- n:要定义的工作
- f:工作对应的处理函数
3)调度工作
需要调用工作才能运行
bool schedule_work(struct work_struct *work)
- work:要调度的工作
- 返回值:0 成功,其他值,失败
同样要在中断的上半部,主动开启调度。
(4)threaded irq:(现在的新技术)
单核的时候工作队列只有一个内核线程,还可以,但是如果用到多核,其他核心就浪费了,所以给每个线程都创建一个线程,有专门的线程中断函数,thread_fn中放入线程里要执行的函数。这样子线程平均的分配到多个核心中。
可以只提供 thread_fn,系统会为这个函数创建一个内核线程。发生中断时,内核线程就会执行这个函数。
5、设备树中断信息节点
中断控制器节点
GIC中断控制器节点
- #interrupt-cells:该中断控制器下设备的cell大小。第一个cell:中断类型,0表示SPI中断,1表示PPI中断。第二个cell:中断号。SPI中断为0~987,PPI中断号为0~15。第三个cell:标志,bit[3:0]表示中断触发类型,为1时表示上升沿触发,为2时表示下降沿触发等。bit[15:8]为PPI中断的CPU掩码
- interrupt-controller:为空,表示当前节点为中断控制器
GPIO节点中断控制器:gpio5
- interrupts:描述中断源信息,gpio5一共两条中断源信息,类型都为SPI,触发电平都为IRQ_TYPE_LEVEL_HIGH,不同之处在中断源,一个是74、一个是75
查表可知GPIO5一共用了两个中断号,一个是74、一个是75,分别对应GPIO5_IO00~GPIO5_IO15和GPIO5_IO16~GPIO5_IO31
- interrupt-controller:表明gpio5节点也是个中断控制器,用于控制所有gpio5所有IO的中断
- #interrupt-cells:修改为2
Fxls8471是6ULL开发板上的磁力计芯片,有一个中断引脚连接到了SNVS_TAMPER0 引脚上,这个引脚可以复用为 GPIO5_IO00。
- interrupt-parent 属性:设置中断控制器,使用gpio5作为中断控制器
- interrupt:设置中断信息,0表示GPIO5_IO00,8表示低电平触发
#interrupt-cells,指定中断源信息的cell个数
interrupt-controller,当前节点为中断控制器
interrupt,指定中断号,触发方式
interrupt-parent,指定父中断,中断控制器
6、设备树里中断
imx6ull.dtsi(厂家提供的)、 100ask_imx6ull-14x14.dts
imx6ull.dtsi:中包含了厂家定义好的中断控制器
100ask_imx6ull-14x14.dts :只需要在外部设备中指定好中断
BSP:工程师定义的两个中断控制器(GIC、GPIO等)
(1)GIC中断控制器(厂家):
1、compatibel:找到对应的驱动程序
2、interrupt-controller:说明这是一个中断控制器
3、#interrupt-controller:使用该中断控制器的cell(参数个数)数量
(2)GPIO等中断控制器(厂家):
1、compatibel:找到对应的驱动程序
2、interrupt-controller:说明这是一个中断控制器
3、#interrupt-controller:使用该中断控制器的cell(参数个数)数量
4、使用上一级哪个中断控制器的哪一个中断
Interrupt_parent = <& >
Interrupts = < > < > 描述一个或多个中断(使用到多个中断时)
(3)指定使用哪一个中断控制器里的哪个中断(我们自己定义)
1、指定使用哪一个中断控制器里的哪个中断
Interrupt_parent = <& >
Interrupts = < > < > 描述一个或多个中断(使用到多个中断时)
GIC中通常Cell有三个属性(下一级中断cell的参数):
1、类(私有中断PPI、还是共享中断SPI)
2、哪个:哪个中断
3、电平触发类型:
要使用 gpio1 里的第 5 号中断, hwirq 就是 5。
7、获取中断号
中断驱动程序编写时要用到中断号,中断信息已写入到设备树中。有两种获取方式。可以用irq_of_parse_and_map函数从interupts属性中提取到对应的设备号
函数参数和返回值含义如下:
- dev:设备节点
- index:索引号,interrupt属性中,可能包含多条中断信息,通过index指定要获取的信息
返回值:中断号
若该引脚使用到了GPIO,可以用gpio_to_irq函数获取gpio对应的中断号
- gpio:要获取的GPIO编号:要获取的GPIO编号
返回值:GPIO对应的中断号
8、按键中断程序过程
a、定义一个软件定时器,完成其初始化,定义其处理函数
b、初始化keyIo,gpio子系统获取设备树信息
c、定义中断服务函数、获取中断号,申请中断
7、运行
cat /proc/interrupts
中断已经成功注册
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define IMX6UIRQ_CNT 1 /* 设备号个数 */
#define IMX6UIRQ_NAME "imx6uirq" /* 名字 */
#define KEY0VALUE 0X01 /* KEY0按键值 */
#define INVAKEY 0XFF /* 无效的按键值 */
#define KEY_NUM 1 /* 按键数量 */
/* 中断IO描述结构体 */
struct irq_keydesc {
int gpio; /* gpio */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};
/* imx6uirq设备结构体 */
struct imx6uirq_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
atomic_t keyvalue; /* 有效的按键键值 */
atomic_t releasekey; /* 标记是否完成一次完成的按键,包括按下和释放 */
struct timer_list timer;/* 定义一个定时器*/
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
unsigned char curkeynum; /* 当前的按键号 */
};
struct imx6uirq_dev imx6uirq; /* irq设备 */
/* @description : 中断服务函数,开启定时器,延时10ms,
* 定时器用于按键消抖。
* @param - irq : 中断号
* @param - dev_id : 设备结构。
* @return : 中断执行结果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定时 */
return IRQ_RETVAL(IRQ_HANDLED);
}
/* @description : 定时器服务函数,用于按键消抖,定时器到了以后
* 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
* @param - arg : 设备结构变量
* @return : 无
*/
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
if(value == 0){ /* 按下按键 */
atomic_set(&dev->keyvalue, keydesc->value);
}
else{ /* 按键松开 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */
}
}
/*
* @description : 按键IO初始化
* @param : 无
* @return : 无
*/
static int keyio_init(void)
{
unsigned char i = 0;
int ret = 0;
imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd== NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
/* 提取GPIO */
for (i = 0; i < KEY_NUM; i++) {
imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
if (imx6uirq.irqkeydesc[i].gpio < 0) {
printk("can't get key%d\r\n", i);
}
}
/* 初始化key所使用的IO,并且设置成中断模式 */
for (i = 0; i < KEY_NUM; i++) {
memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name)); /* 缓冲区清零 */
sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 组合名字 */
gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
printk("key%d:gpio=%d, irqnum=%d\r\n",i, imx6uirq.irqkeydesc[i].gpio,
imx6uirq.irqkeydesc[i].irqnum);
}
/* 申请中断 */
imx6uirq.irqkeydesc[0].handler = key0_handler;
imx6uirq.irqkeydesc[0].value = KEY0VALUE;
for (i = 0; i < KEY_NUM; i++) {
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
if(ret < 0){
printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
/* 创建定时器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_function;
return 0;
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if (releasekey) { /* 有按键按下 */
if (keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
} else {
goto data_error;
}
return 0;
data_error:
return -EINVAL;
}
/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init imx6uirq_init(void)
{
/* 1、构建设备号 */
if (imx6uirq.major) {
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.devid);
}
/* 2、注册字符设备 */
cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
/* 3、创建类 */
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class)) {
return PTR_ERR(imx6uirq.class);
}
/* 4、创建设备 */
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device)) {
return PTR_ERR(imx6uirq.device);
}
/* 5、初始化按键 */
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
keyio_init();
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit imx6uirq_exit(void)
{
unsigned int i = 0;
/* 删除定时器 */
del_timer_sync(&imx6uirq.timer); /* 删除定时器 */
/* 释放中断 */
for (i = 0; i < KEY_NUM; i++) {
free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
gpio_free(imx6uirq.irqkeydesc[i].gpio);
}
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");