Linux中断原理
Linux内核中每个中断irq对应一个struct irq_dest结构体(注意:这里的irq是内核内部irq,不是硬件irq),每个struct irq_desc包含一条struct irqaction链表。
struct irq_desc {
......
struct irqaction *action; /* IRQ action list */
......
};
irq号相同的的中断源,可以添加到同一条struct irqaction链表中,从而实现对中断的共享。在中断发生时,Linux内核遍历该链表,取出每个struct irqaction,执行其中的中断处理函数。
struct irqaction结构体如下所示:
struct irqaction {
irq_handler_t handler; //指向中断处理函数
void *dev_id; //区别共享中断的不同设备的id
void __percpu *percpu_dev_id;
struct irqaction *next; //将共享同一IRQ号的struct irqaction对象链接在一起
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned int irq; //IRQ号
unsigned int flags; //中断触发标志位
unsigned long thread_flags;
unsigned long thread_mask;
const char *name; //用来在/proc/interrupts中显示中断的的拥有者
struct proc_dir_entry *dir;
}
申请中断
申请中断的过程就是把创建相对应的irqaction,链接入对应中断对象irq_desc的action链表中。申请中断的函数为request_irq,接口原型如下:
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
参数意义与struct irqaction中的成员一致。
其中,flags中断触发标志的取值有如下,可以使用位或设置。
IRQF_TRIGGER_RISING 上升沿触发
IRQF_TRIGGER_FALLING 下降沿触发
IRQF_TRIGGER_HIGH 高电平触发
IRQF_TRIGGER_LOW 低电平触发
IRQF_SHARED 实现共享中断必须设置
IRQF_TIMER 定时器```
name成员标志中断的所有者,在/proc/interrupts和/proc/stats中都可以查看。
/proc/interrupts会依次显示irq编号,每个cpu对该irq的处理次数,中断控制器的名字,irq的名字,以及驱动程序注册该irq时使用的名字
/proc/stat中以intr开头的为内核从启动开始接收到的中断数量。第一个数字229765为所有中断总数。后面开始为从中断号0开始的中断发生次数。
void * dev : 该成员对共享中断才有用,非共享中断可以传入一个NULL。实现共享中断时,驱动程序可以传入自己的内部结构体指针,在struct irqaction同一链表中,能够用于区分struct irqaction不同节点。内核遍历struct irqaction链表时,会运行所有结点中的中断处理函数,我们在中断处理函数中可以判断是否是本驱动产生的中断,不是的话,返回IRQ_NONE忽略该中断,是的话,返回中断被处理IRQ_HANDLED。
共享中断
共享中断除了在flags参数上设置IRQF_SHARED外,还必须保证flags参数中中断触发方式的一致。在request_irq中有对flags标记进行判断。
if ( !((old->flags & new->flags) & IRQF_SHARED) ||
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) ||
((old->flags ^ new->flags) & IRQF_ONESHOT)) {
old_name = old->name;
goto mismatch;
}
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
!((old->flags & new->flags) & IRQF_SHARED) : 在old_flags和new_flags中有一个没有设置IRQF_SHARED,返回真。
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) : 该运算很巧妙,若old_flags与new_flags在中断触发方式不一致,返回真。
注销中断
注销中断比较简单,直接调用free_irq,该函数传入irq和dev_id,若是共享中断,dev_id用于从action链表中删除对应的struct irqaction结点。
GPIO中断
需求:按住mt7688的wps按键,触发中断,用printk打印一句话。
首先查看芯片手册,发现WPS按键连接到了WDT_RST_N
在数据手册查找WDT_RST_N,该pin口对应GPIO#38
申请中断的代码如下:
irq = gpio_to_irq(WPS_GPIO);
if( (ret=request_irq(irq,my_interrupt_handler,IRQF_TRIGGER_FALLING,"my_irq",NULL)) < 0){
printk("driver : request_irq failed\n");
goto end;
}
实验效果如下,当我按下WPS按键,触发了WPS的功能,同时,也触发了中断。
通过cat /proc/interrupts看到我们注册的驱动:
完整的代码如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
MODULE_AUTHOR("Javier Huang");
MODULE_LICENSE("GPL");
#define WPS_GPIO 38
static irqreturn_t my_interrupt_handler(int irq,void *dev_id)
{
printk("interrupt happened\n");
return IRQ_HANDLED;
}
static int irq = -1;
static int __init driver_init(void)
{
int ret = 0;
irq = gpio_to_irq(WPS_GPIO);
if( (ret=request_irq(irq,my_interrupt_handler,IRQF_TRIGGER_FALLING,"my_irq",NULL)) < 0){
printk("driver : request_irq failed\n");
goto end;
}
end:
return ret;
}
static void __exit driver_exit(void)
{
if(irq>0){
free_irq(irq,NULL);
}
}
module_init(driver_init);
module_exit(driver_exit);