Linux核心中斷機制(三):中斷處理上
核心中斷處理過程
S3C2410和Linux2.6.26核心為例講解處理過程
1.中斷向量表arch\arm\kernel\entry-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。
2.向量表中找到入口位置(同一個檔案中)
.globl __stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4 @IRQ_MODE 在
include\asm\ptrace.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_\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)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
andlr, 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
ldrlr, [pc, lr, lsl #2] //根據user還是svc模式而不同,決定進入_irq_user還是_irq_svc
movs pc, lr @ 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_\name)。
從上面程式碼中的註釋可以看出,根據進入中斷前的工作模式不同,程式下一步將
跳轉到_irq_usr 、或__irq_svc 等位置。我們先選擇__irq_usr 作為下一步跟蹤
的目標。
3.__irq_usr的實現(同樣的位置)
__irq_usr:
usr_entry
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
get_thread_info tsk //獲取程序中描述符threa_info的地址,存到暫存器裡面
#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 //重要的處理過程
#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
bret_to_user //中斷處理完成,返回中斷產生位置
.ltorg
.align 5
①usr_entry
是一個巨集:將usr模式下的暫存器和中斷返回地址儲存到堆疊(保護現場)
②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
4.irq_handler的實現(同一個地址)
.macro irq_handler
get_irqnr_preamble r5, lr //include/asm/arch-s3c2410/entry-macro.s 中定義,為空操作
1: get_irqnr_and_base r0, r6, r5, lr //判斷中斷號,R0返回
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, 1b
bneasm_do_IRQ //進入中斷處理
①判斷中斷號
include/asm/arch-s3c2410/entry-macro.s
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
mov \base, #S3C24XX_VA_IRQ
@@ try the interrupt offset register, since it is there
ldr \irqstat, [ \base, #INTPND ]
teq \irqstat, #0
beq 1002f
ldr \irqnr, [ \base, #INTOFFSET ] @通過判斷 INTOFFSET 寄
存器得到中斷位置
mov \tmp, #1
tst \irqstat, \tmp, 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 \tmp, \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
5.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找到具體的中斷號
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS)
desc = &bad_irq_desc;trap_init
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標誌含義:
#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)))
表示函式的引數傳遞是通過堆疊完成的。