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
        * to SMDK2410 */
 /*
Maintainer: Jonas Dietsche */
 .phys_io = S3C2410_PA_UART,
 .io_pg_offst =(((u32)S3C24XX_VA_UART)>> 18)& 0xfffc,
 .boot_params = S3C2410_SDRAM_PA+ 0x100,
 .map_io = smdk2410_map_io,
 .init_irq = s3c24xx_init_irq,
 .init_machine = smdk_machine_init,
 .timer = &s3c24xx_timer,
MACHINE_END

注:MACHINE_START宏的作用是对mach_desc结构体进行初始化。mach_desc里定义了一些关键的体系架构相关的函数。Porting kernel到新平台时,这个结构体是非常关键的。

init_irq这个成员在系统初始化的时候会被赋值给init_arch_irq全局变量,如下:

/* arch/arm/kernel/setup.c */
void __init setup_arch(char**cmdline_p)
{
 ……
 cpu_init();
 /*
  * Set up various architecture-specific pointers
  */
 init_arch_irq = mdesc->init_irq;
 system_timer = mdesc->timer;
 init_machine = mdesc->init_machine;
 ……
}

注: 可以看到这里不仅初始化了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 */
void __init init_IRQ(void)
{
 int irq;
 for (irq = 0; irq < NR_IRQS; irq++)
  irq_desc[irq].status|= IRQ_NOREQUEST| IRQ_DELAYED_DISABLE|
   IRQ_NOPROBE;
#ifdef CONFIG_SMP
 bad_irq_desc.affinity
= CPU_MASK_ALL;
 bad_irq_desc.cpu
= smp_processor_id();
#endif
 init_arch_irq();
}

那init_IRQ在哪里被调用呢? 我们猜想肯定是在系统初始化的时会调用它。

实际结果也正是,init_IRQ会在init/main.c里的start_kernel函数内被调用:
asmlinkage void __init start_kernel(void)
{
 ……
 trap_init();
 rcu_init();
 init_IRQ();
 pidhash_init();
 clockevents_init();
 init_timers();
 ……
}

上面转载的文章分析的很好,我也是看了他的问题才找到一些答案的,比如init_arch_irq 函数指针,说明的是这是个全局的函数指针,跟体系结构相关。我接着上面的继续分析一下s3c24xx_init_irq()这个函数

这个函数比较长,主要是为每一个中断号都要进行设置,主要完成几个方面的工作

1.清楚中断标志

/* first, clear all interrupts pending... */

last = 0;

for (i = 0;
i < 4; i++) {

pend
= __raw_readl(S3C24XX_EINTPEND);

if
(pend == 0 || pend == last)

break;

__raw_writel(pend,
S3C24XX_EINTPEND);

printk("irq:
clearing pending ext status %08x\n", (int)pend);

last
= pend;

}

这里有个问题没有弄明白,要是有人知道很感谢告诉我一声,就是上面的for循环,循环清除中断标识,而且设置了一个last变量,不知道是不是为了防止一次擦除中断失败。

2. 设置irq_chip结构,中断的处理函数入口和中断标识

for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++)
{

irqdbf("registering
irq %d (extended s3c irq)\n", irqno);

set_irq_chip(irqno,
&s3c_irqext_chip);

set_irq_handler(irqno,
handle_edge_irq);

set_irq_flags(irqno,
IRQF_VALID);

}

首先是填充irq_chip结构,然后是设置中断的处理函数入口,这个响应中断的时候就通过函数入口调用用户注册的中断处理函数,中断标识设置为可以使用。

<二>中断注册处理流程

requesrt_irq函数

request_irq(unsigned int irq, irq_handler_t handler,
unsigned long flags,

const char *name, void *dev)

{

return
request_threaded_irq(irq, handler, NULL, flags, name, dev);

}

里面就一个request_threaded_irq函数,参数的话irq为中断号,handler为用户的中断函数,flags中断标 识,name中断名称,dev可以是传递的参数可以是作为区分共享中断。对应irqaction结构中的dev_id,其实request_irq主要是 把这些参数来构造一个irqaction结构添加到链表中。

继续看request_threaded_irq函数

/kernel/irq/manage.c

action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);

