2010年(7)
分类: 嵌入式
2010-05-18 15:14:36
我们接着看irq_handler:
arch/arm/kernel/entry-armv.S:
.macro irq_handler
1: get_irqnr_and_base r0, r6, r5, lr @平台相关,获取中断号
movne r1, sp @如果r0(中断号)不等于0,则r1指向sp所在地址,即pt_regs对象地址(看上图)
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, 1b @ 如果r0(中断号)不等于0, lr(返回地址)等于标号1处,即
@ get_irqnr_and_base r0, r6, r5, lr的那行,即循环处理所有的中断。
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, 1b
bne do_IPI
#ifdef CONFIG_LOCAL_TIMERS
test_for_ltirq r0, r6, r5, lr
movne r0, sp
adrne lr, 1b
bne do_local_timer
#endif
#endif
.endm
get_irqnr_and_base是平台相关的,这里就不列出来了,对于s3c2410,代码在include/asm-arm/s3c2410/entry-macro.S里,该宏处理完后,r0 = 中断号, 接下来r1赋值为sp地址(pt_regs对象地址), 最后调用c函数asm_do_IRQ, r0, r1作为参数被传递进去。asm_do_IRQ()处理完后将返回到lr指向的地址处即上面汇编部分标号为1的地址处继续执行。
我们把__irq_usr的汇编部分分析完后再来分析asm_do_IRQ()等c函数。
Arch/arm/kernel/entry-armv.S:
__irq_usr:
……
……
mov why, #0 @ why = 0, why 是 r8的别名,
b ret_to_user @返回到用户模式下
我们看ret_to_user
arch/arm/kernel/entry-common.S:
ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq @ disable interrupts @关中断,
ldr r1, [tsk, #TI_FLAGS] @获取thread_info中flags域的值
tst r1, #_TIF_WORK_MASK @判断task是否被阻塞
bne work_pending @根据需要进行进程的切换。
no_work_pending:
@ slow_restore_user_regs
ldr r1, [sp, #S_PSR] @ get calling cpsr 获取被中断代码处的状态(cpsp)
ldr lr, [sp, #S_PC]! @ get pc 获取返回地址(被中断代码的下条代码处的地址)
msr spsr_cxsf, r1 @ save in spsr_svc, spsr里保存好被中断代码处的状态(cpsp)
ldmdb sp, {r0 - lr}^ @ get calling r1 – lr 从栈上获取用户态下的r0到lr的值
mov r0, r0
add sp, sp, #S_FRAME_SIZE - S_PC @栈地址恢复,避免多个中断后溢出
movs pc, lr @ return & move spsr_svc into cpsr, 返回被中断代码处继续执行,并把spsr赋给cpsp,即恢复被中断处的现场状态。这样CPU又可以从被中断的地方继续执行了,而且这个时候所有的寄存器值(r0到r12),包括状态寄存器值(cpsr)都是源码被中断时的值。
我们顺便看下work_pending
arch/arm/kernel/entry-common.S:
work_pending:
tst r1, #_TIF_NEED_RESCHED @判断是否需要调度进程
bne work_resched @进程调度
tst r1, #_TIF_NOTIFY_RESUME | _TIF_SIGPENDING
beq no_work_pending @无需调度,返回
mov r0, sp @ 'regs'
mov r2, why @ 'syscall'
bl do_notify_resume
b ret_slow_syscall @ Check work again
由该汇编可知,如果在用户模式下产生中断的话,在返回的时候,会根据需要进行进程调度,而从代码可知,如果中断发生在管理等内核模式下的话是不会进行进程调度的。
Ok, 中断的流程大体就是这样的,下面我们就开始分析c函数里的中断流程。
先来看asm_do_IRQ
arch/arm/kernel/Irq.c:
/*
* do_IRQ handles all hardware IRQ's. Decoded IRQs should not
* come via this function. Instead, they should provide their
* own 'handler'
*/
asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct irqdesc *desc = irq_desc + irq; /*获取中断描述符*/
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS) /*参数检查*/
desc = &bad_irq_desc;
irq_enter();
desc_handle_irq(irq, desc, regs); /*中断处理*/
/* AT91 specific workaround */
irq_finish(irq);
irq_exit();
}
该函数的调用desc_handle_irq()来继续处理中断。
include/asm-arm/mach/Irq.h:
/*
* Obsolete inline function for calling irq descriptor handlers.
*/
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc,
struct pt_regs *regs)
{
desc->handle_irq(irq, desc, regs);
}
调用中断描述符的handler_irq函数来处理该中断,对于IRQ_WDT 就是do_edge_IRQ(前面分析过)。
include/asm-arm/mach/Irq.h:
#define do_edge_IRQ handle_edge_irq
kernel/irq/Chip.c:
/**
* handle_edge_irq - edge type IRQ handler
* @irq: the interrupt number
* @desc: the interrupt description structure for this irq
* @regs: pointer to a register structure
*
* Interrupt occures on the falling and/or rising edge of a hardware
* signal. The occurence is latched into the irq controller hardware
* and must be acked in order to be reenabled. After the ack another
* interrupt can happen on the same source even before the first one
* is handled by the assosiacted event handler. If this happens it
* might be necessary to disable (mask) the interrupt depending on the
* controller hardware. This requires to reenable the interrupt inside
* of the loop which handles the interrupts which have arrived while
* the handler was running. If all pending interrupts are handled, the
* loop is left.
*/
void fastcall
handle_edge_irq(unsigned int irq, struct irq_desc *desc, struct pt_regs *regs)
{
const unsigned int cpu = smp_processor_id();
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
*/
/*
* 如果该中断正在处理或者该中断被disable掉了的话,就不处理该中断,并清掉pending
* 寄存器里的相应位
*/
if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
!desc->action)) {
desc->status |= (IRQ_PENDING | IRQ_MASKED);
mask_ack_irq(desc, irq); /*mask该中断,清pending标志位*/
goto out_unlock;
}
kstat_cpu(cpu).irqs[irq]++; /*统计中断数量*/
/* Start handling the irq */
/*开始处理中断,先清掉pending标志位*/
desc->chip->ack(irq);
/* Mark the IRQ currently in progress.*/
desc->status |= IRQ_INPROGRESS; /*标上正在处理的标记*/
do {
struct irqaction *action = desc->action; /*获取该中断的action*/
irqreturn_t action_ret;
if (unlikely(!action)) {
desc->chip->mask(irq) /*如果没有注册action,则mask该中断*/;
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.
*/
/*
* 如果以前被mask掉的话,在这里把它打开
*/
if (unlikely((desc->status &
(IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
(IRQ_PENDING | IRQ_MASKED))) {
desc->chip->unmask(irq); /*unmask该中断*/
desc->status &= ~IRQ_MASKED;
}
desc->status &= ~IRQ_PENDING;
spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, regs, action); /*处理中断事件*/
if (!noirqdebug)
note_interrupt(irq, desc, action_ret, regs);
spin_lock(&desc->lock);
/*如果有IRQ_PENDING状态,则说明又有中断产生过,则继续执行*/
} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
desc->status &= ~IRQ_INPROGRESS;
out_unlock:
spin_unlock(&desc->lock);
}
该函数的大体功能都在函数体内解释出来了,这里我们对调用的每个函数在进行分析。
先看mask_ack_irq
kernel/irq/Chip.c:
static inline void mask_ack_irq(struct irq_desc *desc, int irq)
{
if (desc->chip->mask_ack) /*对于IRQ_WDT, 该函数没定义*/
desc->chip->mask_ack(irq);
else {
desc->chip->mask(irq); /*对于IRQ_WDT,该函数就是s3c_irq_mask*/
desc->chip->ack(irq); /*对于IRQ_WDT,该函数就是s3c_irq_ack*/
}
}
可以看到它调用具体平台相关的mask函数来处理该中断。
我们来看s3c_irq_mask
arch/arm/mach-s3c2410/Irq.c:
static void
s3c_irq_mask(unsigned int irqno)
{
unsigned long mask;
irqno -= IRQ_EINT0;
mask = __raw_readl(S3C2410_INTMSK);
mask |= 1UL << irqno; /*mask掉对应的中断号*/
__raw_writel(mask, S3C2410_INTMSK); /*写MASK寄存器*/
}
改函数仅仅是把MASK寄存器中对应的中断mask掉,即不再响应该中断
arch/arm/mach-s3c2410/Irq.c:
static inline void
s3c_irq_ack(unsigned int irqno)
{
unsigned long bitval = 1UL << (irqno - IRQ_EINT0);
/*清除pending寄存器的相应位*/
__raw_writel(bitval, S3C2410_SRCPND);
__raw_writel(bitval, S3C2410_INTPND);
}
由上面这两个函数可以看出来mask_ack_irq的作用是先mask掉该中断,并清除pending位,中断被mask掉后系统就不再响应了, 而pending位被清掉说明系统中该中断没有触发。一般在中断处理完后都要清pending位, 要不然系统会认为该中断又被触发了。
handle_edge_irq()里调用的unmask函数,其实就是打开相应的中断,让系统响应这个中断,代码就不列出来了。
接下来中断看handle_IRQ_event(),它才是真正的中断处理函数。
kernel/irq/handle.c:
/**
* handle_IRQ_event - irq action chain handler
* @irq: the interrupt number
* @regs: pointer to a register structure
* @action: the interrupt action chain for this irq
*
* Handles the action chain of an irq event
*/
irqreturn_t handle_IRQ_event(unsigned int irq, struct pt_regs *regs,
struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
handle_dynamic_tick(action);
/*下面这个if判断:当执行action操作时是否可以打开中断*/
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq(); /*打开中断*/
do {
/*
* 中断handler,也就是我们通过request_irq注册的中断函数,对于IRQ_WDT就是
* s3c2410wdt_irq
*/
ret = action->handler(irq, action->dev_id, regs);
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next; /*记得吗,如果该中断可以共享的话,它就不为NULL*/
} while (action);
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
该函数主要就是调用了action的handler函数,也就是我们用request_irq注册的中断例程。这里需要注意的是:如果我们注册中断的时候指明可以共享的话,则必须在我们的中断例程里判断当前产生的中断是否就是我们自己的中断,这可以通过传进来的参数来判断(该参数就是我们注册时提供的)。
OK, 到这里整个中断的流程就大致分析完了