中断就是CPU正常运行期间,由于内、外部事件引起的CPU暂时停止正在运行的程序,去执行该内部事件或外部事件的引起的服务中去,服务执行完毕后再返回断点处继续执行的情形。
中断的意义
极大提高CPU运行效率
中断服务程序
中断处理程序:在中断发生时被调用的函数称为中断服务函数。
中断服务函数的原则:linux是多进程操作系统
中断不属于任何一个进程,因此不能在中断程序中休眠和调用schedule函数放弃CPU
实现终端处理函数有一个原则,就是尽可能的处理并返回
linux中断顶部和中断底部
裸机中并没有此概念,他们是什么?
linux中断顶部、底部概念
为保证系统实时性,中断服务程序必须足够简短,但实际应用中某些时候发生中断时必须处理大量的事物,这时候如果都在中断服务程序中完成,则会严重降低中断的实时性,基于这个原因,linux系统提出了一个概念:把中断服务程序分为两部分-顶半部-底半部
顶半部
完成尽可能少的比较急的功能,它往往只是简单的读取寄存器的中断状态,并清除中断标志后就进行“中断标记”(也就是把底半部处理程序挂到设备的底半部执行队列中)的工作。
特点:响应速度快
底半部:
中断处理的大部分工作都在底半部,它几乎做了中断处理程序的所有事情。
特点:处理相对来说不是非常紧急的事件
底半部机制主要有:tasklet、工作队列和软中断
裸机中断设计形式
第一种写法:把发生中断所需要做的所有的事情全部写到中断服务函数体中
void isr()
{
//中断程序
}
第二种写法:中断只记录,标志,要做的事情在主循环中编写。
int flag=0;
void main(void)
{
while(1)
{
if(flag)
{
flag=0;
//中断需要做的事情
}
}
}
void isr()
{
//只做登记
flag=1;
}
第二种写法,不会影响到其他中断响应,保证系统的实时性。
补充:是否所有的中断都需要分为两部分来实现呢?
不一定!如果发生中断要做的事情很少不会影响系统的实时性,则就不必分成两部分实现。
linux中断API
linux中断有专门的中断子系统,其实现原理很复杂,但是驱动开发者不需要知道其实现的具体细节,只需要知道如何应用该子系统提供的API函数来编写中断相关驱动代码即可。
内核使用一个struct irqaction结构描述一个中断,编写中断终极目标就是实现这个结构,当然结构不是我们自己去定义,但是结构中的材料是我们提供的。
struct irq_desc {
struct irq_data irq_data;
·········//省略
irq_flow_handler_t handle_irq;
struct irqaction *action; /* IRQ action list */
}
最关注的几个成员
interrupt.h \linux-3.5\include\linux
struct irq_data {
unsigned int irq;
unsigned long hwirq;
unsigned int node;
unsigned int state_use_accessors;
struct irq_chip *chip;
struct irq_domain *domain;
void *handler_data;
void *chip_data;
struct msi_desc *msi_desc;
#ifdef CONFIG_SMP
cpumask_var_t affinity;
#endif
};
struct irqaction {
irq_handler_t handler; /*我们要提供的*/
void *dev_id; /*我们要提供的*/
void __percpu *percpu_dev_id;
struct irqaction *next;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned int irq; /*我们要提供的*/
unsigned int flags; /*我们要提供的*/
unsigned long thread_flags;
unsigned long thread_mask;
const char *name; /*我们要提供的*/
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
//有五个成员您需要我们提供
request_irq()
原型
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
可以跟struct irqaction 结构体中的成员对应。
功能:向内核注册一个中断服务函数,发生中断号为irq的中断的时候,会执行handle指针函数。
参数:
irq:中断编号(每个中断源有唯一编号),这里的中断编号不是看硬件手册,与裸机不同。由内核分配。
handler:中断服务函数指针,原型typedef irqreturn_t (*irq_handler_t)(int, void *);
flag:中断属性,如快速中断,共享中断,如果是外部中断还有:上升沿,下降沿触发这类标志。
name:中断名字,注册后会在/proc/irq/``irq号name文件夹出现。
dev_id:这个参数时传递给中断服务函数。对共享中断来说,这个参数一定要有,当注销共享中断其中一个时,用这个指定标识注销哪一个。对于有唯一入口的中断,可以传递NULL,但一般来说都会传递一个有意义的指针,在中断程序中使用,以方便编程。
返回值:0表示成功,返回-EINVAL表示中断号无效 ,返回-EBUSY表示中断被占用。
头文件:include/linux/interrupt.h
linux共享中断
共享中断是指多个中断共享一个中断线的情况,在中断到来时,会遍历共次中断的所有中断处理函数,直到某一个中断服务函数时返回IRQ_HANDLED
在中断处理程序顶半部中,应根据中断相关硬件寄存器中的信息,判断是否为本设备的中断,若不是立即返回IRQ_NONE
request_irq函数参数补充说明
中断编号
在linux系统中总段编号可能和裸机的中断编号数值不相同。如何确定一个中断源的中断编号?
一般是由芯片厂商写好所有可中断号宏定义。在内核源码中可以找到,如果是外部中断,内核还提供了通用API函数,可以通过IO管脚编号转换成它对应的中断编号
中断服务函数类型是typedef irqreturn_t (*irq_handler_t)(int, void *);是一个指向有一个整形参数,一个void类型指针,返回值是irqreturn_t的函数指针。这个数据结构规定了中断服务函数的原型。返回值类型在内核中定义为:
/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
第一个参数int类型的参数是中断号,第二个void*类型指针是共享中断的标识id,
这两个参数在中断服务被调用的时候传递进来的内容实际上就是reauest_irq()在注册s时候的irq,dev参数。这一点在源码中有体现,编程的时候要注意,善于利用这个特性编写程序。
中断属性flag
在include/linux/interrupt.h中定义可用的数值,以IRQF开头的宏,若设置了IRQF_DISABLED,则表示中断类型属于独占类型的中断,不是共享中断,若设置了 IRQF_SHARED,则表示镀铬设备共享中断,若设置了 IRQF_SAMPLE_RANDOM,表示对系统获取随机数有好处,(这几个宏可以通过或的方式组合使用),若是外部中断还要设置IRQF_TRIGGER_XXX标志,表示选择外部中断的触发电平,内核中相关定义如下:
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
* These flags used only by the kernel as part of the
* irq handling routines.
*
* IRQF_DISABLED - keep irqs disabled when calling the action handler.
* DEPRECATED. This flag is a NOOP and scheduled to be removed
* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
* Used by threaded interrupts which need to keep the
* irq line disabled until the threaded handler has been run.
* IRQF_NO_SUSPEND - Do not disable this IRQ during suspend
* IRQF_FORCE_RESUME - Force enable it on resume even if IRQF_NO_SUSPEND is set
* IRQF_NO_THREAD - Interrupt cannot be threaded
* IRQF_EARLY_RESUME - Resume IRQ early during syscore instead of at device
* resume time.
*/
#define IRQF_DISABLED 0x00000020
#define IRQF_SAMPLE_RANDOM 0x00000040
#define IRQF_SHARED 0x00000080
#define IRQF_PROBE_SHARED 0x00000100
#define __IRQF_TIMER 0x00000200
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000
#define IRQF_NO_SUSPEND 0x00004000
#define IRQF_FORCE_RESUME 0x00008000
#define IRQF_NO_THREAD 0x00010000
#define IRQF_EARLY_RESUME 0x00020000
#define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
常用值:IRQF_DISABLED,IRQF_SHARED 这两个可以用来设置任何中断,但是不能同时使用
IRQF_TRIGGER_RISING IRQF_TRIGGER_FALLING 只对外部中断有用,可以和上面的IRQF_DISABLED IRQF_SHARED之一组合使用
IRQF_SHARED:添加上这个标志之后,此中断号还可以注册
IRQF_DISABLED:添加上这个标志之后,此中断号只能执行一次
中断名name
中断名字,在cat /proc/interrupts中可以看到此名称,同时会出现/proc/irq/
irq号/name文件夹出现
中断设备id识别标志dev
这个参数会在发生中断,执行服务函数时,作为实参传递给中断服务函数的第二个参数。传递什么内容,完全由开发者决定,只要有利于更好的编写中断服务函数的都可以传递。
在飞共享中断时候可以是NULL,也可以传递任何用户自定的结构地址,在共享中断时必须传递有效参数,用于发生中断时,用于在注销中断时识别具体要注销中断服务函数中的具体哪一个子项共享中断。
很多书,很多人认为dev这个参数能用于识别在发生中断时候,是否是本设备产生的中断,但是实际并不是这样,要看开发者给这个参数传递什么内容,如果传递的是一个和中断状态有关的硬件寄存器地址,中断服务程序中可以读取这个寄存器的内容,从而判断是否是本设备产生的中断,如果只是传递一个普通的变量地址,并不能通过这个地址区分到底是那个硬件设备产生的中断
handled
irqreturn_t (*irq_handler_t)(int, void *);
返回值:有下面三种情况
/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
参数:
第一个参数:中断函数注册时的中断号irq
第二个参数:注册的时候最后一个参数dev_id
中断服务函数原型:
irqreturn_t isr_function(int irq,void* dev_id)
中断服务函数模板:
irqreturn_t isr_function(int irq,void* dev_id)
{
return IRQ_HANDLED;
}
对于共享中断:
irqreturn_t isr_function(int irq,void* dev_id)
{
//根据dev_id判断是否是本设备产生的中断
if(readreg(dev_id)!=本设备产生中断)
return IRQ_NONE;
//以下是本设备产生中断的代码
return IRQ_HANDLED;
}
free_irq()
描述 说明
函数原型 void free_irq(int irq,void *dev_id)
函数功能 从内核中断服务函数链表中删除一个中断结构
函数参数 同上
函数返回值 无
函数头文件 include/linux/interrupt.h
函数定义文件 kernel\irq\manage.c (用EXPORT_SYMBOL(free_irq)导出给内核模块使用)
这个函数跟request_irq函数功能相反,当设备不使用中断时,使用这个函数把相关中断在中断链表中的节点释放掉释放掉。
disable_irq,disable_irq_nosync
描述 说明
函数原型 void disabled_irq(unsigned int irq)
函数功能 关闭指定的中断,如果中断没有执行完,等待执行完在关闭,不能再中断中使用,否则自己关闭自己,引起内核崩溃
函数原型 void disabled_irq_nosync(unsigned int irq)
函数功能 关闭中断,不等待中断执行完毕,可以在中断函数中执行
enable_irq
描述 说明
函数原型 void enabled_irq(unsigned int irq)
函数功能 使能指定中断
local_save_flags(flags)
描述 说明
宏原型 local_save_flags(flags)
宏功能 禁止本CPU全部中断,并保存CPU状态信息,(现在很多芯片是多核CPU)这个函数需要和local_irq_restore函数配合使用
宏参数 flags:unsigned long类型用于保存当前CPU状态信息
宏返回值 无
使用示例:
unsigned long flags;
local_save_flags(flags);
······
local_irq_restore(flags);
local_irq_restore(flags)
描述 说明
宏原型 local_irq_restore(flags)
宏功能 与local_save_flags(flags)功能函数相反,配对使用,用来使能由与local_save_flags禁止的中断
宏参数 flags:unsigned long类型用于恢复当前CPU状态信息
宏返回值 无
local_irq_disable()
禁止本CPU中断,不能保存当前CPU信息
local_irq_enable()
使能由local_irq_disable禁止的中断,不能还原CPU信息
linux3.5内核关于CPU中断号
linux中断注册函数中的irq中断号并不是芯片物理上的编号,而是芯片厂商在移植linux系统时定在相关架构的头文件中定义好的,在内核源码中,名字一般是irqs.h
irqs.h \linux-3.5\arch\arm\mach-exynos\include\mach
下面是其中关于外部中断的一部分内容,还有其他内容这里没有列举
/* For EXYNOS4 SoCs */
#define EXYNOS4_IRQ_EINT0 IRQ_SPI(16)
#define EXYNOS4_IRQ_EINT1 IRQ_SPI(17)
#define EXYNOS4_IRQ_EINT2 IRQ_SPI(18)
#define EXYNOS4_IRQ_EINT3 IRQ_SPI(19)
#define EXYNOS4_IRQ_EINT4 IRQ_SPI(20)
#define EXYNOS4_IRQ_EINT5 IRQ_SPI(21)
#define EXYNOS4_IRQ_EINT6 IRQ_SPI(22)
#define EXYNOS4_IRQ_EINT7 IRQ_SPI(23)
#define EXYNOS4_IRQ_EINT8 IRQ_SPI(24)
#define EXYNOS4_IRQ_EINT9 IRQ_SPI(25)
#define EXYNOS4_IRQ_EINT10 IRQ_SPI(26)
#define EXYNOS4_IRQ_EINT11 IRQ_SPI(27)
#define EXYNOS4_IRQ_EINT12 IRQ_SPI(28)
#define EXYNOS4_IRQ_EINT13 IRQ_SPI(29)
#define EXYNOS4_IRQ_EINT14 IRQ_SPI(30)
#define EXYNOS4_IRQ_EINT15 IRQ_SPI(31)
·····
可以看出这个文件是存放在和芯片体系架构相关的文件夹中
对于外部中断,可以通过IO口编号转换成对应的中断编号。关于IO口在下小节中说明
static inline int gpio_to_irq(unsigned int gpio)