一、中断基础概念
所谓中断,指CPU在执行程序的过程中,出现了某些突发事件即待处理,CPU必须暂停当前的程序。转去处理突发事件,处理完毕后CPU又返回原程序被中断的位置并继续执行。
1、中断分类
a -- 内部中断和外部中断
根据中断的的来源,中断可以分为内部中断和外部中断:
内部中断,其中断源来自CPU内部(软件中断指令、溢出、除法错误等),例如,操作系统从用户态切换到内核态需借助CPU内部的软中断;
外部中断,其中断源来自CPU外部,由外设提出请求;
b -- 可屏蔽中断与不屏蔽中断
根据中断是否可以屏蔽分为可屏蔽中断与不屏蔽中断:
可屏蔽中断,其可以通过屏蔽字被屏蔽,屏蔽后,该中断不再得到响应;
不屏蔽中断,其不能被屏蔽;
c -- 向量中断和非向量中断
根据中断入口跳转方法的不同,分为向量中断和非向量中断:
向量中断,采用向量中断的CPU通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行。不同的中断号有不同的入口地址;
非向量中断,其多个中断共享一个入口地址,进入该入口地址后再通过软件判断中断标志来标识具体是哪个中断。
也就是说,向量中断由硬件提供中断服务程序入口地址,非向量中断由软件提供中断服务入口地址。
2、中断ID
a -- IRQ number
cpu给中断的一个编号,一个IRQ number是一个虚拟的interrupt ID,和硬件无关;
b -- HW interrupt ID
对于中断控制器而言,它收集了多个外设的irq request line,要向cpu传递,GIC要对外设进行编码,GIC就用HW interrupt ID来标示外部中断;
3、SMP情况下中断两种形态
1-Nmode :只有一个processor处理器
N-N :所有的processor都是独立收到中断的
GIC:SPI使用1-Nmode PPI 和 sgi使用N-Nmode
二、中断编程
1、 申请IRQ
在linux内核中用于申请中断的函数是request_irq(),函数原型在Kernel/irq/manage.c中定义:
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
相关参数:
a -- irq是要申请的硬件中断号。另外,这里要思考的问题是,这个irq 是怎么得到的?这里我们在设备树中获取,具体解析见:
b -- handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。
c -- irqflags是中断处理的属性,若设置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了IRQF_SHARED (老版本中的SA_SHIRQ),则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的)
d -- devname设置中断名称,在cat /proc/interrupts中可以看到此名称。
e -- dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。
request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。
顶半部 handler 的类型 irq_handler_t 定义为:
typedef irqreturn_t (*irq_handler_t)(int, void *);
参数1:中断号
参数2 :参数
在Interrupt.h (e:\linux-3.14-fs4412\include\linux) 18323 2014/3/31中定义
IRQ_NONE 共享中断,如果不是我的设备产生的中断,就返回该值
IRQ_HANDLED 中断处理函数正确执行了就返回该值
IRQ_WAKE_THREAD = (1 << 1)
2、释放IRQ
与request_irq()相对应的函数为 free_irq(),free_irq()的原型为:
void free_irq(unsigned int irq, void *dev_id)
free_irq()参数的定义与request_irq()相同。
三、中断注册过程分析
我们每次用中断的时候就是注册一个中断函数。request_irq首先生成一个irqaction结构,其次根据中断号 找到irq_desc数组项(还记得吧,内核中irq_desc是一个数组,没一项对应一个中断号),然后将irqaction结构添加到 irq_desc中的action链表中。当然还做一些其他的工作,注册完成后,中断函数就可以发生并被处理了。
irq_desc 内核中记录一个irq_desc的数组,数组的每一项对应一个中断或者一组中断使用同一个中断号,一句话irq_desc几乎记录所有中断相关的东西,这个结构是中断的核心。其中包括俩个重要的结构irq_chip 和irqaction 。
1、 irq_chip
irq_chip 里面基本上是一些回调函数,其中大多用于操作底层硬件,设置寄存器,其中包括设置GPIO为中断输入就是其中的一个回调函数,分析一些源代码
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq); //启动中断
void (*shutdown)(unsigned int irq); //关闭中断
void (*enable)(unsigned int irq); // 使能中断
void (*disable)(unsigned int irq); // 禁止中断
void (*ack)(unsigned int irq); //中断应答函数,就是清除中断标识函数
void (*mask)(unsigned int irq); //中断屏蔽函数
void (*mask_ack)(unsigned int irq); //屏蔽中断应答函数,一般用于电平触发方式,需要先屏蔽再应答
void (*unmask)(unsigned int irq); //开启中断
void (*eoi)(unsigned int irq);
void (*end)(unsigned int irq);
int (*set_affinity)(unsigned int irq,
const struct cpumask *dest);
int (*retrigger)(unsigned int irq);
int (*set_type)(unsigned int irq, unsigned int flow_type); //设置中断类型,其中包括设置GPIO口为中断输入
int (*set_wake)(unsigned int irq, unsigned int on);
void (*bus_lock)(unsigned int irq); //上锁函数
void (*bus_sync_unlock)(unsigned int irq); //解锁
/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
void (*release)(unsigned int irq, void *dev_id);
#endif
/*
* For compatibility, ->typename is copied into ->name.
* Will disappear.
*/
const char *typename;
};
我们可以看到这里实现的是一个框架,需要我们进一步的填充里面的函数。我们在分析另一个结构irqaction
2、irqaction
include/linux/interrupt.h
struct irqaction {
irq_handler_t handler; //用户注册的中断处理函数
unsigned long flags; //中断标识
const char *name; //用户注册的中断名字,cat/proc/interrupts时可以看到
void *dev_id; //可以是用户传递的参数或者用来区分共享中断
struct irqaction *next; //irqaction结构链,一个共享中断可以有多个中断处理函数
int irq; //中断号
struct proc_dir_entry *dir;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
};
我们用irq_request函数注册中断时,主要做俩个事情,根据中断号生成一个irqaction结构并添加到irq_desc中的 action结构链表,另一发面做一些初始化的工作,其中包括设置中断触发方式,设置一些irq_chip结构中没有初始化的函数为默认,开启中断,设置 GPIO口为中断输入模式。
中断发生之后处理流程
a -- 具体的CPU architecture相关模块进行现场保护,然后调用machine driver执行对应的中断处理handler;
b -- machine driver对应中断处理handler会根据硬件的信息获取HW interrupt id,然后通过irq domain模块翻译成irq number;
c -- 调用该IRQ number对应 hign level irq event handler,在这个hign level的handler中,会通过和中断控制器交互,进行中断处理的flow 控制(中断的嵌套、抢占),最终会遍历我们注册的IRQ action list,调用对应处理函数;
d -- CPU architeccture相关模块进行现场的恢复;
具体分析之前先看几个基础概念:
一、 中断硬件框架
中断的主动通知特性需要硬件设施支持。在数字逻辑电路层面,外部设备和处理器之间有一条专门的中断信号线(Interrupt Line),用于连接外设与CPU的中断引脚(Interrupt Pin)。当外部设备发生状态改变时,可以通过这条信号线向处理器发出一个中断请求(Interrupt Request,IRQ),其中外部设备通常被称作中断源(Interrupt Source)。
处理器一般只有两根左右的中断引脚(Cortex A9 中的 IRQ、FIQ),而管理的外设却很多。为了解决这个问题,现代设备的中断信号线并不是与处理器直接相连,而是与一个称为中断控制器的设备相连接,后者才跟处理器的中断引脚连接。中断控制器一般可以通过处理器进行编程配置,在Cortex A9 中称为通用中断控制器GIC(Gerneric Interrupt Controller)。下图是一个典型的中断硬件连接的系统框架图:
这里的中断控制器是可编程中断控制器PIC(Programmable Interrupt Controller)。PIC的输出中断信号线连接到处理器的INT引脚上,这是处理器专门用来接收中断信号的pin脚。外部设备的中断线连接到PIC的pin引脚上,这是PIC用来接收外设中断的pin脚。比如第一个设备的中断线通过P0连到PIC上。在实际的硬件平台上,PIC有的在CPU外部,比如x86平台的8259中断控制器;有的被封装到CPU的内部,这广泛见于嵌入式领域。一颗SoC芯片内部集成了处理器和各种外部设备的控制器,其中包括PIC。
IRQ相关信息管理的关键点是一个全局数组,每个数组项对应一个IRQ编号,软件中断号irq就是这个数组的索引,irq将一对一或多对一(共享)映射到硬件中断源编号。不同的操作系统相关数据结构的实现和映射策略实现可能有差别。
二、 中断向量表
中断向量表其实是处理器内部的概念,因为处理器除了会被外部设备中断外,其内部也可能产生异常等事件,例如在MIPS中,中断只是异常的一种。当这些事件发生时,CPU必须暂停手头上的工作,转而去处理中断或异常,因此处理器需要知道到哪里去获得这些中断或异常的处理函数的目标地址。中断向量表就是用来解决这个问题,其中每一项都是一个中断或异常处理函数的入口地址,具体来说4个字节的函数指针将指向一段汇编微码(intConnectCode)执行跳转。
外部设备的中断常常对应向量表中的某一项,这是通用框架的外部中断处理函数入口,因此在进入通用的中断处理函数之后,系统必须知道正在处理的中断是哪一个设备产生的,而这正是由软件中断号irq定的决。中断向量表的内容是由操作系统在初始化阶段来填写,对于外部中断,操作系统负责实现一个通用的外部中断处理函数,然后把这个函数的入口地址放到中断向量表中的对应位置。用户注册设备驱动ISR,实际上就是挂接到中断向量表中,覆盖某一项的默认处理实现特化。
中断向量表在linux\arch\arm\kernel\entry-armv.S
三、中断流程分析
1、当中断发生时会跳转到中断向量表处,就是上面那张表;
2、汇编处理部分不做分析:
table16个入口,只有2项有效,对应user mode、svc mode
Q:
1、handle_arch_irq 从哪来?
gic_init_bases —> set_handle_irq(gic_handle_irq)—> handle_arch_irq = handle_irq;
handle_arch_irq 实际执行的是gic_handle_irq
gic_handle_irq —> handle_IRQ —> generic_handle_irq()—> generic_handle_irq_desc—>desc —>handle_irq(irq, desc);
2、desc->handle_irq从哪来?
xxx_irq_type —>irq_set_handler(data->irq, handle_level_irq); —> __irq_set_handler —>
__irq_set_handler----- desc->handle_irq = handle;
handle_level_irq()
handle_irq_event —> handle_irq_event_percpu —>res = action->handler(irq, action->dev_id);真正调用到我们注册的中断处理函数
参考链接:https://blog.csdn.net/zqixiao_09/article/details/50908780
蜗窝科技有几篇文章写中断写得挺不错的 链接如下
1、linux kernel的中断子系统之(一)一个大概的软硬件框架。
2、linux kernel的中断子系统之(二):irq domain介绍。主要描述如何将一个HW interrupt ID转换成IRQ number。
3、linux kernel的中断子系统之(三):IRQ number和中断描述符。主要描述中断描述符相关的数据结构和接口API。
4、linux kernel的中断子系统之(四):high level irq event handler。
5、linux kernel的中断子系统之(五):driver API。主要以一个普通的驱动程序为视角,看待linux interrupt subsystem提供的API,如何利用这些API,分配资源,是否资源,如何处理中断相关的同步问题等等。
6、linux kernel的中断子系统之(六):ARM中断处理过程,这份文档以ARM CPU为例,描述ARM相关的中断处理过程
7、linux kernel的中断子系统之(七):GIC代码分析,这份文档是以一个具体的interrupt controller为例,描述irq chip driver的代码构成情况。
8、linux kernel的中断子系统之(八):softirq
9、linux kernel的中断子系统之(九):tasklet