linux中断流程详解
Posted 飞雪天龙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux中断流程详解相关的知识,希望对你有一定的参考价值。
异常体系比较复杂,但是linux已经准备了很多的函数和框架,但是因为中断是和具体的开发板相关,所以中断需要我们自己来处理一些方面,但是这也是很少的一部分,很多公用的处理函数内核已经实现,linux内核搭建了一个非常容易扩充的中断处理体系。中 断系统结构涉及的方面很多,而且分布在很多的函数中,这里我主要理清一些结构和流程顺序已经在哪些函数中实现,我不知道其他人怎么样?但是我自己一开始怎 是找不到linux内核是怎么把GPIO设置成中断的,我找了很久都找不到,还有我们很多的设置,初始化等等东西好像都没有实现,清除中断寄存器也不知道 是怎么实现的,只是知道使用中断,差不多用request_irq函数就差不多了。下面我就把这些涉及的方方面面的流程整理出来。
一、重要结构
/include/linux/irq.h
irq_desc 内核中记录一个irq_desc的数组,数组的每一项对应一个中断或者一组中断使用同一个中断号,一句话irq_desc几乎记录所有中断相关的东西,这个结构是中断的核心。其中包括俩个重要的结构irq_chip 和irqaction 。
irq_chip 里面基本上是一些回调函数,其中大多用于操作底层硬件,设置寄存器,其中包括设置GPIO为中断输入就是其中的一个回调函数,分析一些源代码
/include/linux/irq.h
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
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口为中断输入模式(这里后面有详细流程分析)。
二、中断流程
整个中断可以分为几个大的流程
1. 中断初始化流程 注意这个阶段就是我非常迷惑的一个阶段,很多初始化,设置寄存器等等问题都是内核启动的时候就已经初始化了,这个阶段做很多工作,其中最重要的就是初始化 了irq_chip结构。使得其中的众多函数已经设置好了,可以被调用了。注意这里只是实现了irq_chip结构的函数,要响应中断还有很多事情要做。
2. 中断注册流程 这个流程是我们比较熟悉的,因为我们每次用中断的时候就是注册一个中断函数。request_irq首先生成一个irqaction结构,其次根据中断号 找到irq_desc数组项(还记得吧,内核中irq_desc是一个数组,没一项对应一个中断号),然后将irqaction结构添加到 irq_desc中的action链表中。当然还做一些其他的工作,注册完成后,中断函数就可以发生并被处理了。
3.中断的处理流程 这个流程主要是产生中断后调用irq_chip中的函数来屏蔽,清除中断等等,然后调用irqaction结构中用户注册的中断函数处理中断,当然还有很多其他的事情要做,不过主要流程是这样。
中断流程应该还包括中断卸载,不过内容比较简单这里就不过啰唆了,下面我们俩详细分析一下这些具体的流程。
<一>中断初始化流程
下面内容转载他人博客,原文地址 http://blog.chinaunix.net/space.php?uid=15193587&do=blog&cuid=2194431
下面我们分析内核中断初始化的过程以及如何调用到一个新平台的irq初始化函数。
这里我们以s3c2410平台为例,他的中断初始化函数定义在:
/* arch/arm/mach-s3c2410/irq.c */
void __init s3c24xx_init_irq(void)
……
在arch/arm/mach-s3c2410/mach-smdk2410.c内通过MACHINE_START宏将s3c24xx_init_irq赋值给mach_desc结构体的.init_irq成员。
MACHINE_START(SMDK2410,"SMDK2410")/* @TODO: request a new identifier and switch |
注:MACHINE_START宏的作用是对mach_desc结构体进行初始化。mach_desc里定义了一些关键的体系架构相关的函数。Porting kernel到新平台时,这个结构体是非常关键的。
init_irq这个成员在系统初始化的时候会被赋值给init_arch_irq全局变量,如下:
/* arch/arm/kernel/setup.c */ |
注: 可以看到这里不仅初始化了init_arch_irq 全局变量,同时初始化了system_timer,init_machine等全局变量。这是kernel支持多平台的一种机制。当然这里 system_timer和init_machine我不多描述,有兴趣的可以大家自己去看。机制和init_arch_irq大同小异。
init_arch_irq函数指针定义在体系架构无关的arch/arm/kernel/irq.c内
/* arch/arm/kernel/irq.c */
void (*init_arch_irq)(void) __initdata = NULL;
并且在init_IRQ函数内会去执行它。
/* arch/arm/kernel/irq.c */ |