1. ?中斷流控層簡(jiǎn)介
早期的內(nèi)核版本中,幾乎所有的中斷都是由__do_IRQ函數(shù)進(jìn)行處理,但是,因?yàn)楦鞣N中斷請(qǐng)求的電氣特性會(huì)有所不同,又或者中斷控制器的特性也不同,這會(huì)導(dǎo)致以下這些處理也會(huì)有所不同:
何時(shí)對(duì)中斷控制器發(fā)出ack回應(yīng);
mask_irq和unmask_irq的處理;
中斷控制器是否需要eoi回應(yīng)?
何時(shí)打開(kāi)cpu的本地irq中斷?以便允許irq的嵌套;
中斷數(shù)據(jù)結(jié)構(gòu)的同步和保護(hù);
為此,通用中斷子系統(tǒng)把幾種常用的流控類(lèi)型進(jìn)行了抽象,并為它們實(shí)現(xiàn)了相應(yīng)的標(biāo)準(zhǔn)函數(shù),我們只要選擇相應(yīng)的函數(shù),賦值給irq所對(duì)應(yīng)的irq_desc結(jié)構(gòu)的handle_irq字段中即可。這些標(biāo)準(zhǔn)的回調(diào)函數(shù)都是irq_flow_handler_t類(lèi)型:
[cpp]?view plain?copy
typedef?void?(*irq_flow_handler_t)(unsigned?int?irq,??
struct?irq_desc?*desc);??
目前的通用中斷子系統(tǒng)實(shí)現(xiàn)了以下這些標(biāo)準(zhǔn)流控回調(diào)函數(shù),這些函數(shù)都定義在:kernel/irq/chip.c中,
handle_simple_irq ?用于簡(jiǎn)易流控處理;
handle_level_irq ?用于電平觸發(fā)中斷的流控處理;
handle_edge_irq ?用于邊沿觸發(fā)中斷的流控處理;
handle_fasteoi_irq ?用于需要響應(yīng)eoi的中斷控制器;
handle_percpu_irq ?用于只在單一cpu響應(yīng)的中斷;
handle_nested_irq ?用于處理使用線程的嵌套中斷;
驅(qū)動(dòng)程序和板級(jí)代碼可以通過(guò)以下幾個(gè)API設(shè)置irq的流控函數(shù):
irq_set_handler();
irq_set_chip_and_handler();
irq_set_chip_and_handler_name();
以下這個(gè)序列圖展示了整個(gè)通用中斷子系統(tǒng)的中斷響應(yīng)過(guò)程,flow_handle一欄就是中斷流控層的生命周期:
圖1.1 ?通用中斷子系統(tǒng)的中斷響應(yīng)過(guò)程
2. ?handle_simple_irq
該函數(shù)沒(méi)有實(shí)現(xiàn)任何實(shí)質(zhì)性的流控操作,在把irq_desc結(jié)構(gòu)鎖住后,直接調(diào)用handle_irq_event處理irq_desc中的action鏈表,它通常用于多路復(fù)用(類(lèi)似于中斷控制器級(jí)聯(lián))中的子中斷,由父中斷的流控回調(diào)中調(diào)用?;蛘哂糜跓o(wú)需進(jìn)行硬件控制的中斷中。以下是它的經(jīng)過(guò)簡(jiǎn)化的代碼:
[cpp]?view plain?copy
void??
handle_simple_irq(unsigned?int?irq,?struct?irq_desc?*desc)??
{??
raw_spin_lock(&desc->lock);??
......??
handle_irq_event(desc);??
out_unlock:??
raw_spin_unlock(&desc->lock);??
}??
3. ?handle_level_irq
該函數(shù)用于處理電平中斷的流控操作。電平中斷的特點(diǎn)是,只要設(shè)備的中斷請(qǐng)求引腳(中斷線)保持在預(yù)設(shè)的觸發(fā)電平,中斷就會(huì)一直被請(qǐng)求,所以,為了避免同一中斷被重復(fù)響應(yīng),必須在處理中斷前先把mask irq,然后ack irq,以便復(fù)位設(shè)備的中斷請(qǐng)求引腳,響應(yīng)完成后再u(mài)nmask irq。實(shí)際的情況稍稍復(fù)雜一點(diǎn),在mask和ack之后,還要判斷IRQ_INPROGRESS標(biāo)志位,如果該標(biāo)志已經(jīng)置位,則直接退出,不再做實(shí)質(zhì)性的處理,IRQ_INPROGRESS標(biāo)志在handle_irq_event的開(kāi)始設(shè)置,在handle_irq_event結(jié)束時(shí)清除,如果監(jiān)測(cè)到IRQ_INPROGRESS被置位,表明該irq正在被另一個(gè)CPU處理中,所以直接退出,對(duì)電平中斷來(lái)說(shuō)是正確的處理方法。但是我覺(jué)得在ARM系統(tǒng)中,這種情況根本就不會(huì)發(fā)生,因?yàn)樵跊](méi)有進(jìn)入handle_level_irq之前,中斷控制器沒(méi)有收到ack通知,它不會(huì)向第二個(gè)CPU再次發(fā)出中斷請(qǐng)求,而當(dāng)程序進(jìn)入handle_level_irq之后,第一個(gè)動(dòng)作就是mask irq,然后ack irq(通常是聯(lián)合起來(lái)的:mask_ack_irq),這時(shí)候就算設(shè)備再次發(fā)出中斷請(qǐng)求,也是在handle_irq_event結(jié)束,unmask irq之后,這時(shí)IRQ_INPROGRESS標(biāo)志已經(jīng)被清除。我不知道其他像X86之類(lèi)的體系是否有不同的行為,有知道的朋友請(qǐng)告知我一下。以下是handle_level_irq經(jīng)過(guò)簡(jiǎn)化之后的代碼:
[cpp]?view plain?copy
void??
handle_level_irq(unsigned?int?irq,?struct?irq_desc?*desc)??
{??
raw_spin_lock(&desc->lock);??
mask_ack_irq(desc);??
if?(unlikely(irqd_irq_inprogress(&desc->irq_data)))??
goto?out_unlock;??
......??
if?(unlikely(!desc->action?||?irqd_irq_disabled(&desc->irq_data)))??
goto?out_unlock;??
handle_irq_event(desc);??
if?(!irqd_irq_disabled(&desc->irq_data)?&&?!(desc->istate?&?IRQS_ONESHOT))??
unmask_irq(desc);??
out_unlock:??
raw_spin_unlock(&desc->lock);??
}??
雖然handle_level_irq對(duì)電平中斷的流控進(jìn)行了必要的處理,因?yàn)殡娖街袛嗟奶匦裕褐灰獩](méi)有ack irq,中斷線會(huì)一直有效,所以我們不會(huì)錯(cuò)過(guò)某次中斷請(qǐng)求,但是驅(qū)動(dòng)程序的開(kāi)發(fā)人員如果對(duì)該過(guò)程理解不透徹,特別容易發(fā)生某次中斷被多次處理的情況。特別是使用了中斷線程(action->thread_fn)來(lái)響應(yīng)中斷的時(shí)候:通常mask_ack_irq只會(huì)清除中斷控制器的pending狀態(tài),很多慢速設(shè)備(例如通過(guò)i2c或spi控制的設(shè)備)需要在中斷線程中清除中斷線的pending狀態(tài),但是未等到中斷線程被調(diào)度執(zhí)行的時(shí)候,handle_level_irq早就返回了,這時(shí)已經(jīng)執(zhí)行過(guò)unmask_irq,設(shè)備的中斷線pending處于有效狀態(tài),中斷控制器會(huì)再次發(fā)出中斷請(qǐng)求,結(jié)果是設(shè)備的一次中斷請(qǐng)求,產(chǎn)生了兩次中斷響應(yīng)。要避免這種情況,最好的辦法就是不要單獨(dú)使用中斷線程處理中斷,而是要實(shí)現(xiàn)request_threaded_irq()的第二個(gè)參數(shù)irq_handler_t:handler,在handle回調(diào)中使用disable_irq()關(guān)閉該irq,然后在退出中斷線程回調(diào)前再enable_irq()。假設(shè)action->handler沒(méi)有屏蔽irq,以下這幅圖展示了電平中斷期間IRQ_PROGRESS標(biāo)志、本地中斷狀態(tài)和觸發(fā)其他CPU的狀態(tài):
圖3.1 ?電平觸發(fā)中斷狀態(tài)
上圖中顏色分別代表不同的狀態(tài):
狀態(tài)紅色綠色I(xiàn)RQ_PROGRESS? ? ? ? ? ?TRUE? ? ? ?FALSE是否允許本地cpu中斷? ? ? ? ? ? 禁止 ? ? ?? ? ? ? ? 允許 ?是否允許該設(shè)備再次觸發(fā)中斷(可能由其它c(diǎn)pu響應(yīng))? ? ? ? ? ? 禁止? ? ? ? ? 允許
4. ?handle_edge_irq
該函數(shù)用于處理邊沿觸發(fā)中斷的流控操作。邊沿觸發(fā)中斷的特點(diǎn)是,只有設(shè)備的中斷請(qǐng)求引腳(中斷線)的電平發(fā)生跳變時(shí)(由高變低或者有低變高),才會(huì)發(fā)出中斷請(qǐng)求,因?yàn)樘兪且凰查g,而且不會(huì)像電平中斷能保持住電平,所以處理不當(dāng)就特別容易漏掉一次中斷請(qǐng)求,為了避免這種情況,屏蔽中斷的時(shí)間必須越短越好。內(nèi)核的開(kāi)發(fā)者們顯然意識(shí)到這一點(diǎn),在正是處理中斷前,判斷IRQ_PROGRESS標(biāo)志沒(méi)有被設(shè)置的情況下,只是ack irq,并沒(méi)有mask irq,以便復(fù)位設(shè)備的中斷請(qǐng)求引腳,在這之后的中斷處理期間,另外的cpu可以再次響應(yīng)同一個(gè)irq請(qǐng)求,如果IRQ_PROGRESS已經(jīng)置位,表明另一個(gè)CPU正在處理該irq的上一次請(qǐng)求,這種情況下,他只是簡(jiǎn)單地設(shè)置IRQS_PENDING標(biāo)志,然后mask_ack_irq后退出,中斷請(qǐng)求交由原來(lái)的CPU繼續(xù)處理。因?yàn)槭莔ask_ack_irq,所以系統(tǒng)實(shí)際上只允許掛起一次中斷。
[cpp]?view plain?copy
if?(unlikely(irqd_irq_disabled(&desc->irq_data)?||??
irqd_irq_inprogress(&desc->irq_data)?||?!desc->action))?{??
if?(!irq_check_poll(desc))?{??
desc->istate?|=?IRQS_PENDING;??
mask_ack_irq(desc);??
goto?out_unlock;??
}??
}??
desc->irq_data.chip->irq_ack(&desc->irq_data);??
從上面的分析可以知道,處理中斷期間,另一次請(qǐng)求可能由另一個(gè)cpu響應(yīng)后掛起,所以在處理完本次請(qǐng)求后還要判斷IRQS_PENDING標(biāo)志,如果被置位,當(dāng)前cpu要接著處理被另一個(gè)cpu“委托”的請(qǐng)求。內(nèi)核在這里設(shè)置了一個(gè)循環(huán)來(lái)處理這種情況,直到IRQS_PENDING標(biāo)志無(wú)效為止,而且因?yàn)榱硪粋€(gè)cpu在響應(yīng)并掛起irq時(shí),會(huì)mask irq,所以在循環(huán)中要再次unmask irq,以便另一個(gè)cpu可以再次響應(yīng)并掛起irq:
[cpp]?view plain?copy
do?{??
......??
if?(unlikely(desc->istate?&?IRQS_PENDING))?{??
if?(!irqd_irq_disabled(&desc->irq_data)?&&??
irqd_irq_masked(&desc->irq_data))??
unmask_irq(desc);??
}??
handle_irq_event(desc);??
}?while?((desc->istate?&?IRQS_PENDING)?&&??
!irqd_irq_disabled(&desc->irq_data));??
IRQS_PENDING標(biāo)志會(huì)在handle_irq_event中清除。
圖4.1 ? 邊沿觸發(fā)中斷狀態(tài)
上圖中顏色分別代表不同的狀態(tài):
狀態(tài)? ? ? ? 紅色? ? ? ? 綠色I(xiàn)RQ_PROGRESS? ? ? ? TRUE? ? ? ? FALSE是否允許本地cpu中斷? ? ? ? 禁止? ? ? ? 允許是否允許該設(shè)備再次觸發(fā)中斷(可能由其它c(diǎn)pu響應(yīng))? ? ? ? 禁止? ? ? ? 允許是否處于中斷上下文? ? 處于中斷上下文? ? 處于進(jìn)程上下文
由圖4.1也可以看出,在處理軟件中斷(softirq)期間,此時(shí)仍然處于中斷上下文中,但是cpu的本地中斷是處于打開(kāi)狀態(tài)的,這表明此時(shí)嵌套中斷允許發(fā)生,不過(guò)這不要緊,因?yàn)橹匾奶幚硪呀?jīng)完成,被嵌套的也只是軟件中斷部分而已。這個(gè)也就是內(nèi)核區(qū)分top和bottom兩個(gè)部分的初衷吧。
5. ?handle_fasteoi_irq
現(xiàn)代的中斷控制器通常會(huì)在硬件上實(shí)現(xiàn)了中斷流控功能,例如ARM體系中的GIC通用中斷控制器。對(duì)于這種中斷控制器,CPU只需要在每次處理完中斷后發(fā)出一個(gè)end of interrupt(eoi),我們無(wú)需關(guān)注何時(shí)mask,何時(shí)unmask。不過(guò)雖然想著很完美,事情總有特殊的時(shí)候,所以?xún)?nèi)核還是給了我們插手的機(jī)會(huì),它利用irq_desc結(jié)構(gòu)中的preflow_handler字段,在正式處理中斷前會(huì)通過(guò)preflow_handler函數(shù)調(diào)用該回調(diào)。
[cpp]?view plain?copy
void??
handle_fasteoi_irq(unsigned?int?irq,?struct?irq_desc?*desc)??
{??
raw_spin_lock(&desc->lock);??
if?(unlikely(irqd_irq_inprogress(&desc->irq_data)))??
if?(!irq_check_poll(desc))??
goto?out;??
......??
if?(unlikely(!desc->action?||?irqd_irq_disabled(&desc->irq_data)))?{??
desc->istate?|=?IRQS_PENDING;??
mask_irq(desc);??
goto?out;??
}??
if?(desc->istate?&?IRQS_ONESHOT)??
mask_irq(desc);??
preflow_handler(desc);??
handle_irq_event(desc);??
out_eoi:??
desc->irq_data.chip->irq_eoi(&desc->irq_data);??
out_unlock:??
raw_spin_unlock(&desc->lock);??
return;??
......??
}??
此外,內(nèi)核還提供了另外一個(gè)eoi版的函數(shù):handle_edge_eoi_irq,它的處理類(lèi)似于handle_edge_irq,只是無(wú)需實(shí)現(xiàn)mask和unmask的邏輯。
6. ?handle_percpu_irq
該函數(shù)用于smp系統(tǒng),當(dāng)某個(gè)irq只在一個(gè)cpu上處理時(shí),我們可以無(wú)需用自旋鎖對(duì)數(shù)據(jù)進(jìn)行保護(hù),也無(wú)需處理cpu之間的中斷嵌套重入,所以函數(shù)很簡(jiǎn)單:
[cpp]?view plain?copy
void??
handle_percpu_irq(unsigned?int?irq,?struct?irq_desc?*desc)??
{??
struct?irq_chip?*chip?=?irq_desc_get_chip(desc);??
kstat_incr_irqs_this_cpu(irq,?desc);??
if?(chip->irq_ack)??
chip->irq_ack(&desc->irq_data);??
handle_irq_event_percpu(desc,?desc->action);??
if?(chip->irq_eoi)??
chip->irq_eoi(&desc->irq_data);??
}??
7. ?handle_nested_irq
該函數(shù)用于實(shí)現(xiàn)其中一種中斷共享機(jī)制,當(dāng)多個(gè)中斷共享某一根中斷線時(shí),我們可以把這個(gè)中斷線作為父中斷,共享該中斷的各個(gè)設(shè)備作為子中斷,在父中斷的中斷線程中決定和分發(fā)響應(yīng)哪個(gè)設(shè)備的請(qǐng)求,在得出真正發(fā)出請(qǐng)求的子設(shè)備后,調(diào)用handle_nested_irq來(lái)響應(yīng)中斷。所以,該函數(shù)是在進(jìn)程上下文執(zhí)行的,我們也無(wú)需掃描和執(zhí)行irq_desc結(jié)構(gòu)中的action鏈表。父中斷在初始化時(shí)必須通過(guò)irq_set_nested_thread函數(shù)明確告知中斷子系統(tǒng):這些子中斷屬于線程嵌套中斷類(lèi)型,這樣驅(qū)動(dòng)程序在申請(qǐng)這些子中斷時(shí),內(nèi)核不會(huì)為它們建立自己的中斷線程,所有的子中斷共享父中斷的中斷線程。
[cpp]?view plain?copy
void?handle_nested_irq(unsigned?int?irq)??
{??
......??
might_sleep();??
raw_spin_lock_irq(&desc->lock);??
......??
action?=?desc->action;??
if?(unlikely(!action?||?irqd_irq_disabled(&desc->irq_data)))??
goto?out_unlock;??
irqd_set(&desc->irq_data,?IRQD_IRQ_INPROGRESS);??
raw_spin_unlock_irq(&desc->lock);??
action_ret?=?action->thread_fn(action->irq,?action->dev_id);??
raw_spin_lock_irq(&desc->lock);??
irqd_clear(&desc->irq_data,?IRQD_IRQ_INPROGRESS);??
out_unlock:??
raw_spin_unlock_irq(&desc->lock);??
} ?
?
評(píng)論