if (!action)

return
-ENOMEM;

action->handler
= handler;

action->thread_fn
= thread_fn;

action->flags
= irqflags;

action->name
= devname;

action->dev_id
= dev_id;

chip_bus_lock(irq,
desc);

retval =
__setup_irq(irq, desc, action);

chip_bus_sync_unlock(irq,
desc);

代码比较多,但是主要是利用用户传递的参数构造一个irqaction结构,并调用__setup_irq函数

__setup_irq内容比较多,主要完成几个方面的工作

1.添加irqaction结构到irq_desc的action链表中,需要判断是否为共享中断,只有共享中断可以添加多个中断处理函数,如果是共享中断,则要检查中断处理函数是否和链表中其他函数的触发方式等是否相同,只有一致才可以添加到链表中。

2.设置一些irq_chip结构中的函数指针指向默认函数

3.设置中断的触发方式和启动中断

完成request_irq后中断就可以被接收和处理了。下面我在整理一下设置GPIO口为中断输入的流程

request_irq() --> request_threaded_irq -->__setup_irq() -->
__irq_set_trigger() (定义在kernel/irq/manage.c里) -->set_type

还 有印象没,set_type是irq_chip结构中的一个回调函数,在arch/arm/plat-s3c24xx/irq.c具体的中断的 irq_chip结构中set_type即对应s3c_irqext_type() (定义在arch/arm/plat-s3c24xx/irq.c里),看一个外部中断的代码

arch/arm/plat-s3c24xx/irq.c

static struct irq_chip s3c_irqext_chip = {

.name          = "s3c-ext",

.mask          = s3c_irqext_mask,

.unmask        = s3c_irqext_unmask,

.ack           = s3c_irqext_ack,

.set_type      = s3c_irqext_type,

.set_wake      = s3c_irqext_wake

};

再看s3c_irqext_type() 函数,其中就设置了GPIO端口为中断输入模式

if ((irq
>= IRQ_EINT0) && (irq <= IRQ_EINT3))

{

gpcon_reg
= S3C2410_GPFCON;

extint_reg
= S3C24XX_EXTINT0;

gpcon_offset
= (irq - IRQ_EINT0) * 2;

extint_offset
= (irq - IRQ_EINT0) * 4;

}

<三>中断的处理流程

中断处理流程又是一个比较复杂的过程,要牵涉到ARM的工作模式,异常,异常向量,还有一堆汇编代码。这些我不怎么懂,我只是看了其他一些人的分析,大概整个流程出来吧。

首先异常向量表,保存一条跳转指令,一般存放在0x00000000或者0xffff000地址,linux使用后者,中断发生后CPU进入异常模式,将跳转到相应的异常向量表处执行,异常向量表保存跳转指令,经过一段汇编,最后跳转到中断的入口asm_do_IRQ

arch/arm/kernel/irq.c

asmlinkage void __exception asm_do_IRQ(unsigned int irq,
struct pt_regs *regs)

{

struct
pt_regs *old_regs = set_irq_regs(regs);

irq_enter();

/*

* Some hardware gives randomly wrong
interrupts.  Rather

* than crashing, do something sensible.

*/

if
(unlikely(irq >= NR_IRQS)) {

if
(printk_ratelimit())

printk(KERN_WARNING
"Bad IRQ%u\n", irq);

ack_bad_irq(irq);

} else {

generic_handle_irq(irq);

}

/* AT91 specific
workaround */

irq_finish(irq);

irq_exit();

set_irq_regs(old_regs);

}

主要调用generic_handle_irq(irq)

include/linux/irq.h

static inline void generic_handle_irq_desc(unsigned int
irq, struct irq_desc *desc)

{

#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ

desc->handle_irq(irq,
desc);

#else

if
(likely(desc->handle_irq))

desc->handle_irq(irq,
desc);

else

__do_IRQ(irq);

#endif

}

static inline void generic_handle_irq(unsigned int irq)

