?Linux 中,當(dāng)外設(shè)觸發(fā)中斷后,大體處理流程如下:
a -- 具體CPU architecture相關(guān)的模塊會進(jìn)行現(xiàn)場保護(hù),然后調(diào)用machine driver對應(yīng)的中斷處理handler;
b -- machine driver對應(yīng)的中斷處理handler中會根據(jù)硬件的信息獲取HW interrupt ID,并且通過irq domain模塊翻譯成IRQ number;
c -- ?調(diào)用該IRQ number 對應(yīng)的high level irq event handler,在這個high level的handler中,會通過和interupt controller交互,進(jìn)行中斷處理的flow control(處理中斷的嵌套、搶占等),當(dāng)然最終會遍歷該中斷描述符的IRQ action list,調(diào)用外設(shè)的specific handler來處理該中斷;
d -- 具體CPU architecture相關(guān)的模塊會進(jìn)行現(xiàn)場恢復(fù);
? ? ?
? ? ? ? 總結(jié)下來,整個過程可以分為三部分:1、硬件處理部分;2、匯編處理部分;3、C 處理部分;
? ? ? ?下面我們來追蹤一下代碼,了解當(dāng)中斷發(fā)生時,Linux 是如何處理的,前面的一些中斷初始化部分就不再這里詳述了,下面開始具體分析:
一、硬件處理部分
? ? ? ??當(dāng)一切準(zhǔn)備好之后,一旦打開處理器的全局中斷就可以處理來自外設(shè)的各種中斷事件了。
? ? ? ? 當(dāng)外設(shè)(SOC內(nèi)部或者外部都可以)檢測到了中斷事件,就會通過interrupt requestion line上的電平或者邊沿(上升沿或者下降沿或者both)通知到該外設(shè)連接到的那個中斷控制器,而中斷控制器就會在多個處理器中選擇一個,并把該中斷通過IRQ(或者FIQ,本文不討論FIQ的情況)分發(fā)給該processor。
? ? ? ? ARM處理器感知到了中斷事件后,會進(jìn)行下面一系列的動作(硬件處理部分):
1、切換處理器模式
? ? ? ?修改 CPSR 寄存器中的 M[4:0],切換處理器模式位?IRQ Mode(這里M[4:0] 所添值為 10010);
2、保護(hù)現(xiàn)場
? ? ??保存發(fā)生中斷時,CPSR值與PC值(為恢復(fù)現(xiàn)場做準(zhǔn)備);這里要注意,此時中斷可能發(fā)生在 usr mode (用戶空間),也可能發(fā)生在 SVC mode(內(nèi)核空間);
3、mask IRQ exception
? ? ??關(guān)閉IRQ中斷,也就是設(shè)定CPSR.I = 1;
4、設(shè)定PC值為IRQ exception vector
? ? ? ?實現(xiàn)向異常向量表的跳轉(zhuǎn),ARM處理器會跳轉(zhuǎn)到IRQ的exception?vector地址,到這硬件所做的工作就結(jié)束了,下面就是軟件行為了。
? ? ???軟件處理部分流程如下:
? ? ? ?可以看到 Vetor_irq 是匯編部分入口點,而Asm_do_irq 是C 部分入口點,下面分析Vetor_irq 向 Asm_do_irq 跳轉(zhuǎn)過程
二、匯編部分
? ? ? ? 前面硬件部分結(jié)束后,跳轉(zhuǎn)到相應(yīng)的異常中斷處理程序處執(zhí)行,對于ARMv7向量表普遍是0xFFFF0018 ,而對于低向量PC=0x00000018 ??
? ? ? ? 假設(shè)在用戶空間時,產(chǎn)生了外部硬件中斷,這個時候的會跳轉(zhuǎn)到異常向量表,向量表(vector table)的代碼如下
【arch/arm/kernel/entry-armv.S】
[cpp]?view plaincopy
__vectors_start:---------------〉在中斷向量表被拷貝后,該地址就是0xffff0000.???? ?ARM(?swi?SYS_ERROR0?)???? ?THUMB(?svc?#0?)???? ?THUMB(?nop?)???? W(b)?vector_und?+?stubs_offset???? W(ldr)?pc,?.LCvswi?+?stubs_offset???? W(b)?vector_pabt?+?stubs_offset???? W(b)?vector_dabt?+?stubs_offset???? W(b)?vector_addrexcptn?+?stubs_offset???? W(b)vector_irq?+?stubs_offset----------〉當(dāng)外部中斷產(chǎn)生時,pc直接指向這個地址。???? W(b)?vector_fiq?+?stubs_offset???? .globl?__vectors_end????
1、IRQ mode中的處理 (vector table --- > vector_irq )
? ? ? IRQ mode的處理都在vector_irq中,vector_stub是一個宏,定義如下:
[cpp]?view plaincopy
? ? ? ? ? ? ? ? ? ? ? ? ???? ????.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,同時將中斷模式下的spsr_irq(irq私有的)賦值給cpsr(該寄存器所有模式共享)???? ENDPROC(vector_\name)??
從這可以看出 vector_stub 的使用方法:
? ? ???vector_stub, name, mode, correction=0
上面這段究竟做了些什么呢?
(1)我們期望在棧上保存發(fā)生中斷時候的硬件現(xiàn)場(HW context),這里就包括ARM的core register。上一章我們已經(jīng)了解到,當(dāng)發(fā)生IRQ中斷的時候,lr中保存了發(fā)生中斷的PC+4,如果減去4的話,得到的就是發(fā)生中斷那一點的PC值。
(2)當(dāng)前是IRQ mode,SP_irq在初始化的時候已經(jīng)設(shè)定(12個字節(jié))。在irq mode的stack上,依次保存了發(fā)生中斷那一點的r0值、PC值以及CPSR值(具體操作是通過spsr進(jìn)行的,其實硬件已經(jīng)幫我們保存了CPSR到SPSR中了)。為何要保存r0值?因為隨后的代碼要使用r0寄存器,因此我們要把r0放到棧上,只有這樣才能完完全全恢復(fù)硬件現(xiàn)場。
(3)可憐的IRQ mode稍縱即逝,這段代碼就是準(zhǔn)備將ARM推送到SVC mode。如何準(zhǔn)備?其實就是修改SPSR的值,SPSR不是CPSR,不會引起processor mode的切換(畢竟這一步只是準(zhǔn)備而已)。
(4)很多異常處理的代碼返回的時候都是使用了stack相關(guān)的操作,這里沒有。“movs pc, lr ”指令除了字面上意思(把lr的值付給pc),還有一個
隱含的操作(movs中‘s’的含義):把SPSR copy到CPSR,從而實現(xiàn)了模式的切換。
這里有個問題:中斷為什么必須進(jìn)入svc模式?
一個最重要原因是:如果一個中斷模式(例如從usr進(jìn)入irq模式,在irq模式中)中重新允許了中斷,并且在這個中斷例程中使用了BL指令調(diào)用子程序,BL指令會自動將子程序返回地址保存到當(dāng)前模式的sp(即r14_irq)中,這個地址隨后會被在當(dāng)前模式下產(chǎn)生的中斷所破壞,因為產(chǎn)生中斷時CPU會將當(dāng)前模式的PC保存到r14_irq,這樣就把剛剛保存的子程序返回地址沖掉。為了避免這種情況,中斷例程應(yīng)該切換到SVC或者系統(tǒng)模式,這樣的話,BL指令可以使用r14_svc來保存子程序的返回地址。
2、vector table --- > vector_irq ?---> vector _stub
對于IRQ Mode 則?vector_stub, irq, IRQ_MODE, 4? ?
[cpp]?view plaincopy
__stubs_start:???? ? ? ???? 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????
這里根據(jù)被中斷時,處理器模式的不同,分別跳轉(zhuǎn)到__irq_usr和__irq_svc兩個分支。
3、vector table --- > vector_irq ?---> vector _stub --->?__irq_usr
在這里我們以__irq_usr為例來說明:
[cpp]?view plaincopy
__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)??
4、vector table --- > vector_irq ?---> vector _stub --->?__irq_usr ---> usr_entry
usr_entry展開如下:
[cpp]?view plaincopy
.macro??usr_entry???? UNWIND(.fnstart?)???? UNWIND(.cantunwind??)???@?don't?unwind?the?user?space???? sub?sp,?sp,?#S_FRAME_SIZE????? ARM(????stmib???sp,?{r1?-?r12}??)???????? THUMB(??stmia???sp,?{r0?-?r12}??)???? ???? ldmia???r0,?{r3?-?r5}???????????? add?r0,?sp,?#S_PC???????@?here?for?interlock?avoidance?#S_PC=60???? mov?r6,?#-1?????????@??""??""?????""????????""???? ???? str?r3,?[sp]????????@?save?the?"real"?r0?copied???? ????????????????@?from?the?exception?stack???? ???? @???? @?We?are?now?ready?to?fill?in?the?remaining?blanks?on?the?stack:???? @???? @??r4?-?lr_<exception>,?already?fixed?up?for?correct?return/restart???? @??r5?-?spsr_<exception>???? @??r6?-?orig_r0?(see?pt_regs?definition?in?ptrace.h)???? @???? @?Also,?separately?save?sp_usr?and?lr_usr???? @???? stmia???r0,?{r4?-?r6}???? ARM(????stmdb???r0,?{sp,?lr}^???????????)?? THUMB(??store_user_sp_lr?r0,?r1,?S_SP?-?S_PC????)???? ???? @???? @?Enable?the?alignment?trap?while?in?kernel?mode???? @???? alignment_trap?r0???? ???? @???? @?Clear?FP?to?mark?the?first?stack?frame???? @???? zero_fp???? ???? ifdef?CONFIG_IRQSOFF_TRACER???? bl??trace_hardirqs_off???? endif???? .endm????
? ? ? 代碼執(zhí)行到這里的時候,ARM處理已經(jīng)切換到了【SVC mode】。一旦進(jìn)入SVC mode,ARM處理器看到的寄存器已經(jīng)發(fā)生變化,這里的sp已經(jīng)變成了sp_svc了。因此,后續(xù)的壓棧操作都是壓入了發(fā)生中斷那一刻的進(jìn)程的(或者內(nèi)核線程)內(nèi)核棧(svc mode棧)。
? ? ?此時的管理模式的內(nèi)核棧分布如下:
? ? ? ?需要說明的是:上圖中的lr_irq即為用戶模式下被中斷指令的下一條指令,spsr_irq即為用戶模式下被中斷時的cpsr寄存器。
5、vector table --- > vector_irq ?---> vector _stub --->?__irq_usr ---> (usr_entry ?--->?irq_handler )
? ? ? ?usr_entry 結(jié)束后,會執(zhí)行 irq_handler
irq_handler的實現(xiàn)過程?arch\arm\kernel\entry-armv.S
[cpp]?view plaincopy
.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節(jié)有實現(xiàn)過程??? ??????????????movne?r1,?sp?? ??????????????@?? ??????????????@?routine?called?with?r0?=?irq?number,?r1?=?struct?pt_regs?*?? ??????????????@?? ??????????????adrne?lr,?1b?? ??????????????bne?asm_do_IRQ?@進(jìn)入中斷處理。??? ???????……??? ??????????????.endm??
可以看到?bne asm_do_IRQ @進(jìn)入中斷處理 ?淚奔~~o(>_<)o ~~ 終于到了asm_do_IRQ
可以看到整個跳轉(zhuǎn)過程:
vector table --- >?vector_irq??--->vector_stub, irq, IRQ_MODE, 4?--->?__irq_usr?---> usr_entry ?--->?irq_handler?--->asm_do_IRQ?
三、C語言處理部分
1、?asm_do_IRQ
? ? ? ??asm_do_IRQ實現(xiàn)過程,arch/arm/kernel/irq.c?
[cpp]?view plaincopy
?*?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);?? }??
[cpp]?view plaincopy
<span?style="font-family:?Arial,?Helvetica,?sans-serif;">其中關(guān)鍵一步handle_IRQ(irq,?regs)</span>??
2、handle_IRQ(irq, regs)
[cpp]?view plaincopy
? ? ? ? ? ?? void?handle_IRQ(unsigned?int?irq,?struct?pt_regs?*regs)?? {?? ????struct?pt_regs?*old_regs?=?set_irq_regs(regs);?? ?? ????irq_enter();?? ?? ????? ? ? ?? ????if?(unlikely(irq?>=?nr_irqs))?{?? ????????if?(printk_ratelimit())?? ????????????printk(KERN_WARNING?"Bad?IRQ%u\n",?irq);?? ????????ack_bad_irq(irq);?? ????}?else?{?? ????????generic_handle_irq(irq);?? ????}?? ?? ????irq_exit();?? ????set_irq_regs(old_regs);?? }??
主要調(diào)用generic_handle_irq(irq)
3、generic_handle_irq(irq)
[cpp]?view plaincopy
include/linux/irq.h?? static?inline?void?generic_handle_irq_desc(unsigned?int?irq,?struct?irq_desc?*desc)?? {?? #ifdef?CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ?? ????????desc->handle_irq(irq,?desc);?? #else?? ????????if?(likely(desc->handle_irq))?? ???????????????desc->handle_irq(irq,?desc);?? ????????else?? ???????????????__do_IRQ(irq);?? #endif?? }?? ?? static?inline?void?generic_handle_irq(unsigned?int?irq)?? {?? ????????generic_handle_irq_desc(irq,?irq_to_desc(irq));?? }??
generic_handle_irq調(diào)用前面定義的generic_handle_irq_desc
4、generic_handle_irq_des
[cpp]?view plaincopy
static?inline?void?generic_handle_irq_desc(unsigned?int?irq,?struct?irq_desc?*desc)?? ?? desc->handle_irq(irq,?desc);??
而 generic_handle_irq_desc也沒做什么,調(diào)用desc——>handle_irq</strong>,這個函數(shù)就是irq_desc中的成員
5、irq_desc結(jié)構(gòu)體 ??? ?
? ? ? ?Linux內(nèi)核將所有中斷統(tǒng)一編號,使用irq_desc結(jié)構(gòu)來描述中斷:每個數(shù)組項對應(yīng)一個中斷(也可能是一組中斷,它們使用共同的中斷號),里面記錄了中斷的名稱,中斷狀態(tài),中斷標(biāo)記,并提供硬件訪問函數(shù)(清除,屏蔽,使能中斷),提供了這個中斷的處理函數(shù)的入口,通過它可以調(diào)用用戶注冊的中斷處理函數(shù)
[cpp]?view plaincopy
include/linux/irq.h?? {?? ????.........?? ?? ????irq_flow_handler_t?handle_irq;????? ????struct?irq_chip?*chip;????????? ?????? ????..........?? ?? ????struct?irqaction?*action;??? ????unsigned?int?status;??? ?? ????...........?? ?? ????const?char?*name;??????? }?____cacheline_internodealigned_in_smp;??
? ? Handle_irq是這個或者這組中斷的處理函數(shù)入口
? ? 這里調(diào)用desc->handle_irq分為倆種情況,一是單獨的中斷號的,一是共享中斷號的,倆者的區(qū)別在于后者需要先判斷是共享中斷的中的哪一個然后再真正的去調(diào)用handle_irq,所以我這里分析一下單獨中斷號的處理流程,共享中斷也是一樣可以分析。
? ? ? ? 我們分析一個具體的,以外部中斷為例
[cpp]?view plaincopy
for?(irqno?=?IRQ_EINT0;?irqno?<=?IRQ_EINT3;?irqno++)?{?? ???????????????irqdbf("registering?irq?%d?(ext?int)\n",?irqno);?? ???????????????set_irq_chip(irqno,?&s3c_irq_eint0t4);?? ???????????????set_irq_handler(irqno,?handle_edge_irq);?? ???????????????set_irq_flags(irqno,?IRQF_VALID);?? ????????}??
? ? ? ?上面代碼我們看到,set_irq_handler的值是handler_edge_irq ,這里是處理邊沿觸發(fā)的中斷函數(shù),當(dāng)然還有電平觸發(fā)方式的中斷(handler_level_irq),繼續(xù)看代碼
[cpp]?view plaincopy
kernel/irq/chip.c?? void?? handle_edge_irq(unsigned?int?irq,?struct?irq_desc?*desc)?? {?? ????????spin_lock(&desc->lock);?????????上鎖?? ??? ????????desc->status?&=?~(IRQ_REPLAY?|?IRQ_WAITING);??? ??? ????????? ? ? ? ?? ????????if?(unlikely((desc->status?&?(IRQ_INPROGRESS?|?IRQ_DISABLED))?||???判斷?? ???????????????????!desc->action))?{?? ???????????????desc->status?|=?(IRQ_PENDING?|?IRQ_MASKED);?? ???????????????mask_ack_irq(desc,?irq);???屏蔽并清除中斷?? ???????????????goto?out_unlock;?? ????????}?? ????????kstat_incr_irqs_this_cpu(irq,?desc);?中斷統(tǒng)計計數(shù)?? ??? ?????????? ????????if?(desc->chip->ack)??????應(yīng)答中斷?? ???????????????desc->chip->ack(irq);?? ??? ?????????? ????????desc->status?|=?IRQ_INPROGRESS;???標(biāo)記中斷狀態(tài)?? ??? ????????do?{?? ???????????????struct?irqaction?*action?=?desc->action;?? ???????????????irqreturn_t?action_ret;?? ??? ???????????????if?(unlikely(!action))?{?? ???????????????????????desc->chip->mask(irq);?? ???????????????????????goto?out_unlock;?? ???????????????}?? ??? ???????????????? ? ? ? ?? ???????????????if?(unlikely((desc->status?&?? ??????????????????????????????(IRQ_PENDING?|?IRQ_MASKED?|?IRQ_DISABLED))?==?? ?????????????????????????????(IRQ_PENDING?|?IRQ_MASKED)))?{?? ???????????????????????desc->chip->unmask(irq);?? ???????????????????????desc->status?&=?~IRQ_MASKED;?? ???????????????}?? ??? ???????????????desc->status?&=?~IRQ_PENDING;?? ???????????????spin_unlock(&desc->lock);?? ???????????????action_ret?=?handle_IRQ_event(irq,?action);??處理中斷,最重要的函數(shù),注意參數(shù),action這個參數(shù)將聯(lián)系到我們的用戶中斷處理函數(shù)?? ???????????????if?(!noirqdebug)?? ???????????????????????note_interrupt(irq,?desc,?action_ret);?? ???????????????spin_lock(&desc->lock);?? ??? ????????}?while?((desc->status?&?(IRQ_PENDING?|?IRQ_DISABLED))?==?IRQ_PENDING);?? ??? ????????desc->status?&=?~IRQ_INPROGRESS;?? out_unlock:?? ????????spin_unlock(&desc->lock);?? }??
進(jìn)行追蹤handle_IRQ_event()
[cpp]?view plaincopy
kernel/irq/handle.c?? ???????????????trace_irq_handler_entry(irq,?action);?? ???????????????ret?=?action->handler(irq,?action->dev_id);?? ???????????????trace_irq_handler_exit(irq,?action,?ret);??
? ? ? ?調(diào)用action中的handler,我們注冊中斷的時候注意任務(wù)就是構(gòu)造一個irqaction結(jié)構(gòu)并添加到irq_desc中的irqaction鏈表中的指針action下面。現(xiàn)在處理中斷中我們就看到了調(diào)用了我們自己的中斷處理函數(shù)來處理中斷了。至此中斷處理流程就結(jié)束了
總結(jié)軟件部分:
asm_do_IRQ --> handle_IRQ(irq, regs)--> generic_handle_irq(irq) --> generic_handle_irq_desc --> (desc—>handle_irq ) -->handle_level_irq --> handle_irq_event--->?action->handler(irq, action->dev_id)
總結(jié)
以上是生活随笔為你收集整理的Exynos4412 中断处理流程详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。