arm linux 下中斷流程簡要分析中斷處理流程
首先在分析源碼之前,讓我們了解一些原理性的東西,我們都知道在處理中斷要保存當(dāng)前現(xiàn)場狀態(tài),然后才能處理中斷,處理完之后還要把現(xiàn)場狀態(tài)恢復(fù)過來才能返回到被中斷的地方繼續(xù)執(zhí)行,這里要說明的是在指令跳轉(zhuǎn)到中斷向量的地方開始執(zhí)行之前,CPU幫我們做了哪些事情:
R14_irq =要執(zhí)行的下條指令地址+ 4//這里的下條指令是相對于被中斷指令的下條。即返回地址
SPSR_irq = CPSR//保存的現(xiàn)場狀態(tài),r0到r12要由我們軟件來保存(如果需要的話)。
CPSR[4:0] = 0b10010//進(jìn)入中斷模式
CPSR[5] = 0//在ARM模式下執(zhí)行(不是Thumb下)
CPSR[7] = 1//關(guān)掉IRQ中斷,F(xiàn)IQ還是開著
PC = 0Xffff0018/0x00000018//根據(jù)異常向量表的位置,跳轉(zhuǎn)到特定的中斷向量處去執(zhí)行。
更詳細(xì)的關(guān)于異常處理的細(xì)節(jié)可參考<>
接下來我們在來分析watchdog產(chǎn)生中斷后的處理流程:
當(dāng)watchdog超時時將會產(chǎn)生中斷,中斷號就是IRQ_WDT,當(dāng)產(chǎn)生中斷時,系統(tǒng)將從跳轉(zhuǎn)表中的中斷位置開始運行,對于我們這篇文章來說:是從0xffff0000 + 24處開始運行。 這個地址的指令是:
bvector_irq + stubs_offset
即直接跳轉(zhuǎn)到vector_irq處去運行。這些都在中斷初始化的時候分析過了。
我們來看vector_irq,它是通過宏vector_stub來定義的:
arch/arm/kernel/entry-armv.S:
/*
* Interrupt dispatcher
*/
vector_stubirq, IRQ_MODE, 4/*這是個宏定義*/
/*下面這些都是不同模式下的irq處理函數(shù)*/
.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
arch/arm/kernel/entry-armv.S:
.macrovector_stub, name, mode, correction=0
.align5
vector_/name:
.if /correction
sublr, lr, #/correction
.endif
@
@ Save r0, lr_
@ (parent CPSR)
@
stmiasp, {r0, lr}@ save r0, lr
mrslr, spsr
strlr, [sp, #8]@ save spsr
@
@ Prepare for SVC32 mode.IRQs remain disabled.
@
mrsr0, cpsr
eorr0, r0, #(/mode ^ SVC_MODE)
msrspsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
andlr, lr, #0x0f
movr0, sp
ldrlr, [pc, lr, lsl #2]
movspc, lr@ branch to handler in SVC mode
.endm
這樣展開后vector_irq如下所示:
arch/arm/kernel/entry-armv.S:
vector_irq:
.if 4
@ lr保存的是被打斷指令處地址+8的值,(看上面的分析,由PC得到), 這里-4則就是中斷
@處理完后的返回地址,在中斷處理完后該值會賦給PC
sublr, lr, #4
.endif
@
@ Save r0, lr_
@ (parent CPSR)
@ r0后面會用到所以要保存。
stmiasp, {r0, lr}@ save r0, lr,保存r0,lr到棧上,這里的棧是中斷模式下的。
mrslr, spsr@獲取spsr的值,該值保存了被中斷處執(zhí)行環(huán)境的狀態(tài)(參考上面的分析)
strlr, [sp, #8]@ save spsr, 保存到棧上
@
@ Prepare for SVC32 mode.IRQs remain disabled.
@
mrsr0, cpsr
eorr0, r0, #( IRQ_MODE ^ SVC_MODE)
msrspsr_cxsf, r0@把spsr設(shè)置成管理模式
@
@ the branch table must immediately follow this code
@
andlr, lr, #0x0f
movr0, sp
ldrlr, [pc, lr, lsl #2]
movspc, lr@ branch to handler in SVC mode @ pc = lr, cpsr = spsr
.endm
movs的目的對象如果是pc的話,則還會把spsr賦值給cpsr,上面我們看到spsr被設(shè)成管理模式,因此這條語句過后的代碼也就跑在了管理模式下。
此時的棧情況如下:
S_FRAME_SIZE,S_PC在arch/arm/kernel/Asm-offsets.c:中定義
DEFINE(S_FRAME_SIZE,sizeof(struct pt_regs));
DEFINE(S_PC,offsetof(struct pt_regs, ARM_pc));
include/asm-arm/Ptrace.h:
struct pt_regs {
long uregs[18];
};
#define ARM_pcuregs[15]
,pt_regs中對應(yīng)的就是上面棧上的18個寄存器,ARM_pc是pc寄存器存放在這個數(shù)組中的偏移。
接著看get_thread_info, 它也是個宏,用來獲取當(dāng)前線程的地址。在我的一篇linux啟動代碼分析里曾寫過線程的定義方式:
include/linux/Sched.h:
union thread_union {
struct thread_info thread_info;/*線程屬性*/
unsigned long stack[THREAD_SIZE/sizeof(long)];/*棧*/
};
由它定義的線程是8K字節(jié)對齊的, 并且在這8K的最低地址處存放的就是thread_info對象,即該棧擁有者線程的對象,而get_thread_info就是通過把sp低13位清0(8K邊界)來獲取當(dāng)前thread_info對象的地址。
arch/arm/kernel/entry-armv.S:
.macroget_thread_info, rd
mov/rd, sp, lsr #13
mov/rd, /rd, lsl #13
.endm
調(diào)用該宏后寄存器tsk里存放的就是當(dāng)前線程的地址了,tsk是哪個寄存器呢,我們在看:
arch/arm/kernel/entry-header.S:
tsk.reqr9@ current thread_info
,tsk只是r9的別名而已, 因此這時r9里保存的就是當(dāng)前線程的地址。
我們接著看irq_handler:
arch/arm/kernel/entry-armv.S:
.macroirq_handler
1:get_irqnr_and_base r0, r6, r5, lr@平臺相關(guān),獲取中斷號
movner1, sp@如果r0(中斷號)不等于0,則r1指向sp所在地址,即pt_regs對象地址(看上圖)
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrnelr, 1b@如果r0(中斷號)不等于0,lr(返回地址)等于標(biāo)號1處,即
@ get_irqnr_and_base r0, r6, r5, lr的那行,即循環(huán)處理所有的中斷。
bneasm_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
movner0, sp
adrnelr, 1b
bnedo_IPI
#ifdef CONFIG_LOCAL_TIMERS
test_for_ltirq r0, r6, r5, lr
movner0, sp
adrnelr, 1b
bnedo_local_timer
#endif
#endif
.endm
get_irqnr_and_base是平臺相關(guān)的,這里就不列出來了,對于s3c2410,代碼在include/asm-arm/s3c2410/entry-macro.S里,該宏處理完后,r0 =中斷號,接下來r1賦值為sp地址(pt_regs對象地址), 最后調(diào)用c函數(shù)asm_do_IRQ, r0, r1作為參數(shù)被傳遞進(jìn)去。asm_do_IRQ()處理完后將返回到lr指向的地址處即上面匯編部分標(biāo)號為1的地址處繼續(xù)執(zhí)行。
我們把__irq_usr的匯編部分分析完后再來分析asm_do_IRQ()等c函數(shù)。
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@關(guān)中斷,
ldrr1, [tsk, #TI_FLAGS] @獲取thread_info中flags域的值
tstr1, #_TIF_WORK_MASK@判斷task是否被阻塞
bnework_pending@根據(jù)需要進(jìn)行進(jìn)程的切換。
no_work_pending:
@ slow_restore_user_regs
ldrr1, [sp, #S_PSR]@ get calling cpsr獲取被中斷代碼處的狀態(tài)(cpsp)
ldrlr, [sp, #S_PC]!@ get pc獲取返回地址(被中斷代碼的下條代碼處的地址)
msrspsr_cxsf, r1@ save in spsr_svc, spsr里保存好被中斷代碼處的狀態(tài)(cpsp)
ldmdbsp, {r0 - lr}^@ get calling r1 – lr從棧上獲取用戶態(tài)下的r0到lr的值
movr0, r0
addsp, sp, #S_FRAME_SIZE - S_PC@棧地址恢復(fù),避免多個中斷后溢出
movspc, lr@ return & move spsr_svc into cpsr, 返回被中斷代碼處繼續(xù)執(zhí)行,并把spsr賦給cpsp,即恢復(fù)被中斷處的現(xiàn)場狀態(tài)。這樣CPU又可以從被中斷的地方繼續(xù)執(zhí)行了,而且這個時候所有的寄存器值(r0到r12),包括狀態(tài)寄存器值(cpsr)都是源碼被中斷時的值。
我們順便看下work_pending
arch/arm/kernel/entry-common.S:
work_pending:
tstr1, #_TIF_NEED_RESCHED@判斷是否需要調(diào)度進(jìn)程
bnework_resched@進(jìn)程調(diào)度
tstr1, #_TIF_NOTIFY_RESUME | _TIF_SIGPENDING
beqno_work_pending@無需調(diào)度,返回
movr0, sp@ regs
movr2, why@ syscall
bldo_notify_resume
bret_slow_syscall@ Check work again
由該匯編可知,如果在用戶模式下產(chǎn)生中斷的話,在返回的時候,會根據(jù)需要進(jìn)行進(jìn)程調(diào)度,而從代碼可知,如果中斷發(fā)生在管理等內(nèi)核模式下的話是不會進(jìn)行進(jìn)程調(diào)度的。
Ok,中斷的流程大體就是這樣的,下面我們就開始分析c函數(shù)里的中斷流程。
先來看asm_do_IRQ
arch/arm/kernel/Irq.c:
/*
* do_IRQ handles all hardware IRQs.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)/*參數(shù)檢查*/
desc = &bad_irq_desc;
irq_enter();
desc_handle_irq(irq, desc, regs);/*中斷處理*/
/* AT91 specific workaround */
irq_finish(irq);
irq_exit();
}
該函數(shù)的調(diào)用desc_handle_irq()來繼續(xù)處理中斷。
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);
}
調(diào)用中斷描述符的handler_irq函數(shù)來處理該中斷,對于IRQ_WDT就是do_edge_IRQ(前面分析過)。
include/asm-arm/mach/Irq.h:
#define do_edge_IRQhandle_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 were currently running this IRQ, or its disabled,
* we shouldnt process the IRQ. Mark it pending, handle
* the necessary masking and go out
*/
/*
*如果該中斷正在處理或者該中斷被disable掉了的話,就不處理該中斷,并清掉pending
*寄存器里的相應(yīng)位
*/
if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
!desc->action)) {
desc->status |= (IRQ_PENDING | IRQ_MASKED);
mask_ack_irq(desc, irq);/*mask該中斷,清pending標(biāo)志位*/
goto out_unlock;
}
kstat_cpu(cpu).irqs[irq]++;/*統(tǒng)計中斷數(shù)量*/
/* Start handling the irq */
/*開始處理中斷,先清掉pending標(biāo)志位*/
desc->chip->ack(irq);
/* Mark the IRQ currently in progress.*/
desc->status |= IRQ_INPROGRESS;/*標(biāo)上正在處理的標(biāo)記*/
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狀態(tài),則說明又有中斷產(chǎn)生過,則繼續(xù)執(zhí)行*/
} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
desc->status &= ~IRQ_INPROGRESS;
out_unlock:
spin_unlock(&desc->lock);
}
該函數(shù)的大體功能都在函數(shù)體內(nèi)解釋出來了,這里我們對調(diào)用的每個函數(shù)在進(jìn)行分析。
先看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, 該函數(shù)沒定義*/
desc->chip->mask_ack(irq);
else {
desc->chip->mask(irq); /*對于IRQ_WDT,該函數(shù)就是s3c_irq_mask*/
desc->chip->ack(irq);/*對于IRQ_WDT,該函數(shù)就是s3c_irq_ack*/
}
}
可以看到它調(diào)用具體平臺相關(guān)的mask函數(shù)來處理該中斷。
我們來看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掉對應(yīng)的中斷號*/
__raw_writel(mask, S3C2410_INTMSK);/*寫MASK寄存器*/
}
改函數(shù)僅僅是把MASK寄存器中對應(yīng)的中斷mask掉,即不再響應(yīng)該中斷
arch/arm/mach-s3c2410/Irq.c:
static inline void
s3c_irq_ack(unsigned int irqno)
{
unsigned long bitval = 1UL << (irqno - IRQ_EINT0);
/*清除pending寄存器的相應(yīng)位*/
__raw_writel(bitval, S3C2410_SRCPND);
__raw_writel(bitval, S3C2410_INTPND);
}
由上面這兩個函數(shù)可以看出來mask_ack_irq的作用是先mask掉該中斷,并清除pending位,中斷被mask掉后系統(tǒng)就不再響應(yīng)了, 而pending位被清掉說明系統(tǒng)中該中斷沒有觸發(fā)。一般在中斷處理完后都要清pending位, 要不然系統(tǒng)會認(rèn)為該中斷又被觸發(fā)了。
handle_edge_irq()里調(diào)用的unmask函數(shù),其實就是打開相應(yīng)的中斷,讓系統(tǒng)響應(yīng)這個中斷,代碼就不列出來了。
接下來中斷看handle_IRQ_event(),它才是真正的中斷處理函數(shù)。
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判斷:當(dāng)執(zhí)行action操作時是否可以打開中斷*/
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();/*打開中斷*/
do {
/*
*中斷handler,也就是我們通過request_irq注冊的中斷函數(shù),對于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;
}
該函數(shù)主要就是調(diào)用了action的handler函數(shù),也就是我們用request_irq注冊的中斷例程。這里需要注意的是:如果我們注冊中斷的時候指明可以共享的話,則必須在我們的中斷例程里判斷當(dāng)前產(chǎn)生的中斷是否就是我們自己的中斷,這可以通過傳進(jìn)來的參數(shù)來判斷(該參數(shù)就是我們注冊時提供的)。
OK,到這里整個中斷的流程就大致分析完了。
評論