您的位置:首页 > 运维架构 > Linux

linux在ARM平台上的中断流程

2015-05-10 10:58 316 查看
当发生中断时,系统跳转到ARM平台的异常向量表(vector_irq位置):

.section .vectors, "ax", %progbits
__vectors_start:
W(b)    vector_rst
W(b)    vector_und
W(ldr)  pc, __vectors_start + 0x1000
W(b)    vector_pabt
W(b)    vector_dabt
W(b)    vector_addrexcptn
W(b)    vector_irq
W(b)    vector_fiq


以下的vector_stub irq位置为vector_irq跳转到的位置,后面跟着的是irq分发表,这里只区分了SVC和USR两种情况:

/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4

.long   __irq_usr           @  0  (USR_26 / USR_32)
.long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)
.long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)
.long   __irq_svc           @  3  (SVC_26 / SVC_32)
.long   __irq_invalid           @  4
.long   __irq_invalid           @  5
.long   __irq_invalid           @  6
.long   __irq_invalid           @  7
.long   __irq_invalid           @  8
.long   __irq_invalid           @  9
.long   __irq_invalid           @  a
.long   __irq_invalid           @  b
.long   __irq_invalid           @  c
.long   __irq_invalid           @  d
.long   __irq_invalid           @  e
.long   __irq_invalid           @  f


vector_stub宏的定义如下:

/*
* Vector stubs.
*
* This code is copied to 0xffff1000 so we can use branches in the
* vectors, rather than ldr's.  Note that this code must not exceed
* a page size.
*
* Common stub entry macro:
*   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*
* SP points to a minimal amount of processor-private memory, the address
* of which is copied into r0 for the mode specific abort handler.
*/
.macro  vector_stub, name, mode, correction=0
.align  5

vector_\name:
.if \correction
sub lr, lr, #\correction
.endif

