Linux中斷實現方法(二):中斷處理過程
三、中斷處理過程
這一節將以S3C2410為例,描述linux-2.6.26核心中,從中斷開始,中斷是如何一步一步執行到我們註冊函數的。
3.1 中斷向量表 archarmkernelentry-armv.S
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
中斷髮生後,跳轉到b vector_irq + stubs_offset的位置執行。注意現在的向量表的初始位置是0xffff0000。
3.2 中斷跳轉的入口位置 archarmkernelentry-armv.S
.globl __stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4 @IRQ_MODE在includeasmptrace.h中定義:0x12
.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宏的定義為:
.macro vector_stub, name, mode, correction=0
.align 5
vector_ ame:
.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)
msr spsr_cxsf, r0 @為後面進入svc模式做準備
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f @進入中斷前的mode的後4位
@#define USR_MODE 0x00000010
@#define FIQ_MODE 0x00000011
@#define IRQ_MODE 0x00000012
@#define SVC_MODE 0x00000013
@#define ABT_MODE 0x00000017
@#define UND_MODE 0x0000001b
@#define SYSTEM_MODE 0x0000001f
mov r0, sp
ldr lr, [pc, lr, lsl #2] @如果進入中斷前是usr,則取出PC+4*0的內容,即__irq_usr @如果進入中斷前是svc,則取出PC+4*3的內容,即__irq_svc
movs pc, lr @ 當指令的目標寄存器是PC,且指令以S結束,則它會把@ spsr的值恢複給cpsr branch to handler in SVC mode
.endm
.globl __stubs_start
__stubs_start:
/*
* 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)
用“irq, IRQ_MODE, 4”代替宏vector_stub中的“name, mode, correction”,找到了我們中斷處理的入口位置為vector_irq(宏裡面的vector_ ame)。
從上面代碼中的注釋可以看出,根據進入中斷前的工作模式不同,程式下一步將跳轉到_irq_usr 、或__irq_svc等位置。我們先選擇__irq_usr作為下一步跟蹤的目標。
3.3 __irq_usr的實現 archarmkernelentry-armv.S
__irq_usr:
usr_entry @後面有解釋
kuser_cmpxchg_check
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
get_thread_info tsk @擷取當前進程的進程描述符中的成員變數thread_info的地址,並將該地址儲存到寄存器tsk等於r9(在entry-header.S中定義)
#ifdef CONFIG_PREEMPT//如果定義了搶佔,增加搶佔數值
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif
irq_handler @中斷處理,我們最關心的地方,3.4節有實現過程。
#ifdef CONFIG_PREEMPT
ldr r0, [tsk, #TI_PREEMPT]
str r8, [tsk, #TI_PREEMPT]
teq r0, r7
strne r0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on
#endif
mov why, #0
b ret_to_user @中斷處理完成,返回中斷產生的位置,3.7節有實現過程
上面代碼中的usr_entry是一個宏,主要實現了將usr模式下的寄存器、中斷返回地址儲存到堆棧中。
.macro usr_entry
sub sp, sp, #S_FRAME_SIZE @ S_FRAME_SIZE的值在archarmkernelasm-offsets.c
@ 中定義 DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));實際上等於72
stmib sp, {r1 - r12}
ldmia r0, {r1 - r3}
add r0, sp, #S_PC @ here for interlock avoidance
mov r4, #-1 @ "" "" "" ""
str r1, [sp] @ save the "real" r0 copied
@ from the exception stack
@
@ We are now ready to fill in the remaining blanks on the stack:
@
@ r2 - lr_<exception>, already fixed up for correct return/restart
@ r3 - spsr_<exception>
@ r4 - orig_r0 (see pt_regs definition in ptrace.h)
@
@ Also, separately save sp_usr and lr_usr
@
stmia r0, {r2 - r4}
stmdb r0, {sp, lr}^
@
@ Enable the alignment trap while in kernel mode
@
alignment_trap r0
@
@ Clear FP to mark the first stack frame
@
zero_fp
.endm
上面的這段代碼主要在填充結構體pt_regs ,這裡提到的struct pt_regs,在include/asm/ptrace.h中定義。此時sp指向struct pt_regs。
struct pt_regs {
long uregs[18];
};
#define ARM_cpsr uregs[16]
#define ARM_pc uregs[15]
#define ARM_lr uregs[14]
#define ARM_sp uregs[13]
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_r10 uregs[10]
#define ARM_r9 uregs[9]
#define ARM_r8 uregs[8]
#define ARM_r7 uregs[7]
#define ARM_r6 uregs[6]
#define ARM_r5 uregs[5]
#define ARM_r4 uregs[4]
#define ARM_r3 uregs[3]
#define ARM_r2 uregs[2]
#define ARM_r1 uregs[1]
#define ARM_r0 uregs[0]
#define ARM_ORIG_r0 uregs[17]
3.4 irq_handler的實現過程,archarmkernelentry-armv.S
.macro irq_handler
get_irqnr_preamble r5, lr
@在include/asm/arch-s3c2410/entry-macro.s中定義了宏get_irqnr_preamble為空白操作,什麼都不做
1: get_irqnr_and_base r0, r6, r5, lr @判斷中斷號,通過R0返回,3.5節有實現過程
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, 1b
bne asm_do_IRQ @進入中斷處理。
……
.endm
3.5 get_irqnr_and_base中斷號判斷過程,include/asm/arch-s3c2410/entry-macro.s
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
mov ase, #S3C24XX_VA_IRQ
@@ try the interrupt offset register, since it is there
ldr irqstat, [ ase, #INTPND ]
teq irqstat, #0
beq 1002f
ldr irqnr, [ ase, #INTOFFSET ] @通過判斷INTOFFSET寄存器得到中斷位置
mov mp, #1
tst irqstat, mp, lsl irqnr
bne 1001f
@@ the number specified is not a valid irq, so try
@@ and work it out for ourselves
mov irqnr, #0 @@ start here
@@ work out which irq (if any) we got
movs mp, irqstat, lsl#16
addeq irqnr, irqnr, #16
moveq irqstat, irqstat, lsr#16
tst irqstat, #0xff
addeq irqnr, irqnr, #8
moveq irqstat, irqstat, lsr#8
tst irqstat, #0xf
addeq irqnr, irqnr, #4
moveq irqstat, irqstat, lsr#4
tst irqstat, #0x3
addeq irqnr, irqnr, #2
moveq irqstat, irqstat, lsr#2
tst irqstat, #0x1
addeq irqnr, irqnr, #1
@@ we have the value
1001:
adds irqnr, irqnr, #IRQ_EINT0 @加上中斷號的基準數值,得到最終的中斷號,注意:此時沒有考慮子中斷的具體情況,(子中斷的問題後面會有講解)。IRQ_EINT0在include/asm/arch-s3c2410/irqs.h中定義.從這裡可以看出,中斷號的具體值是有平台相關的代碼決定的,和硬體中斷掛起寄存器中的中斷號是不等的。
1002:
@@ exit here, Z flag unset if IRQ
.endm
3.6 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);
struct irq_desc *desc = irq_desc + irq;//根據中斷號找到對應的irq_desc
/*
* 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);// 根據中斷號和desc進入中斷處理
/* AT91 specific workaround */
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);//中斷處理
}
上述asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)使用了asmlinkage標識。那麼這個標識的含義如何理解呢?
該符號定義在kernel/include/linux/linkage.h中,如下所示:
#include <asm/linkage.h>//各個具體處理器在此檔案中定義asmlinkage
#ifdef __cplusplus
#define CPP_ASMLINKAGE extern "C"
#else
#define CPP_ASMLINKAGE
#endif
#ifndef asmlinkage//如果以前沒有定義asmlinkage
#define asmlinkage CPP_ASMLINKAGE
#endif
對於ARM處理器的<asm/linkage.h>,沒有定義asmlinkage,所以沒有意義(不要以為參數是從堆棧傳遞的,對於ARM平台來說還是符合ATPCS程序呼叫標準,通過寄存器傳遞的)。
但對於X86處理器的<asm/linkage.h>中是這樣定義的:
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
表示函數的參數傳遞是通過堆棧完成的。
3.7 描述3.3節中的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]
tst r1, #_TIF_WORK_MASK
bne work_pending
no_work_pending:
/* perform architecture specific actions before user return */
arch_ret_to_user r1, lr
@ slow_restore_user_regs
ldr r1, [sp, #S_PSR] @ get calling cpsr
ldr lr, [sp, #S_PC]! @ get pc
msr spsr_cxsf, r1 @ save in spsr_svc
ldmdb sp, {r0 - lr}^ @ get calling r0 - lr
mov r0, r0
add sp, sp, #S_FRAME_SIZE - S_PC
movs pc, lr @ return & move spsr_svc into cpsr
第三章主要跟蹤了從中斷髮生到調用到對應中斷號的desc->handle_irq(irq, desc)中斷函數的過程。後面的章節還會繼續講解後面的內容。
本文來自: DZ3W.COM 原文網址:http://www.dz3w.com/mcu/othermcu/0078341.html