@ r4 - orig_r0 (see pt_regs definition in ptrace.h)
@
stmia r5, {r0 - r4}
// 这一段代码保存所有的寄存器
asm_trace_hardirqs_off
.endm
这个宏主要就是保存各个寄存器值到栈上相应的位置,这个宏执 行完后的栈如下所示:
接着来看get_thread_info,它也是个宏,用来获取当前线程的地址。如果配置了内核抢占,则会执行宏展开的代码。线程的定义在include/linux/sched.h中:
union thread_union {
struct thread_info thread_info; // 线程属性
unsigned long stack[THREAD_SIZE/sizeof(long)]; // 栈
};
由它定义的线程是8K字节边界对齐的,并且在这8K的最低地址处存放的就是thread_info对象,即该栈拥有者线程的对象,而get_thread_info就是通过把sp低13位清0(8K边 界)来获取当前thread_info对象的地址。get_thread_info宏在arch/arm/kernel/entry-header.S中定义:
.macro get_thread_info, rd
mov \rd, sp, lsr #13
mov \rd, \rd, lsl #13
.endm
调用该宏后寄存器tsk里存放的就是当前线程对象的地址了, tsk是哪个寄存器呢,在arch/arm/kernel/entry-header.S文件中我们看到:
tsk .req r9 @ current thread_info
tsk只是r9的别名而已, 因此这时r9里保存的就是当前线程的地址。上面的那一段代码主要完成的工作即是获得线程对象基地址,进而增加线程对象的抢占计数
接着看irq_handler,它在文件arch/arm/kernel/entry-armv.S中定义:
.macro irq_handler
get_irqnr_preamble r5, lr
1: get_irqnr_and_base r0, r6, r5, lr // 平台相关,获取中断号
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
// 中断处理完成后返回的地方:获得中断号的地方,根据中断控制器中相
// 应寄存器的内容作为退出条件。退出时下面的两行代码就会被略过去。
adrne lr, BSYM(1b)
// 通过上面的宏get_irqnr_and_base为调用asm_do_IRQ准备了参数中断号
// struct pt_regs *参数也早已获得,于是乎调用asm_do_IRQ来处理中断
bne asm_do_IRQ
#ifdef CONFIG_SMP
/*
* XXX
*
* this macro assumes that irqstat (r6) and base (r5) are
* preserved from get_irqnr_and_base above
*/
test_for_ipi r0, r6, r5, lr
movne r0, sp
adrne lr, BSYM(1b)
bne do_IPI
#ifdef CONFIG_LOCAL_TIMERS
test_for_ltirq r0, r6, r5, lr
movne r0, sp
adrne lr, BSYM(1b)
bne do_local_timer
#endif
#endif
.endm
对于我们的平台来说get_irqnr_preamble是空的宏。irq_handler首先通过宏 get_irqnr_and_base获得中断号,存入r0。然后把上面建立的pt_regs结构的指针,也就是sp值赋给r1,把调用宏 get_irqnr_and_base的位置作为返回地址(为了循环地处理挂起的所有中断)。最后调用 asm_do_IRQ进一步处理中断。以上这些操作都建立在获得中断号的前提下,也就是有中断发生,某个外部设备触发中断的时候,kernel最终会调用到asm_do_IRQ()函数。
get_irqnr_and_base是平台相关的,这个宏查询ISPR(IRQ挂起中断服务寄存器,当有需要处理的中断时,这个寄存器的相应位会置位,任意时刻,最多一个位会置位),计算出的中断号放在irqnr指定的寄存器中。该宏结束后,r0 = 中断号。这个宏在不同的ARM芯片上是不一样的,它需要读写中断控制器中的寄存器。对于s3c2440,代码在arch/arm/mach-s3c2410/include/entry-macro.S里,用上面的调用参数将宏展开,如下:
1:
mov r5, #S3C24XX_VA_IRQ
@@ try the interrupt offset register, since it is there
ldr r6, [ r5, #INTPND ]
teq r6, #0
beq 1002f
ldr r0, [ r5, #INTOFFSET ]
mov lr, #1
tst r6, lr, lsl r0
bne 1001f
@@ the number specified is not a valid irq, so try
@@ and work it out for ourselves
mov r0, #0 @@ start here
@@ work out which irq (if any) we got
movs lr, r6, lsl#16
addeq r0, r0, #16
moveq r6, r6, lsr#16
tst r6, #0xff
addeq r0, r0, #8
moveq r6, r6, lsr#8
tst r6, #0xf
addeq r0, r0, #4
moveq r6, r6, lsr#4
tst r6, #0x3
addeq r0, r0, #2
moveq r6, r6, lsr#2
tst r6, #0x1
addeq r0, r0, #1
@@ we have the value
1001:
adds r0, r0, #IRQ_EINT0
1002:
@@ exit here, Z flag unset if IRQ
我们把__irq_svc的汇编部分分析完后再来分析asm_do_IRQ()等c函数。宏irq_handler执行完毕,如果配置了抢占,则还会恢复线程对象的抢占计数,获得线程对象的标记字段值,以检查是否需要重新调度。
__irq_svc例程调用svc_exit宏来退出中断处理过程。前面的一条语句,我们看到,中断发生时的CPSR被保存在了r4寄存器中了,这个宏在arch/arm/kernel/entry-armv.S中定义:
.macro svc_exit, rpsr
msr spsr_cxsf, \rpsr
#if defined(CONFIG_CPU_32v6K)
clrex @ clear the exclusive monitor
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
#elif defined (CONFIG_CPU_V6)
ldr r0, [sp]
strex r1, r2, [sp] @ clear the exclusive monitor
ldmib sp, {r1 - pc}^ @ load r1 - pc, cpsr
#else
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
#endif
.endm
这个宏恢复中断时运行环境,也就是各个寄存器中的值,从而推出中断的处理过程。
OK,中断的流程大体就是这样的,下面我们就开始分析c部分的中断处理流程。在上面的汇编语言代码里,我们看到,系统在保存好中断时环境,获得中断号之后,调用了函数asm_do_IRQ(),从而进入中断处理的C程序部分。asm_do_IRQ()函数定义如下:
arch/arm/kernel/irq.c:
108 asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *reg s)
109 {
110 struct pt_regs *old_regs = set_irq_regs(regs);
111
112 irq_enter(); //进入中断上下文
113
114 /*
115 * Some hardware gives randomly wrong interrupts. Rather
116 * than crashing, do something sensible.
117 */
118 if (unlikely(irq >= nr_irqs)) {
119 if (printk_ratelimit())
120 printk(KERN_WARNING "Bad IRQ%u\n", irq);
121 ack_bad_irq(irq);
122 } else {
123 generic_handle_irq(irq); //根据中断号获取中断描述结构体,并调用其中断处理函数。
124 }
125
126 /* AT91 specific workaround */
127 irq_finish(irq); //退出中断上下文
128
129 irq_exit();
130 set_irq_regs(old_regs);
131 }
这个函数完成如下操作:
1、调用set_irq_regs(regs)函数更新处理器的当前帧指针,并在局部变量old_regs中保存老的帧指针
---------------------------------------------------------------------
include/asm-generic/irq_regs.h
21 DECLARE_PER_CPU(struct pt_regs *, __irq_regs);
28 static inline struct pt_regs *set_irq_regs(struct pt_regs *new_regs)
29 {
30 struct pt_regs *old_regs, **pp_regs = &__get_cpu_var(__irq_regs);
31
32 old_regs = *pp_regs;
33 *pp_regs = new_regs;
34 return old_regs;
35 }
---------------------------------------------------------------------
2、调用irq_enter()进入一个中断处理上下文。
3、检查中断号的有效性,有些硬件会随机的给一些错误的中断,做一些检查以防止系统崩溃。如果不正确,就调用ack_bad_irq(irq),该函数会增加用来表征发生的错误中断数量的变量irq_err_count,这个变量貌似仅供了解系统状况之用。
4、若传递的中断号有效,则会掉用generic_handle_irq(irq)来处理中断。
5、调用irq_exit()来推出中断处理上下文。
6、调用set_irq_regs(old_regs)来恢复处理器的当前帧指针。
接下来我们来看看函数generic_handle_irq()对于中断的处理,这个函数仅仅是对generic_handle_irq_desc()函数的封装而已:
include/linux/irq.h
static inline void generic_handle_irq(unsigned int irq)
{
generic_handle_irq_desc(irq, irq_to_desc(irq));
}
如果实现了上层中断处理函数desc->handle_irq就调用它,实际上在中断处理函数s3c24xx_init_irq()中已为每一个中断线分配了一个上层中断处理函数。
如果desc->handle_irq为空就调用通用中断处理函数__do_IRQ(irq);,在干函数中调用了函数handle_IRQ_event(),
在函数handle_IRQ_event()中执行了该条中断线上的每一个中断例程。
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
}
这个函数接收两个参数,中断号及对应的中断描述符指针。体系架构相关的中断处理函数调用这个函数来进行通用IRQ层的中断处理。如果中断的irq_desc结构的handle_irq成员非空则调用它。否则,会调用__do_IRQ()来让通用的IRQ层来处理一个中断。中断描述符irq_desc结构的handle_irq成员因中断类型的不同而不同,在我们前面分析的芯片级中断初始化函数s3c24xx_init_irq()中,我们看到这个字段基本上被设置为了这么几个函数:
用来处理具有多个子中断源的中断线的情况(SoC中断控制器的特性,而不是中断共享)的一组函数s3c_irq_demux_extint4t7()、s3c_irq_demux_extint8()、s3c_irq_demux_uart0()、s3c_irq_demux_uart1)、s3c_irq_demux_uart2()、s3c_irq_demux_adc()
其他情况的handle_edge_irq()函数
还有handle_level_irq()函数,只是很快就被第一种情况的几个函数取代。
OMG,这个地方似乎好复杂,如此之多的函数。不过,结构还是蛮清晰的。首先我们来看这几个特定于SoC的函数:
arch/arm/plat-s3c24xx/irq.c
static void s3c_irq_demux_adc(unsigned int irq,
struct irq_desc *desc)
{
unsigned int subsrc, submsk;
unsigned int offset = 9;
/* read the current pending interrupts, and the mask
* for what it is available */
subsrc = __raw_readl(S3C2410_SUBSRCPND);
submsk = __raw_readl(S3C2410_INTSUBMSK);
subsrc &= ~submsk;
subsrc >>= offset;
subsrc &= 3;
if (subsrc != 0) {
if (subsrc & 1) {
generic_handle_irq(IRQ_TC);
}
if (subsrc & 2) {
generic_handle_irq(IRQ_ADC);
}
}
}
static void s3c_irq_demux_uart(unsigned int start)
{
unsigned int subsrc, submsk;
unsigned int offset = start - IRQ_S3CUART_RX0;
/* read the current pending interrupts, and the mask
* for what it is available */
subsrc = __raw_readl(S3C2410_SUBSRCPND);
submsk = __raw_readl(S3C2410_INTSUBMSK);
irqdbf2("s3c_irq_demux_uart: start=%d (%d), subsrc=0x%08x,0x%08x\n",
start, offset, subsrc, submsk);
subsrc &= ~submsk;
subsrc >>= offset;
subsrc &= 7;
if (subsrc != 0) {
if (subsrc & 1)
generic_handle_irq(start);
if (subsrc & 2)
generic_handle_irq(start+1);
if (subsrc & 4)
generic_handle_irq(start+2);
}
}
/* uart demux entry points */
static void
s3c_irq_demux_uart0(unsigned int irq,
struct irq_desc *desc)
{
irq = irq;
s3c_irq_demux_uart(IRQ_S3CUART_RX0);
}
static void
s3c_irq_demux_uart1(unsigned int irq,
struct irq_desc *desc)
{
irq = irq;
s3c_irq_demux_uart(IRQ_S3CUART_RX1);
}
static void
s3c_irq_demux_uart2(unsigned int irq,
struct irq_desc *desc)
{
irq = irq;
s3c_irq_demux_uart(IRQ_S3CUART_RX2);
}
static void
s3c_irq_demux_extint8(unsigned int irq,
struct irq_desc *desc)
{
unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
eintpnd &= ~eintmsk;
eintpnd &= ~0xff; /* ignore lower irqs */
/* we may as well handle all the pending IRQs here */
while (eintpnd) {
irq = __ffs(eintpnd);
eintpnd &= ~(1<
irq += (IRQ_EINT4 - 4);
generic_handle_irq(irq);
}
}
static void
s3c_irq_demux_extint4t7(unsigned int irq,
struct irq_desc *desc)
{
unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
eintpnd &= ~eintmsk;
eintpnd &= 0xff; /* only lower irqs */
/* we may as well handle all the pending IRQs here */
while (eintpnd) {
irq = __ffs(eintpnd);
eintpnd &= ~(1<
irq += (IRQ_EINT4 - 4);
generic_handle_irq(irq);
}
}
话说这几个函数执行的操作都还是非常相似的:
SoC中断控制器中有一个中断挂起寄存器SRCPND,当相应的中断发生时,这个寄存器中相应的位就被置位,寄存器总共有32位。而实际上SoC支持多得多的中断源,于是中断控制器被扩展,中断挂起寄存器SRCPND中有些位可以表征多个中断源的发生,然后另外有子中断源挂起寄存器SUBSRCPND等来告诉系统到底发生的中断是哪一个。上面的这组函数就是找到产生中断的子中断源的中断号,然后用找到的这个中断号做为参数来调用generic_handle_irq(irq)。更多详细情况可以参考S3C24XX系列SoC的数据手册中断控制器的相关部分内容。
在前面的中断系统初始化函数中我们看到,如果中断线没有子中断源的话,则其中断描述符的handle_irq字段会被设置为handle_edge_irq()函数,接下来我们来看handle_edge_irq()函数:
---------------------------------------------------------------------
krnel/irq/chip.c
void
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
raw_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)) {
mask_irq(desc, 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))) {
unmask_irq(desc, irq);
}
desc->status &= ~IRQ_PENDING;
raw_spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, action);
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);
raw_spin_lock(&desc->lock);
} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
desc->status &= ~IRQ_INPROGRESS;
out_unlock:
raw_spin_unlock(&desc->lock);
}
这个函数接收两个参数,irq为中断号, desc为相应的中断描述符。中断发生在硬件信号的上升沿或下降沿。中断被锁进中断控制器,中断必须被确认,以重新使能。在中断被确认之后,则另一个在相同的中断源上的中断就可能会发生,即使前面的一个正在被相关的中断处理程序处理。如果这种情况发生了,则通过硬件控制器,禁用中断是必要的。这需要在处理中断处理程序执行时产生的中断的中断处理循环中重新使能中断。如果所有挂起的中断都已经被处理,则循环退出。
这个函数完成如下操作:
1、获得中断描述符的自旋锁。
2、清除中断描述符状态字段status的IRQ_REPLAY和IRQ_WAITING标志。
3、检查desc->status及desc->action,若desc->status设置了IRQ_INPROGRESS或IRQ_DISABLED标志,即中断处理中或中断禁用,或者desc->action为空,则设置desc->status的IRQ_PENDING和IRQ_MASKED标志,屏蔽并确认中断,释放自旋锁并退出。
4、增加中断产生计数值。
5、若desc->chip->ack非空,则调用desc->chip->ack(irq)开始处理中断。
6、标记IRQ处理当前正在进行中。
7、通过一个循环来处理中断。主要完成的工作即是调用中断描述符的irqaction链。
在这里我们可以看一下Linux内核中对于中断嵌套的处理。Linux使用desc->status的IRQ_INPROGRESS来标记中断处理正在进行中,当第一次进入中断处理时,设置相应的中断描述符状态字段的该标志。则在重新使能中断后,即使前面的中断处理过程还没有结束,依然有可能会产生中断会进入中断处理流程。则在后面的中断处理流程里,进入handle_edge_irq()后,检测到前一个中断处理流程没有结束,则仅仅是设置desc->status的IRQ_PENDING和IRQ_MASKED标志便迅速退出。而在前一个中断处理流程的handle_edge_irq()的一个do{}while循环结束后,会检查desc->status的IRQ_PENDING和IRQ_MASKED标志,若设置了这两个标志,则会进行另外的一个中断处理do{}while循环。
8、清除desc->status的IRQ_INPROGRESS标志,释放自旋锁。
我们接着来看handle_IRQ_event()函数,这个函数定义为:
kernel/irq/handle.c
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
do {
trace_irq_handler_entry(irq, action);
ret = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, ret);
switch (ret) {
case IRQ_WAKE_THREAD:
/*
* Set result to handled so the spurious check
* does not trigger.
*/
ret = IRQ_HANDLED;
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
/*
* Wake up the handler thread for this
* action. In case the thread crashed and was
* killed we just pretend that we handled the
* interrupt. The hardirq handler above has
* disabled the device interrupt, so no irq
* storm is lurking.
*/
if (likely(!test_bit(IRQTF_DIED,
&action->thread_flags))) {
set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
wake_up_process(action->thread);
}
/* Fall through to add to randomness */
case IRQ_HANDLED:
status |= action->flags;
break;
default:
break;
}
retval |= ret;
action = action->next;
} while (action);
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
该函数主要的工作即是逐个地调用特定中断号的action链表的handler函数,也就是我们在驱动程序中用request_irq注册的中断例程。这里需要注意的是:如果我们注册中断的时候指明可以共享的话,则必须在我们的中断例程里判断当前产生的中断是否就是我们自己的中断,这可以通过传进来的参数来判断(该参数就是我们注册时提供的action->dev_id)。
接下来来看handle_level_irq()函数,其定义为:
kernel/irq/handle.c
void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
struct irqaction *action;
irqreturn_t action_ret;
raw_spin_lock(&desc->lock);
mask_ack_irq(desc, irq);
if (unlikely(desc->status & IRQ_INPROGRESS))
goto out_unlock;
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
kstat_incr_irqs_this_cpu(irq, desc);
/*
* If its disabled or no action available
* keep it masked and get out of here
*/
action = desc->action;
if (unlikely(!action || (desc->status & IRQ_DISABLED)))
goto out_unlock;
desc->status |= IRQ_INPROGRESS;
raw_spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, action);
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);
raw_spin_lock(&desc->lock);
desc->status &= ~IRQ_INPROGRESS;
if (!(desc->status & (IRQ_DISABLED | IRQ_ONESHOT)))
unmask_irq(desc, irq);
out_unlock:
raw_spin_unlock(&desc->lock);
}
这个函数的功能基本上和handle_edge_irq()相同,只不过这个函数用来处理电平触发的中断,而handle_edge_irq()则用来处理边缘触发的中断。
void set_irq_flags(unsigned int irq, unsigned int iflags)
{
struct irq_desc *desc;
unsigned long flags;
if (irq >= nr_irqs) {
printk(KERN_ERR "Trying to set irq flags for IRQ%d\n", irq);
return;
}
desc = irq_to_desc(irq);
raw_spin_lock_irqsave(&desc->lock, flags);
desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
if (iflags & IRQF_VALID)
desc->status &= ~IRQ_NOREQUEST;
if (iflags & IRQF_PROBE)
desc->status &= ~IRQ_NOPROBE;
if (!(iflags & IRQF_NOAUTOEN))
desc->status &= ~IRQ_NOAUTOEN;
raw_spin_unlock_irqrestore(&desc->lock, flags);
}
generic_handle_irq函数直接调用desc结构中的handle_irq成员函数,它就是irq_desc[irq].handle.irq
asm_do_IRQ函数中参数irq的取值范围为IRQ_EINT0~(IRQ_EINT0 + 31),只有32个取值。它可能是一个实际的中断号,也可能是一组中断的中断号。这里有S3C2410的芯片特性决定的:发生中断时,INTPND寄存器的某一位被置1,INTOFFSET寄存器中记录了是哪一位(0--31),中断向量调用asm_do_IRQ之前要把INTOFFSET寄存器的值确定 irq参数。每一个实际的中断在irq_desc数组中都有一项与它对应,它们的数目不止32.当asm_do_IRQ函数参数irq表示的是“一组”中断时,irq_desc[irq].handle_irq成员函数还需要先分辨出是哪一个中断,然后调用 irq_desc[irqno].handle_irq来进一步处理。
以外部中断EINT8—EINT23为例,它们通常是边沿触发
(1) 它们被触发里,INTOFFSET寄存器中的值都是5,asm_do_IRQ函数中参数irq的值为(IRQ_EINTO+5),即IRQ_EINT8t23,
(2)irq_desc[IRQ_EINT8t23].handle_irq在前面init_IRQ函数初始化中断体系结构的时候被设为s3c_irq_demux_extint8.
(3)s3c_irq_demux_extint8函数的代码在arch/arm/plat-s3c24xx/irq.c中,它首先读取 EINTPEND、EINTMASK寄存器,确定发生了哪些中断,重新计算它们的中断号,然后调用irq_desc数组项中的handle_irq成员函数
452 static void
453 s3c_irq_demux_extint8(unsigned int irq,
454 struct irq_desc *desc)
455 {
456 unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);//EINT8-EINT23 发生时,相应位被置1
457 unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);//屏蔽寄存器
458
459 eintpnd &= ~eintmsk; //清除被屏蔽的位
460 eintpnd &= ~0xff; /* 清除低8位(EINT8对应位8)ignore lower irqs */
461
462 /* we may as well handle all the pending IRQs here */
463 /* 循环处理所有子中断*/
464 while (eintpnd) {
465 irq = __ffs(eintpnd); //确定eintpnd中为1的最高位
466 eintpnd &= ~(1<
467
468 irq += (IRQ_EINT4 - 4); //重新计算中断号,前面计算出irq等于8时,中断号为IRQ_EINT8
469 generic_handle_irq(irq);//调用这中断的真正的处理函数
470 }
471
472 }
(4)IRQ_EINT8--IRQ_EINT23这几个中断的处理函数入口,在init_IRQ函数初始化中断体系结构的时候已经被设置为 handle_edge_irq函数,desc_handle_irq(irq,irq_desc+irq)就是调用这个函数,它在kernel/irq /chip.c中定义,它用来处理边沿触发的中断,中断发生的次数统计
578 void
579 handle_edge_irq(unsigned int irq, struct irq_desc *desc)
580 {
581 raw_spin_lock(&desc->lock);
582
583 desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
584
585 /*
586 * If we‘re currently running this IRQ, or its disabled,
587 * we shouldn‘t process the IRQ. Mark it pending, handle
588 * the necessary masking and go out
589 */
590 if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
591 !desc->action)) {
592 desc->status |= (IRQ_PENDING | IRQ_MASKED);
593 mask_ack_irq(desc, irq);
594 goto out_unlock;
595 }
596 kstat_incr_irqs_this_cpu(irq, desc);
597
598 /* Start handling the irq */
599 if (desc->chip->ack)
600 desc->chip->ack(irq);
601
602 /* Mark the IRQ currently in progress.*/
603 desc->status |= IRQ_INPROGRESS;
604
605 do {
606 struct irqaction *action = desc->action;
607 irqreturn_t action_ret;
608
609 if (unlikely(!action)) {
610 mask_irq(desc, irq);
611 goto out_unlock;
612 }
613
614 /*
615 * When another irq arrived while we were handling
616 * one, we could have masked the irq.
617 * Renable it, if it was not disabled in meantime.
618 */
619 if (unlikely((desc->status &
620 (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
621 (IRQ_PENDING | IRQ_MASKED))) {
622 unmask_irq(desc, irq);
623 }
624
625 desc->status &= ~IRQ_PENDING;
626 raw_spin_unlock(&desc->lock);
627 action_ret = handle_IRQ_event(irq, action);
628 if (!noirqdebug)
629 note_interrupt(irq, desc, action_ret);
630 raw_spin_lock(&desc->lock);
631
632 } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDIN G);
633
634 desc->status &= ~IRQ_INPROGRESS;
635 out_unlock:
636 raw_spin_unlock(&desc->lock);
637 }
响应中断,通常是清除当前中断使得可以接收下一个中断,对于IRQ_EINT8~IRQ_EINT23这几个中断,desc->chip在前面init_IRQ函数初始化中断体系结构的时候被设为s3c_irqext_chip.desc->chip->ack就是 s3c_irqext_ack函数,(arch/armplat-s3c24xx/irq.c)它用来清除中断handle_IRQ_event函数来逐个执行action链表中用户注册的中断处理函数,它在kernel/irq/handle.c中定义。
368 irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
369 {
370 irqreturn_t ret, retval = IRQ_NONE;
371 unsigned int status = 0;
372
373 do {
374 trace_irq_handler_entry(irq, action);
375 ret = action->handler(irq, action->dev_id);//执行用户注册的中断处理函数
376 trace_irq_handler_exit(irq, action, ret);
377
378 switch (ret) {
379 case IRQ_WAKE_THREAD:
380 /*
381 * Set result to handled so the spurious check
382 * does not trigger.
383 */
384 ret = IRQ_HANDLED;
385
386 /*
387 * Catch drivers which return WAKE_THREAD but
388 * did not set up a thread function
389 */
390 if (unlikely(!action->thread_fn)) {
391 warn_no_thread(irq, action);
392 break;
393 }
394
395 /*
396 * Wake up the handler thread for this
397 * action. In case the thread crashed and was
398 * killed we just pretend that we handled the
399 * interrupt. The hardirq handler above has
400 * disabled the device interrupt, so no irq
401 * storm is lurking.
402 */
403 if (likely(!test_bit(IRQTF_DIED,
404 &action->thread_flags))) {
405 set_bit(IRQTF_RUNTHREAD, &action->thread_fla gs);
406 wake_up_process(action->thread);
407 }
408
409 /* Fall through to add to randomness */
410 case IRQ_HANDLED:
411 status |= action->flags;
412 break;
413
414 default:
415 break;
416 }
417
418 retval |= ret;
419 action = action->next;
420 } while (action);
421
422 if (status & IRQF_SAMPLE_RANDOM)
423 add_interrupt_randomness(irq);
424 local_irq_disable();
425
426 return retval;
427 }
用户注册的中断处理函数的参数为中断号irq,action->dev_id。后一个参数是通过request_irq函数注册中断时传入的dev_id参数,它由用户自己指定、自己使用,可以为空,当这个中断是“共享中断”时除外。
对于电平触发的中断,它们的irq_desc[irq].handle_irq通常是handle_level_irq函数。它也是在kernel/irq/chip.c中定义,其功能与上述handle_edge_irq函数相似,
对于handle_level_irq函数已经清除了中断,但是它只限于清除SoC内部的的信号,如果外设输入到SoC的中断信号仍然有效,这就会导致当前中断处理完成后,会误认为再次发生了中断,对于这种情况,需要用户注册的中断处理函数中清除中断,先清除外设的中断,然后再清除SoC内部的中断号。
中断的处理流程可以总结如下
(1)中断向量调用总入口函数asm_do_IRQ,传入根据中断号irq
(2)asm_do_IRQ函数根据中断号irq调用irq_desc[irq].handle_irq,它是这个中断的处理函数入口,对于电平触发的中断,这个入口函数通常为handle_level_irq,对于边沿触发的中断,这个入口通常为handle_edge_irq
(3)入口函数首先清除中断,入口函数是handle_level_irq时还要屏蔽中断
(4)逐个调用用户在irq_desc[irq].aciton链表中注册的中断处理函数
(5) 入口函数是handle_level_irq时还要重新开启中断
卸载中断处理函数这通过free_irq函数来实现,它与request_irq一样,也是在kernel/irq/mangage.c中定义。
它需要用到两个参数:irq和dev_id,它们与通过request_irq注册中断函数时使用的参数一样,使用中断号irq定位action链表,再使用dev_id在action链表中找到要卸载的表项。同一个中断的不同中断处理函数必须使用不同的dev_id来区分,在注册共享中断时参数 dev_id必惟一。
free_irq函数的处理过程与request_irq函数相反
(1)根据中断号irq,dev_id从action链表中找到表项,将它移除
(2)如果它是惟一的表项,还要调用IRQ_DESC[IRQ].CHIP->SHUTDOWN 或IRQ_DESC[IRQ].CHIP->DISABLW来关闭中断。
在响应一个特定的中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)或中断服务例程(interrupt service routine ,ISP).产生中断的每个设备都有一个相应的中断处理程序,中断处理程序通常不和特定的设备关联,而是和特定的中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该就可以对应多个中断处理程序,相应的,该设备的驱动程序也就要准备多个这样的函数。在Linux内核中处理中断是分为上半部(top half),和下半部(bottom half)之分的。上半部只做有严格时限的工作,例如对接收到的中断进行应答或复位硬件,这些工作是在所有的中断被禁止的情况下完成的,能够被允许稍后完成的工作会推迟到下半部去。要想了解上半部和下半部的机制可以阅读一下《Linux内核设计与实现》
四、对数据异常的处理的do_DataAbort函数
以svc模式数据异常的处理为例,说明异常处理
1120 vector_stub dabt, ABT_MODE, 8
1121
1122 .long __dabt_usr @ 0 (USR_26 / USR_32)
1123 .long __dabt_invalid @ 1 (FIQ_26 / FIQ_32)
1124 .long __dabt_invalid @ 2 (IRQ_26 / IRQ_32)
1125 .long __dabt_svc @ 3 (SVC_26 / SVC_32)
数据异常发生时,跳转到__dabt_svc接口处理,如下:
__dabt_svc:
svc_entry
@
@ get ready to re-enable interrupts if appropriate
@
mrs r9, cpsr
tst r3, #PSR_I_BIT
biceq r9, r9, #PSR_I_BIT
@
@ Call the processor-specific abort handler:
@
@ r2 - aborted context pc
@ r3 - aborted context cpsr
@
@ The abort handler must return the aborted address in r0, and
@ the fault status register in r1. r9 must be preserved.
@
#ifdef MULTI_DABORT
ldr r4, .LCprocfns
mov lr, pc
ldr pc, [r4, #PROCESSOR_DABT_FUNC]
#else
bl CPU_DABORT_HANDLER
#endif
@
@ set desired IRQ state, then call main handler
@
msr cpsr_c, r9
mov r2, sp
bl do_DataAbort
@
@ IRQs off again before pulling preserved data off the stack
@
disable_irq_notrace
@
@ restore SPSR and restart the instruction
@
ldr r2, [sp, #S_PSR]
svc_exit r2 @ return from exception
UNWIND(.fnend )
ENDPROC(__dabt_svc)
.align 5
do_DataAbort:为数据异常处理函数,如下:
arch/arm/mm/fault.c
asmlinkage void __exception
do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{
const struct fsr_info *inf = fsr_info + fsr_fs(fsr);
struct siginfo info;
if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs))
return;
printk(KERN_ALERT "Unhandled fault: %s (0x%03x) at 0x%08lx\n",
inf->name, fsr, addr);
info.si_signo = inf->sig;
info.si_errno = 0;
info.si_code = inf->code;
info.si_addr = (void __user *)addr;
arm_notify_die("", regs, &info, fsr, 0);
}
该接口打印相关的信息后,调用arm_notify_die接口,根据用户空间还是内核空间的异常,进行不同的处理操作,如下:
arch/arm/kernel/traps.c
void arm_notify_die(const char *str, struct pt_regs *regs,
struct siginfo *info, unsigned long err, unsigned long trap)
{
if (user_mode(regs)) {
current->thread.error_code = err;
current->thread.trap_no = trap;
force_sig_info(info->si_signo, info, current);
} else {
die(str, regs, err);
}
}
对用户空间的数据异常,调用force_sig_info发送信号;
对内核空间的数据异常,调用die打印相应的异常信息,然后panic内核,如下:
arch/arm/kernel/traps.c
void die(const char *str, struct pt_regs *regs, int err)
{
struct thread_info *thread = current_thread_info();
int ret;
oops_enter();
spin_lock_irq(&die_lock);
console_verbose();
bust_spinlocks(1);
ret = __die(str, err, thread, regs); //打印异常发生时的堆栈等信息
if (regs && kexec_should_crash(thread->task))
crash_kexec(regs);
bust_spinlocks(0);
add_taint(TAINT_DIE);
spin_unlock_irq(&die_lock);
oops_exit();
if (in_interrupt())
panic("Fatal exception in interrupt");
if (panic_on_oops)
panic("Fatal exception");
if (ret != NOTIFY_STOP)
do_exit(SIGSEGV);
}
arm-Linux中断处理体系结构与处理流程分析
原文:http://blog.chinaunix.net/uid-20760878-id-5708665.html