linux在ARM平台上的中断流程

时间:2022-10-15 04:22:49

当发生中断时,系统跳转到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! */
}