{

generic_handle_irq_desc(irq,
irq_to_desc(irq));

}

generic_handle_irq调用前面定义的generic_handle_irq_desc,而
generic_handle_irq_desc也没做什么,调用desc——>handle_irq,这个函数就是irq_desc中的成员,我 们在s3c24xx_init_irq()中不是设置了一个中断函数入口的吗?(set_irq_handler() ),这里就用到了。
这里调用desc->handle_irq分为俩种情况,一是单独的中断号的,一是共享中断号的,俩者的区别在于后者需要先判断是共享中断的中的哪一个然后再真正的去调用handle_irq,所以我这里分析一下单独中断号的处理流程,共享中断也是一样可以分析。

我们在回到s3c24xx_init_irq()中,分析一个具体的,以外部中断为例

for (irqno =
IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {

irqdbf("registering
irq %d (ext int)\n", irqno);

set_irq_chip(irqno,
&s3c_irq_eint0t4);

set_irq_handler(irqno,
handle_edge_irq);

set_irq_flags(irqno,
IRQF_VALID);

}

上面代码我们看到,set_irq_handler的值是handler_edge_irq
,这里是处理边沿触发的中断函数,当然还有电平触发方式的中断(handler_level_irq),继续看代码

kernel/irq/chip.c

void

handle_edge_irq(unsigned int irq, struct irq_desc *desc)

{

spin_lock(&desc->lock);         上锁

desc->status
&= ~(IRQ_REPLAY | IRQ_WAITING);

/*

* If we're currently running this IRQ, or its
disabled,

* we shouldn't process the IRQ. Mark it
pending, handle

* the necessary masking and go out

*/

if
(unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||   判断

!desc->action)) {

desc->status
|= (IRQ_PENDING | IRQ_MASKED);

mask_ack_irq(desc,
irq);   屏蔽并清除中断

goto
out_unlock;

}

kstat_incr_irqs_this_cpu(irq,
desc); 中断统计计数

/* Start
handling the irq */

if (desc->chip->ack)      应答中断

desc->chip->ack(irq);

/* Mark the
IRQ currently in progress.*/

desc->status
|= IRQ_INPROGRESS;   标记中断状态

do {

struct
irqaction *action = desc->action;

irqreturn_t
action_ret;

if
(unlikely(!action)) {

desc->chip->mask(irq);

goto
out_unlock;

}

/*

* When another irq arrived while we were
handling

* one, we could have masked the irq.

* Renable it, if it was not disabled in
meantime.

*/

if
(unlikely((desc->status &

(IRQ_PENDING | IRQ_MASKED |
IRQ_DISABLED)) ==

(IRQ_PENDING | IRQ_MASKED))) {

desc->chip->unmask(irq);

desc->status
&= ~IRQ_MASKED;

}

desc->status
&= ~IRQ_PENDING;

spin_unlock(&desc->lock);

action_ret
= handle_IRQ_event(irq, action);  处理中断,最重要的函数,注意参数,action这个参数将联系到我们的用户中断处理函数

if
(!noirqdebug)

note_interrupt(irq,
desc, action_ret);

spin_lock(&desc->lock);

} while
((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

desc->status
&= ~IRQ_INPROGRESS;

out_unlock:

spin_unlock(&desc->lock);

}

进行追踪handle_IRQ_event()

kernel/irq/handle.c

trace_irq_handler_entry(irq,
action);

ret =
action->handler(irq, action->dev_id);

trace_irq_handler_exit(irq,
action, ret);

调用action中的handler,我们注册中断的时候注意任务就是构造一个irqaction结构并添加到irq_desc中的irqaction链表中的指针action下面。现在处理中断中我们就看到了调用了我们自己的中断处理函数来处理中断了。

至此中断处理流程就结束了,如有不足之处,或者不对的地方,欢迎大家指出,转载请标明出处。

2011-09-23 19:48

上一篇:转-Linux启动过程详解(inittab、rc.sysinit、rcX.d、rc.local)


下一篇:RedHat Linux 5.5 x64系统NTP时钟服务器架设过程