@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia   sp, {r0, lr}        @ save r0, lr
mrs lr, spsr
str lr, [sp, #8]        @ save spsr

@
@ Prepare for SVC32 mode.  IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0

@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
THUMB( adr r0, 1f          )
THUMB( ldr lr, [r0, lr, lsl #2]    )
mov r0, sp
ARM(   ldr lr, [pc, lr, lsl #2]    )
movs    pc, lr          @ branch to handler in SVC mode
ENDPROC(vector_\name)


分别可以由svc和usr模式进入:

.align  5
__irq_svc:
svc_entry
irq_handler

#ifdef CONFIG_PREEMPT
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT]      @ get preempt count
ldr r0, [tsk, #TI_FLAGS]        @ get flags
teq r8, #0              @ if preempt count != 0
movne   r0, #0              @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne    svc_preempt
#endif

svc_exit r5, irq = 1            @ return from exception
UNWIND(.fnend      )
ENDPROC(__irq_svc)

.align  5
__irq_usr:
usr_entry
kuser_cmpxchg_check
irq_handler
get_thread_info tsk
mov why, #0
b   ret_to_user_from_irq
UNWIND(.fnend      )
ENDPROC(__irq_usr)


不论SVC还是USR都会调用IRQ handler,这里可以根据配置选择1:1或者1:N的方式:

/*
* Interrupt handling.
*/
.macro  irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
adr lr, BSYM(9997f)
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
9997:
.endm


当配置为CONFIG_MULTI_IRQ_HANDLER时,handle_arch_irq可以在2个地方初始化:

#ifdef CONFIG_MULTI_IRQ_HANDLER
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq)
return;

handle_arch_irq = handle_irq;
}
#endif

void __init setup_arch(char **cmdline_p)
{
.....
#ifdef CONFIG_MULTI_IRQ_HANDLER
handle_arch_irq = mdesc->handle_irq;
#endif
.....
}


如果CONFIG_MULTI_IRQ_HANDLER未打开,则:

.macro  arch_irq_handler_default
get_irqnr_preamble r6, lr
1:  get_irqnr_and_base r0, r2, r6, lr
movne   r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne   lr, BSYM(1b)
bne asm_do_IRQ

#ifdef CONFIG_SMP
/*
* XXX
*
* this macro assumes that irqstat (r2) and base (r6) are
* preserved from get_irqnr_and_base above
*/
ALT_SMP(test_for_ipi r0, r2, r6, lr)
ALT_UP_B(9997f)
movne   r1, sp
adrne   lr, BSYM(1b)
bne do_IPI
#endif
9997:
.endm


get_irqnr_preamble和get_irqnr_and_base调用具体chip的实现来获取中断号,比如对于mach-davinci而言,其实现如下:

.macro  get_irqnr_preamble, base, tmp
ldr \base, =davinci_intc_base
ldr \base, [\base]
.endm

.macro  get_irqnr_and_base, irqnr, irqstat, base, tmp
#if defined(CONFIG_AINTC) && defined(CONFIG_CP_INTC)
ldr \tmp, =davinci_intc_type
ldr \tmp, [\tmp]
cmp \tmp, #DAVINCI_INTC_TYPE_CP_INTC
beq 1001f
#endif
#if defined(CONFIG_AINTC)
ldr \tmp, [\base, #0x14]
movs \tmp, \tmp, lsr #2
sub \irqnr, \tmp, #1
b 1002f
#endif
#if defined(CONFIG_CP_INTC)
1001:       ldr \irqnr, [\base, #0x80] /* get irq number */
mov \tmp, \irqnr, lsr #31
and \irqnr, \irqnr, #0xff  /* irq is in bits 0-9 */
and \tmp, \tmp, #0x1
cmp \tmp, #0x1
#endif
1002:
.endm


然后就走到asm_do_IRQ这个C环境函数:

/*
* handle_IRQ handles all hardware IRQ's.  Decoded IRQs should
* not come via this function.  Instead, they should provide their
* own 'handler'.  Used by platform code implementing C-based 1st
* level decoding.
*/
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
__handle_domain_irq(NULL, irq, false, regs);
}

/*
* asm_do_IRQ is the interface to be used from assembly code.
*/
asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
handle_IRQ(irq, regs);
}


int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
unsigned int irq = hwirq;
int ret = 0;

irq_enter();

#ifdef CONFIG_IRQ_DOMAIN
if (lookup)
irq = irq_find_mapping(domain, hwirq);
#endif

/*
* Some hardware gives randomly wrong interrupts.  Rather
* than crashing, do something sensible.
*/
if (unlikely(!irq || irq >= nr_irqs)) {
ack_bad_irq(irq);
ret = -EINVAL;
} else {
generic_handle_irq(irq);
}

irq_exit();
set_irq_regs(old_regs);
return ret;
}


以下是根据映射的虚拟中断号,找到对应的struct irq_desc,并执行里面注册的中断处理函数:

/**
* generic_handle_irq - Invoke the handler for a particular irq
* @irq:    The irq number to handle
*
*/
int generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);

if (!desc)
return -EINVAL;
generic_handle_irq_desc(irq, desc);
return 0;
}

static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}


在中断处理函数执行前后分别有irq_enter函数和irq_exit函数:

/*
* Enter an interrupt context.
*/
void irq_enter(void)
{
rcu_irq_enter();
if (is_idle_task(current) && !in_interrupt()) {
/*
* Prevent raise_softirq from needlessly waking up ksoftirqd
* here, as softirq will be serviced on return from interrupt.
*/
local_bh_disable();
tick_irq_enter();
_local_bh_enable();
}

__irq_enter();
}

/*
* Exit an interrupt context. Process softirqs if needed and possible:
*/
void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
WARN_ON_ONCE(!irqs_disabled());
#endif

account_irq_exit_time(current);
preempt_count_sub(HARDIRQ_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();

tick_irq_exit();
rcu_irq_exit();
trace_hardirq_exit(); /* must be last! */
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: