作者:linuxer 發(fā)布于:2014-8-28 20:00 分類:中斷子系統(tǒng)
一、前言
當(dāng)外設(shè)觸發(fā)一次中斷后,一個大概的處理過程是:
1、具體CPU architecture相關(guān)的模塊會進(jìn)行現(xiàn)場保護(hù),然后調(diào)用machine driver對應(yīng)的中斷處理handler
2、machine driver對應(yīng)的中斷處理handler中會根據(jù)硬件的信息獲取HW interrupt ID,并且通過irq domain模塊翻譯成IRQ number
3、調(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來處理該中斷
4、具體CPU architecture相關(guān)的模塊會進(jìn)行現(xiàn)場恢復(fù)。
上面的1、4這兩個步驟在linux kernel的中斷子系統(tǒng)之(六):ARM中斷處理過程中已經(jīng)有了較為細(xì)致的描述,步驟2在linux kernel的中斷子系統(tǒng)之(二):irq domain介紹中介紹,本文主要描述步驟3,也就是linux中斷子系統(tǒng)的high level irq event handler。
注:這份文檔充滿了猜測和空想,很多地方描述可能是有問題的,不過我還是把它發(fā)出來,拋磚引玉,希望可以引發(fā)大家討論。
一、如何進(jìn)入high level irq event handler
1、從具體CPU architecture的中斷處理到machine相關(guān)的處理模塊
說到具體的CPU,我們還是用ARM為例好了。對于ARM,我們在ARM中斷處理文檔中已經(jīng)有了較為細(xì)致的描述。這里我們看看如何從從具體CPU的中斷處理到machine相關(guān)的處理模塊 ,其具體代碼如下:
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
adr lr, BSYM(9997f)
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
9997:
.endm
其實(shí),直接從CPU的中斷處理跳轉(zhuǎn)到通用中斷處理模塊是不可能的,中斷處理不可能越過interrupt controller這個層次。一般而言,通用中斷處理模塊會提供一些通用的中斷代碼處理庫,然后由interrupt controller這個層次的代碼調(diào)用這些通用中斷處理的完成整個的中斷處理過程?!癷nterrupt controller這個層次的代碼”是和硬件中斷系統(tǒng)設(shè)計(jì)相關(guān)的,例如:系統(tǒng)中有多少個interrupt contrller,每個interrupt controller是如何控制的?它們是如何級聯(lián)的?我們稱這些相關(guān)的驅(qū)動模塊為machine interrupt driver。
在上面的代碼中,如果配置了MULTI_IRQ_HANDLER的話,ARM中斷處理則直接跳轉(zhuǎn)到一個叫做handle_arch_irq函數(shù),如果系統(tǒng)中只有一個類型的interrupt controller(可能是多個interrupt controller,例如使用兩個級聯(lián)的GIC),那么handle_arch_irq可以在interrupt controller初始化的時(shí)候設(shè)定。代碼如下:
……
if (gic_nr == 0) {
set_handle_irq(gic_handle_irq);
}……
gic_nr是GIC的編號,linux kernel初始化過程中,每發(fā)現(xiàn)一個GIC,都是會指向GIC driver的初始化函數(shù)的,不過對于第一個GIC,gic_nr等于0,對于第二個GIC,gic_nr等于1。當(dāng)然handle_arch_irq這個函數(shù)指針不是per CPU的變量,是全部CPU共享的,因此,初始化一次就OK了。
當(dāng)使用多種類型的interrupt controller的時(shí)候(例如HW 系統(tǒng)使用了S3C2451這樣的SOC,這時(shí)候,系統(tǒng)有兩種interrupt controller,一種是GPIO type,另外一種是SOC上的interrupt controller),則不適合在interrupt controller中進(jìn)行設(shè)定,這時(shí)候,可以考慮在machine driver中設(shè)定。在這種情況下,handle_arch_irq 這個函數(shù)是在setup_arch函數(shù)中根據(jù)machine driver設(shè)定,具體如下:
handle_arch_irq = mdesc->handle_irq;
關(guān)于MULTI_IRQ_HANDLER這個配置項(xiàng),我們可以再多說幾句。當(dāng)然,其實(shí)這個配置項(xiàng)的名字已經(jīng)出賣它了。multi irq handler就是說系統(tǒng)中有多個irq handler,可以在run time的時(shí)候指定。為何要run time的時(shí)候,從多個handler中選擇一個呢?HW interrupt block難道不是固定的嗎?我的理解(猜想)是:一個kernel的image支持多個HW platform,對于不同的HW platform,在運(yùn)行時(shí)檢查HW platform的類型,設(shè)定不同的irq handler。
2、interrupt controller相關(guān)的代碼
我們還是以2個級聯(lián)的GIC為例來描述interrupt controller相關(guān)的代碼。代碼如下:
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];-----獲取root GIC的硬件描述符
void __iomem *cpu_base = gic_data_cpu_base(gic); 獲取root GIC mapping到CPU地址空間的信息do {
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);---獲取HW interrupt ID
irqnr = irqstat & ~0x1c00;if (likely(irqnr > 15 && irqnr < 1021)) {----SPI和PPI的處理
irqnr = irq_find_mapping(gic->domain, irqnr);---將HW interrupt ID轉(zhuǎn)成IRQ number
handle_IRQ(irqnr, regs);----處理該IRQ number
continue;
}
if (irqnr < 16) {-----IPI類型的中斷處理
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
handle_IPI(irqnr, regs);
#endif
continue;
}
break;
} while (1);
}
更多關(guān)于GIC相關(guān)的信息,請參考linux kernel的中斷子系統(tǒng)之(七):GIC代碼分析。對于ARM處理器,handle_IRQ代碼如下:
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{……
generic_handle_irq(irq);……
}
3、調(diào)用high level handler
調(diào)用high level handler的代碼邏輯非常簡單,如下:
int generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq); ---通過IRQ number獲取該irq的描述符if (!desc)
return -EINVAL;
generic_handle_irq_desc(irq, desc);----調(diào)用high level的irq handler來處理該IRQ
return 0;
}static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}
二、理解high level irq event handler需要的知識準(zhǔn)備
1、自動探測IRQ
一個硬件驅(qū)動可以通過下面的方法進(jìn)行自動探測它使用的IRQ:
unsigned long irqs;
int irq;irqs = probe_irq_on();--------啟動IRQ自動探測
驅(qū)動那個打算自動探測IRQ的硬件產(chǎn)生中斷
irq = probe_irq_off(irqs);-------結(jié)束IRQ自動探測
如果能夠自動探測到IRQ,上面程序中的irq(probe_irq_off的返回值)就是自動探測的結(jié)果。后續(xù)程序可以通過request_threaded_irq申請?jiān)揑RQ。probe_irq_on函數(shù)主要的目的是返回一個32 bit的掩碼,通過該掩碼可以知道可能使用的IRQ number有哪些,具體代碼如下:
unsigned long probe_irq_on(void)
{……
for_each_irq_desc_reverse(i, desc) { ----scan 從nr_irqs-1 到0 的中斷描述符
raw_spin_lock_irq(&desc->lock);
if (!desc->action && irq_settings_can_probe(desc)) {--------(1)
desc->istate |= IRQS_AUTODETECT | IRQS_WAITING;-----(2)
if (irq_startup(desc, false))
desc->istate |= IRQS_PENDING;
}
raw_spin_unlock_irq(&desc->lock);
}
msleep(100); --------------------------(3)
for_each_irq_desc(i, desc) {
raw_spin_lock_irq(&desc->lock);if (desc->istate & IRQS_AUTODETECT) {------------(4)
if (!(desc->istate & IRQS_WAITING)) {
desc->istate &= ~IRQS_AUTODETECT;
irq_shutdown(desc);
} else
if (i < 32)------------------------(5)
mask |= 1 << i;
}
raw_spin_unlock_irq(&desc->lock);
}return mask;
}
(1)那些能自動探測IRQ的中斷描述符需要具體兩個條件:
a、該中斷描述符還沒有通過request_threaded_irq或者其他方式申請?jiān)揑RQ的specific handler(也就是irqaction數(shù)據(jù)結(jié)構(gòu))
b、該中斷描述符允許自動探測(不能設(shè)定IRQ_NOPROBE)
(2)如果滿足上面的條件,那么該中斷描述符屬于備選描述符。設(shè)定其internal state為IRQS_AUTODETECT | IRQS_WAITING。IRQS_AUTODETECT表示本IRQ正處于自動探測中。
(3)在等待過程中,系統(tǒng)仍然允許,各種中斷依然會觸發(fā)。在各種high level irq event handler中,總會有如下的代碼:
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
這里會清除IRQS_WAITING狀態(tài)。
(4)這時(shí)候,我們還沒有控制那個想要自動探測IRQ的硬件產(chǎn)生中斷,因此處于自動探測中,并且IRQS_WAITING并清除的一定不是我們期待的IRQ(可能是spurious interrupts導(dǎo)致的),這時(shí)候,clear IRQS_AUTODETECT,shutdown該IRQ。
(5)最大探測的IRQ是31(mask是一個32 bit的value),mask返回的是可能的irq掩碼。
我們再來看看probe_irq_off的代碼:
int probe_irq_off(unsigned long val)
{
int i, irq_found = 0, nr_of_irqs = 0;
struct irq_desc *desc;for_each_irq_desc(i, desc) {
raw_spin_lock_irq(&desc->lock);if (desc->istate & IRQS_AUTODETECT) {----只有處于IRQ自動探測中的描述符才會被處理
if (!(desc->istate & IRQS_WAITING)) {----找到一個潛在的中斷描述符
if (!nr_of_irqs)
irq_found = i;
nr_of_irqs++;
}
desc->istate &= ~IRQS_AUTODETECT; ----IRQS_WAITING沒有被清除,說明該描述符
irq_shutdown(desc); 不是自動探測的那個,shutdown之
}
raw_spin_unlock_irq(&desc->lock);
}
mutex_unlock(&probing_active);if (nr_of_irqs > 1) ------如果找到多于1個的IRQ,說明探測失敗,返回負(fù)的IRQ個數(shù)信息
irq_found = -irq_found;return irq_found;
}
因?yàn)樵谡{(diào)用probe_irq_off已經(jīng)觸發(fā)了自動探測IRQ的那個硬件中斷,因此在該中斷的high level handler的執(zhí)行過程中,該硬件對應(yīng)的中斷描述符的IRQS_WAITING標(biāo)致應(yīng)該已經(jīng)被清除,因此probe_irq_off函數(shù)scan中斷描述符DB,找到處于auto probe中,而且IRQS_WAITING標(biāo)致被清除的那個IRQ。如果找到一個,那么探測OK,返回該IRQ number,如果找到多個,說明探測失敗,返回負(fù)的IRQ個數(shù)信息,沒有找到的話,返回0。
2、resend一個中斷
一個ARM SOC總是有很多的GPIO,有些GPIO可以提供中斷功能,這些GPIO的中斷可以配置成level trigger或者edge trigger。一般而言,大家都更喜歡用level trigger的中斷。有的SOC只能是有限個數(shù)的GPIO可以配置成電平中斷,因此,在項(xiàng)目初期進(jìn)行pin define的時(shí)候,大家都在爭搶電平觸發(fā)的GPIO。
電平觸發(fā)的中斷有什么好處呢?電平觸發(fā)的中斷很簡單、直接,只要硬件檢測到硬件事件(例如有數(shù)據(jù)到來),其assert指定的電平信號,CPU ack該中斷后,電平信號消失。但是對于邊緣觸發(fā)的中斷,它是用一個上升沿或者下降沿告知硬件的狀態(tài),這個狀態(tài)不是一個持續(xù)的狀態(tài),如果軟件處理不好,容易丟失中斷。
什么時(shí)候會resend一個中斷呢?我們考慮一個簡單的例子:
(1)CPU A上正在處理x外設(shè)的中斷
(2)x外設(shè)的中斷再次到來(CPU A已經(jīng)ack該IRQ,因此x外設(shè)的中斷可以再次觸發(fā)),這時(shí)候其他CPU會處理它(mask and ack),并設(shè)置該中斷描述符是pending狀態(tài),并委托CPU A處理該pending狀態(tài)的中斷。需要注意的是CPU已經(jīng)ack了該中斷,因此該中斷的硬件狀態(tài)已經(jīng)不是pending狀態(tài),無法觸發(fā)中斷了,這里的pending狀態(tài)是指中斷描述符的軟件狀態(tài)。
(3)CPU B上由于同步的需求,disable了x外設(shè)的IRQ,這時(shí)候,CPU A沒有處理pending狀態(tài)的x外設(shè)中斷就離開了中斷處理過程。
(4)當(dāng)enable x外設(shè)的IRQ的時(shí)候,需要檢測pending狀態(tài)以便resend該中斷,否則,該中斷會丟失的
具體代碼如下:
void check_irq_resend(struct irq_desc *desc, unsigned int irq)
{
if (irq_settings_is_level(desc)) {-------電平中斷不存在resend的問題
desc->istate &= ~IRQS_PENDING;
return;
}
if (desc->istate & IRQS_REPLAY)----如果已經(jīng)設(shè)定resend的flag,退出就OK了,這個應(yīng)該
return; 和irq的enable disable能多層嵌套相關(guān)
if (desc->istate & IRQS_PENDING) {-------如果有pending的flag則進(jìn)行處理
desc->istate &= ~IRQS_PENDING;
desc->istate |= IRQS_REPLAY; ------設(shè)置retrigger標(biāo)志if (!desc->irq_data.chip->irq_retrigger ||
!desc->irq_data.chip->irq_retrigger(&desc->irq_data)) {----調(diào)用底層irq chip的callback
#ifdef CONFIG_HARDIRQS_SW_RESEND
也可以使用軟件手段來完成resend一個中斷,具體代碼省略,有興趣大家可以自己看看
#endif
}
}
}
在各種high level irq event handler中,總會有如下的代碼:
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
這里會清除IRQS_REPLAY狀態(tài),表示該中斷已經(jīng)被retrigger,一次resend interrupt的過程結(jié)束。
3、unhandled interrupt和spurious interrupt
在中斷處理的最后,總會有一段代碼如下:
irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{……
if (!noirqdebug)
note_interrupt(irq, desc, retval);
return retval;
}
note_interrupt就是進(jìn)行unhandled interrupt和spurious interrupt處理的。對于這類中斷,linux kernel有一套復(fù)雜的機(jī)制來處理,你可以通過command line參數(shù)(noirqdebug)來控制開關(guān)該功能。
當(dāng)發(fā)生了一個中斷,但是沒有被處理(有兩種可能,一種是根本沒有注冊的specific handler,第二種是有handler,但是handler否認(rèn)是自己對應(yīng)的設(shè)備觸發(fā)的中斷),怎么辦?毫無疑問這是一個異常狀況,那么kernel是否要立刻采取措施將該IRQ disable呢?也不太合適,畢竟interrupt request信號線是允許共享的,直接disable該IRQ有可能會下手太狠,kernel采取了這樣的策略:如果該IRQ觸發(fā)了100,000次,但是99,900次沒有處理,在這種條件下,我們就是disable這個interrupt request line。多么有情有義的策略??!相關(guān)的控制數(shù)據(jù)在中斷描述符中,如下:
struct irq_desc {
……
unsigned int irq_count;--------記錄發(fā)生的中斷的次數(shù),每100,000則回滾
unsigned long last_unhandled;-----上一次沒有處理的IRQ的時(shí)間點(diǎn)
unsigned int irqs_unhandled;------沒有處理的次數(shù)
……
}
irq_count和irqs_unhandled都是比較直觀的,為何要記錄unhandled interrupt發(fā)生的時(shí)間呢?我們來看具體的代碼。具體的相關(guān)代碼位于note_interrupt中,如下:
void note_interrupt(unsigned int irq, struct irq_desc *desc, irqreturn_t action_ret)
{
if (desc->istate & IRQS_POLL_INPROGRESS || irq_settings_is_polled(desc))
return;
if (action_ret == IRQ_WAKE_THREAD)----h(huán)andler返回IRQ_WAKE_THREAD是正常情況
return;if (bad_action_ret(action_ret)) {-----報(bào)告錯誤,這些是由于specific handler的返回錯誤導(dǎo)致的
report_bad_irq(irq, desc, action_ret);
return;
}if (unlikely(action_ret == IRQ_NONE)) {-------是unhandled interrupt
if (time_after(jiffies, desc->last_unhandled + HZ/10))---(1)
desc->irqs_unhandled = 1;---重新開始計(jì)數(shù)
else
desc->irqs_unhandled++;---判定為unhandled interrupt,計(jì)數(shù)加一
desc->last_unhandled = jiffies;-------保存本次unhandled interrupt對應(yīng)的jiffies時(shí)間
}if (unlikely(try_misrouted_irq(irq, desc, action_ret))) {---是否啟動Misrouted IRQ fixup
int ok = misrouted_irq(irq);
if (action_ret == IRQ_NONE)
desc->irqs_unhandled -= ok;
}desc->irq_count++;
if (likely(desc->irq_count < 100000))-----------(2)
return;desc->irq_count = 0;
if (unlikely(desc->irqs_unhandled > 99900)) {--------(3)
__report_bad_irq(irq, desc, action_ret);---報(bào)告錯誤
desc->istate |= IRQS_SPURIOUS_DISABLED;
desc->depth++;
irq_disable(desc);mod_timer(&poll_spurious_irq_timer,----------(4)
jiffies + POLL_SPURIOUS_IRQ_INTERVAL);
}
desc->irqs_unhandled = 0;
}
(1)是否是一次有效的unhandled interrupt還要根據(jù)時(shí)間來判斷。一般而言,當(dāng)硬件處于異常狀態(tài)的時(shí)候往往是非常短的時(shí)間觸發(fā)非常多次的中斷,如果距離上次unhandled interrupt的時(shí)間超過了10秒(HZ=100),那么我們要把irqs_unhandled重新計(jì)數(shù)。如果不這么處理的話,隨著時(shí)間的累計(jì),最終irqs_unhandled可能會達(dá)到99900次的,從而把這個IRQ錯誤的推上了審判臺。
(2)irq_count每次都會加一,記錄IRQ被觸發(fā)的次數(shù)。但只要大于100000才啟動 step (3)中的檢查。一旦啟動檢查,irq_count會清零,irqs_unhandled也會清零,進(jìn)入下一個檢查周期。
(3)如果滿足條件(IRQ觸發(fā)了100,000次,但是99,900次沒有處理),disable該IRQ。
(4)啟動timer,輪詢整個系統(tǒng)中的handler來處理這個中斷(輪詢啊,絕對是真愛啊)。這個timer的callback函數(shù)定義如下:
static void poll_spurious_irqs(unsigned long dummy)
{
struct irq_desc *desc;
int i;if (atomic_inc_return(&irq_poll_active) != 1)----確保系統(tǒng)中只有一個excuting thread進(jìn)入臨界區(qū)
goto out;
irq_poll_cpu = smp_processor_id(); ----記錄當(dāng)前正在polling的CPUfor_each_irq_desc(i, desc) {------遍歷所有的中斷描述符
unsigned int state;if (!i)-------------越過0號中斷描述符。對于X86,這是timer的中斷
continue;/* Racy but it doesn't matter */
state = desc->istate;
barrier();
if (!(state & IRQS_SPURIOUS_DISABLED))----名花有主的那些就不必考慮了
continue;local_irq_disable();
try_one_irq(i, desc, true);---------OK,嘗試一下是不是可以處理
local_irq_enable();
}
out:
atomic_dec(&irq_poll_active);
mod_timer(&poll_spurious_irq_timer,--------一旦觸發(fā)了該timer,就停不下來
jiffies + POLL_SPURIOUS_IRQ_INTERVAL);
}
三、和high level irq event handler相關(guān)的硬件描述
1、CPU layer和Interrupt controller之間的接口
從邏輯層面上看,CPU和interrupt controller之間的接口包括:
(1)觸發(fā)中斷的signal。一般而言,這個(些)信號是電平觸發(fā)的。對于ARM CPU,它是nIRQ和nFIQ信號線,對于X86,它是INT和NMI信號線,對于PowerPC,這些信號線包括MC(machine check)、CRIT(critical interrupt)和NON-CRIT(Non critical interrupt)。對于linux kernel的中斷子系統(tǒng),我們只使用其中一個信號線(例如對于ARM而言,我們只使用nIRQ這個信號線)。這樣,從CPU層面看,其邏輯動作非常的簡單,不區(qū)分優(yōu)先級,觸發(fā)中斷的那個信號線一旦assert,并且CPU沒有mask中斷,那么軟件就會轉(zhuǎn)到一個異常向量執(zhí)行,完畢后返回現(xiàn)場。
(2)Ack中斷的signal。這個signal可能是物理上的一個連接CPU和interrupt controller的銅線,也可能不是。對于X86+8259這樣的結(jié)構(gòu),Ack中斷的signal就是nINTA信號線,對于ARM+GIC而言,這個信號就是總線上的一次訪問(讀Interrupt Acknowledge Register寄存器)。CPU ack中斷標(biāo)識cpu開啟啟動中斷服務(wù)程序(specific handler)去處理該中斷。對于X86而言,ack中斷可以讓8259將interrupt vector數(shù)據(jù)送到數(shù)據(jù)總線上,從而讓CPU獲取了足夠的處理該中斷的信息。對于ARM而言,ack中斷的同時(shí)也就是獲取了發(fā)生中斷的HW interrupt ID,總而言之,ack中斷后,CPU獲取了足夠開啟執(zhí)行中斷處理的信息。
(3)結(jié)束中斷(EOI,end of interrupt)的signal。這個signal用來標(biāo)識CPU已經(jīng)完成了對該中斷的處理(specific handler或者ISR,interrupt serivce routine執(zhí)行完畢)。實(shí)際的物理形態(tài)這里就不描述了,和ack中斷signal是類似的。
(4)控制總線和數(shù)據(jù)總線接口。通過這些接口,CPU可以訪問(讀寫)interrupt controller的寄存器。
2、Interrupt controller和Peripheral device之間的接口
所有的系統(tǒng)中,Interrupt controller和Peripheral device之間的接口都是一個Interrupt Request信號線。外設(shè)通過這個信號線上的電平或者邊緣向CPU(實(shí)際上是通過interrupt controller)申請中斷服務(wù)。
四、幾種典型的high level irq event handler
本章主要介紹幾種典型的high level irq event handler,在進(jìn)行high level irq event handler的設(shè)定的時(shí)候需要注意,不是外設(shè)使用電平觸發(fā)就選用handle_level_irq,選用什么樣的high level irq event handler是和Interrupt controller的行為以及外設(shè)電平觸發(fā)方式?jīng)Q定的。介紹每個典型的handler之前,我會簡單的描述該handler要求的硬件行為,如果該外設(shè)的中斷系統(tǒng)符合這個硬件行為,那么可以選擇該handler為該中斷的high level irq event handler。
1、邊緣觸發(fā)的handler。
使用handle_edge_irq這個handler的硬件中斷系統(tǒng)行為如下:
我們以上升沿為例描述邊緣中斷的處理過程(下降沿的觸發(fā)是類似的)。當(dāng)interrupt controller檢測到了上升沿信號,會將該上升沿狀態(tài)(pending)鎖存在寄存器中,并通過中斷的signal向CPU觸發(fā)中斷。需要注意:這時(shí)候,外設(shè)和interrupt controller之間的interrupt request信號線會保持高電平,這也就意味著interrupt controller不可能檢測到新的中斷信號(本身是高電平,無法形成上升沿)。這個高電平信號會一直保持到軟件ack該中斷(調(diào)用irq chip的irq_ack callback函數(shù))。ack之后,中斷控制器才有可能繼續(xù)探測上升沿,觸發(fā)下一次中斷。
ARM+GIC組成的系統(tǒng)不符合這個類型。雖然GIC提供了IAR(Interrupt Acknowledge Register)寄存器來讓ARM來ack中斷,但是,在調(diào)用high level handler之前,中斷處理程序需要通過讀取IAR寄存器獲得HW interrpt ID并轉(zhuǎn)換成IRQ number,因此實(shí)際上,對于GIC的irq chip,它是無法提供本場景中的irq_ack函數(shù)的。很多GPIO type的interrupt controller符合上面的條件,它們會提供pending狀態(tài)寄存器,讀可以獲取pending狀態(tài),而向pending狀態(tài)寄存器寫1可以ack該中斷,讓interrupt controller可以繼續(xù)觸發(fā)下一次中斷。
handle_edge_irq代碼如下:
void handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
raw_spin_lock(&desc->lock); -----------------(0)desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);----參考上一章的描述
if (unlikely(irqd_irq_disabled(&desc->irq_data) ||-----------(1)
irqd_irq_inprogress(&desc->irq_data) || !desc->action)) {
if (!irq_check_poll(desc)) {
desc->istate |= IRQS_PENDING;
mask_ack_irq(desc);
goto out_unlock;
}
}
kstat_incr_irqs_this_cpu(irq, desc); ---更新該IRQ統(tǒng)計(jì)信息
desc->irq_data.chip->irq_ack(&desc->irq_data); ---------(2)do {
if (unlikely(!desc->action)) { -----------------(3)
mask_irq(desc);
goto out_unlock;
}
if (unlikely(desc->istate & IRQS_PENDING)) { ---------(4)
if (!irqd_irq_disabled(&desc->irq_data) &&
irqd_irq_masked(&desc->irq_data))
unmask_irq(desc);
}handle_irq_event(desc); -------------------(5)
} while ((desc->istate & IRQS_PENDING) &&
!irqd_irq_disabled(&desc->irq_data)); -------------(6)out_unlock:
raw_spin_unlock(&desc->lock); -----------------(7)
}
(0) 這時(shí)候,中斷仍然是關(guān)閉的,因此不會有來自本CPU的并發(fā),使用raw spin lock就防止其他CPU上對該IRQ的中斷描述符的訪問。針對該spin lock,我們直觀的感覺是raw_spin_lock和(7)中的raw_spin_unlock是成對的,實(shí)際上并不是,handle_irq_event中的代碼是這樣的:
irqreturn_t handle_irq_event(struct irq_desc *desc)
{raw_spin_unlock(&desc->lock); -------和上面的(0)對應(yīng)
處理具體的action list
raw_spin_lock(&desc->lock);--------和上面的(7)對應(yīng)
}
實(shí)際上,由于在handle_irq_event中處理action list的耗時(shí)還是比較長的,因此處理具體的action list的時(shí)候并沒有持有中斷描述符的spin lock。在如果那樣的話,其他CPU在對中斷描述符進(jìn)行操作的時(shí)候需要spin的時(shí)間會很長的。
(1)判斷是否需要執(zhí)行下面的action list的處理。這里分成幾種情況:
a、該中斷事件已經(jīng)被其他的CPU處理了
b、該中斷被其他的CPU disable了
c、該中斷描述符沒有注冊specific handler。這個比較簡單,如果沒有irqaction,根本沒有必要調(diào)用action list的處理
如果該中斷事件已經(jīng)被其他的CPU處理了,那么我們僅僅是設(shè)定pending狀態(tài)(為了委托正在處理的該中斷的那個CPU進(jìn)行處理),mask_ack_irq該中斷并退出就OK了,并不做具體的處理。另外正在處理該中斷的CPU會檢查pending狀態(tài),并進(jìn)行處理的。同樣的,如果該中斷被其他的CPU disable了,本就不應(yīng)該繼續(xù)執(zhí)行該中斷的specific handler,我們也是設(shè)定pending狀態(tài),mask and ack中斷就退出了。當(dāng)其他CPU的代碼離開臨界區(qū),enable 該中斷的時(shí)候,軟件會檢測pending狀態(tài)并resend該中斷。
這里的irq_check_poll代碼如下:
static bool irq_check_poll(struct irq_desc *desc)
{
if (!(desc->istate & IRQS_POLL_INPROGRESS))
return false;
return irq_wait_for_poll(desc);
}
IRQS_POLL_INPROGRESS標(biāo)識了該IRQ正在被polling(上一章有描述),如果沒有被輪詢,那么返回false,進(jìn)行正常的設(shè)定pending標(biāo)記、mask and ack中斷。如果正在被輪詢,那么需要等待poll結(jié)束。
(2)ack該中斷。對于中斷控制器,一旦被ack,表示該外設(shè)的中斷被enable,硬件上已經(jīng)準(zhǔn)備好觸發(fā)下一次中斷了。再次觸發(fā)的中斷會被調(diào)度到其他的CPU上?,F(xiàn)在,我們可以再次回到步驟(1)中,為什么這里用mask and ack而不是單純的ack呢?如果單純的ack則意味著后續(xù)中斷還是會觸發(fā),這時(shí)候怎么處理?在pending+in progress的情況下,我們要怎么處理?記錄pending的次數(shù),有意義嗎?由于中斷是完全異步的,也有可能pending的標(biāo)記可能在另外的CPU上已經(jīng)修改為replay的標(biāo)記,這時(shí)候怎么辦?當(dāng)事情變得復(fù)雜的時(shí)候,那一定是本來方向就錯了,因此,mask and ack就是最好的策略,我已經(jīng)記錄了pending狀態(tài),不再考慮pending嵌套的情況。
(3)在調(diào)用specific handler處理具體的中斷的時(shí)候,由于不持有中斷描述符的spin lock,因此其他CPU上有可能會注銷其specific handler,因此do while循環(huán)之后,desc->action有可能是NULL,如果是這樣,那么mask irq,然后退出就OK了
(4)如果中斷描述符處于pending狀態(tài),那么一定是其他CPU上又觸發(fā)了該interrupt source的中斷,并設(shè)定了pending狀態(tài),“委托”本CPU進(jìn)行處理,這時(shí)候,需要把之前mask住的中斷進(jìn)行unmask的操作。一旦unmask了該interrupt source,后續(xù)的中斷可以繼續(xù)觸發(fā),由其他的CPU處理(仍然是設(shè)定中斷描述符的pending狀態(tài),委托當(dāng)前正在處理該中斷請求的那個CPU進(jìn)行處理)。
(5)處理該中斷請求事件
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
struct irqaction *action = desc->action;
irqreturn_t ret;desc->istate &= ~IRQS_PENDING;----CPU已經(jīng)準(zhǔn)備處理該中斷了,因此,清除pending狀態(tài)
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);--設(shè)定INPROGRESS的flag
raw_spin_unlock(&desc->lock);ret = handle_irq_event_percpu(desc, action); ---遍歷action list,調(diào)用specific handler
raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);---處理完成,清除INPROGRESS標(biāo)記
return ret;
}
(6)只要有pending標(biāo)記,就說明該中斷還在pending狀態(tài),需要繼續(xù)處理。當(dāng)然,如果有其他的CPU disable了該interrupt source,那么本次中斷結(jié)束處理。
2、電平觸發(fā)的handler
使用handle_level_irq這個handler的硬件中斷系統(tǒng)行為如下:
我們以高電平觸發(fā)為例。當(dāng)interrupt controller檢測到了高電平信號,并通過中斷的signal向CPU觸發(fā)中斷。這時(shí)候,對中斷控制器進(jìn)行ack并不能改變interrupt request signal上的電平狀態(tài),一直要等到執(zhí)行具體的中斷服務(wù)程序(specific handler),對外設(shè)進(jìn)行ack的時(shí)候,電平信號才會恢復(fù)成低電平。在對外設(shè)ack之前,中斷狀態(tài)一直是pending的,如果沒有mask中斷,那么中斷控制器就會assert CPU。
handle_level_irq的代碼如下:
void handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
mask_ack_irq(desc); ---------------------(1)if (unlikely(irqd_irq_inprogress(&desc->irq_data)))---------(2)
if (!irq_check_poll(desc))
goto out_unlock;desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);--和retrigger中斷以及自動探測IRQ相關(guān)
kstat_incr_irqs_this_cpu(irq, desc);
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {-----(3)
desc->istate |= IRQS_PENDING;
goto out_unlock;
}handle_irq_event(desc);
cond_unmask_irq(desc); --------------(4)
out_unlock:
raw_spin_unlock(&desc->lock);
}
(1)考慮CPU<------>interrupt controller<------>device這樣的連接方式中,我們認(rèn)為high level handler主要是和interrupt controller交互,而specific handler(request_irq注冊的那個)是和device進(jìn)行交互。Level類型的中斷的特點(diǎn)就是只要外設(shè)interrupt request line的電平狀態(tài)是有效狀態(tài),對于interrupt controller,該外設(shè)的interrupt總是active的。由于外設(shè)檢測到了事件(比如數(shù)據(jù)到來了),因此assert了指定的電平信號,這個電平信號會一直保持,直到軟件清除了外設(shè)的狀態(tài)寄存器。但是,high level irq event handler這個層面只能操作Interrupt controller,不能操作具體外設(shè)的寄存器(那應(yīng)該屬于具體外設(shè)的specific interrupt handler處理內(nèi)容,該handler會掛入中斷描述符中的IRQ action list)。直到在具體的中斷服務(wù)程序(specific handler中)操作具體外設(shè)的寄存器,才能讓這個asserted電平信號消息。
正是因?yàn)閘evel trigger的這個特點(diǎn),因此,在high level handler中首先mask并ack該IRQ。這一點(diǎn)和邊緣觸發(fā)的high level handler有顯著的不同,在handle_edge_irq中,我們僅僅是ack了中斷,并沒有mask,因?yàn)檫吘売|發(fā)的中斷稍縱即逝,一旦mask了該中斷,容易造成中斷丟失。而對于電平中斷,我們不得不mask住該中斷,如果不mask住,只要CPU ack中斷,中斷控制器將持續(xù)的assert CPU中斷(因?yàn)橛行щ娖綘顟B(tài)一直保持)。如果我們mask住該中斷,中斷控制器將不再轉(zhuǎn)發(fā)該interrupt source來的中斷,因此,所有的CPU都不會感知到該中斷,直到軟件unmask。這里的ack是針對interrupt controller的ack,本身ack就是為了clear interrupt controller對該IRQ的狀態(tài)寄存器,不過由于外部的電平仍然是有效信號,其實(shí)未必能清除interrupt controller的中斷狀態(tài),不過這是和中斷控制器硬件實(shí)現(xiàn)相關(guān)的。
(2)對于電平觸發(fā)的high level handler,我們一開始就mask并ack了中斷,因此后續(xù)specific handler因該是串行化執(zhí)行的,為何要判斷in progress標(biāo)記呢?不要忘記spurious interrupt,那里會直接調(diào)用handler來處理spurious interrupt。
(3)這里有兩個場景
a、沒有注冊specific handler。如果沒有注冊handler,那么保持mask并設(shè)定pending標(biāo)記(這個pending標(biāo)記有什么作用還沒有想明白)。
b、該中斷被其他的CPU disable了。如果該中斷被其他的CPU disable了,本就不應(yīng)該繼續(xù)執(zhí)行該中斷的specific handler,我們也是設(shè)定pending狀態(tài),mask and ack中斷就退出了。當(dāng)其他CPU的代碼離開臨界區(qū),enable 該中斷的時(shí)候,軟件會檢測pending狀態(tài)并resend該中斷。
(4)為何是有條件的unmask該IRQ?正常的話當(dāng)然是umask就OK了,不過有些threaded interrupt(這個概念在下一份文檔中描述)要求是one shot的(首次中斷,specific handler中開了一槍,wakeup了irq handler thread,如果允許中斷嵌套,那么在specific handler會多次開槍,這也就不是one shot了,有些IRQ的handler thread要求是one shot,也就是不能嵌套specific handler)。
3、支持EOI的handler
TODO
原創(chuàng)文章,轉(zhuǎn)發(fā)請注明出處。蝸窩科技。http://www.wowotech.net/linux_kenrel/High_level_irq_event_handler.html
標(biāo)簽: 中斷處理
評論: