一、設(shè)備IRQ的suspend和resume
本小節(jié)主要解決這樣一個(gè)問(wèn)題:在系統(tǒng)休眠過(guò)程中,如何suspend設(shè)備中斷(IRQ)?在從休眠中喚醒的過(guò)程中,如何resume設(shè)備IRQ?
一般而言,在系統(tǒng)suspend過(guò)程的后期,各個(gè)設(shè)備的IRQ (interrupt request line)會(huì)被disable掉。具體的時(shí)間點(diǎn)是在各個(gè)設(shè)備的late suspend階段之后。代碼如下(刪除了部分無(wú)關(guān)代碼):
static int suspend_enter(suspend_state_t state, bool *wakeup)
{……
error = dpm_suspend_late(PMSG_SUSPEND);-----late suspend階段
error = platform_suspend_prepare_late(state);
下面的代碼中會(huì)disable各個(gè)設(shè)備的irq
error = dpm_suspend_noirq(PMSG_SUSPEND);----進(jìn)入noirq的階段
error = platform_suspend_prepare_noirq(state);
……
}
在dpm_suspend_noirq函數(shù)中,會(huì)針對(duì)系統(tǒng)中的每一個(gè)device,依次調(diào)用device_suspend_noirq來(lái)執(zhí)行該設(shè)備noirq情況下的suspend callback函數(shù),當(dāng)然,在此之前會(huì)調(diào)用suspend_device_irqs函數(shù)來(lái)disable所有設(shè)備的irq。
之所以這么做,其思路是這樣的:在各個(gè)設(shè)備驅(qū)動(dòng)完成了late suspend之后,按理說(shuō)這些已經(jīng)被suspend的設(shè)備不應(yīng)該再觸發(fā)中斷了。如果還有一些設(shè)備沒(méi)有被正確的suspend,那么我們最好的策略是mask該設(shè)備的irq,從而阻止中斷的遞交。此外,在過(guò)去的代碼中(指interrupt handler),我們對(duì)設(shè)備共享IRQ的情況處理的不是很好,存在這樣的問(wèn)題:在共享IRQ的設(shè)備們完成suspend之后,如果有中斷觸發(fā),這時(shí)候設(shè)備驅(qū)動(dòng)的interrupt handler并沒(méi)有準(zhǔn)備好。在有些場(chǎng)景下,interrupt handler會(huì)訪問(wèn)已經(jīng)suspend設(shè)備的IO地址空間,從而導(dǎo)致不可預(yù)知的issue。這些issue很難debug,因此,我們引入了suspend_device_irqs()以及設(shè)備noirq階段的callback函數(shù)。
系統(tǒng)resume過(guò)程中,在各個(gè)設(shè)備的early resume過(guò)程之前,各個(gè)設(shè)備的IRQ會(huì)被重新打開(kāi),具體代碼如下(刪除了部分無(wú)關(guān)代碼):
static int suspend_enter(suspend_state_t state, bool *wakeup)
{……
platform_resume_noirq(state);----首先執(zhí)行noirq階段的resume
dpm_resume_noirq(PMSG_RESUME);------在這里會(huì)恢復(fù)irq,然后進(jìn)入early resume階段
platform_resume_early(state);
dpm_resume_early(PMSG_RESUME);
……}
在dpm_resume_noirq函數(shù)中,會(huì)調(diào)用各個(gè)設(shè)備驅(qū)動(dòng)的noirq callback,在此之后,調(diào)用resume_device_irqs函數(shù),完成各個(gè)設(shè)備irq的enable。
二、關(guān)于IRQF_NO_SUSPEND Flag
當(dāng)然,有些中斷需要在整個(gè)系統(tǒng)的suspend-resume過(guò)程中(包括在noirq階段,包括將nonboot CPU推送到offline狀態(tài)以及系統(tǒng)resume后,將其重新設(shè)置為online的階段)保持能夠觸發(fā)的狀態(tài)。一個(gè)簡(jiǎn)單的例子就是timer中斷,此外IPI以及一些特殊目的設(shè)備中斷也需要如此。
在中斷申請(qǐng)的時(shí)候,IRQF_NO_SUSPEND flag可以用來(lái)告知IRQ subsystem,這個(gè)中斷就是上一段文字中描述的那種中斷:需要在系統(tǒng)的suspend-resume過(guò)程中保持enable狀態(tài)。有了這個(gè)flag,suspend_device_irqs并不會(huì)disable該IRQ,從而讓該中斷在隨后的suspend和resume過(guò)程中,保持中斷開(kāi)啟。當(dāng)然,這并不能保證該中斷可以將系統(tǒng)喚醒。如果想要達(dá)到喚醒的目的,請(qǐng)調(diào)用enable_irq_wake。
需要注意的是:IRQF_NO_SUSPEND flag影響使用該IRQ的所有外設(shè)(一個(gè)IRQ可以被多個(gè)外設(shè)共享,不過(guò)ARM中不會(huì)這么用)。如果一個(gè)IRQ被多個(gè)外設(shè)共享,并且各個(gè)外設(shè)都注冊(cè)了對(duì)應(yīng)的interrupt handler,如果其一在申請(qǐng)中斷的時(shí)候使用了IRQF_NO_SUSPEND flag,那么在系統(tǒng)suspend的時(shí)候(指suspend_device_irqs之后,按理說(shuō)各個(gè)IRQ已經(jīng)被disable了),所有該IRQ上的各個(gè)設(shè)備的interrupt handler都可以被正常的被觸發(fā)執(zhí)行,即便是有些設(shè)備在調(diào)用request_irq(或者其他中斷注冊(cè)函數(shù))的時(shí)候沒(méi)有設(shè)定IRQF_NO_SUSPEND flag。正因?yàn)槿绱耍覀儜?yīng)該盡可能的避免同時(shí)使用IRQF_NO_SUSPEND 和IRQF_SHARED這兩個(gè)flag。
三、系統(tǒng)中斷喚醒接口:enable_irq_wake() 和 disable_irq_wake()
有些中斷可以將系統(tǒng)從睡眠狀態(tài)中喚醒,我們稱之“可以喚醒系統(tǒng)的中斷”,當(dāng)然,“可以喚醒系統(tǒng)的中斷”需要配置才能啟動(dòng)喚醒系統(tǒng)這樣的功能。這樣的中斷一般在工作狀態(tài)的時(shí)候就是作為普通I/O interrupt出現(xiàn),只要在準(zhǔn)備使能喚醒系統(tǒng)功能的時(shí)候,才會(huì)發(fā)起一些特別的配置和設(shè)定。
這樣的配置和設(shè)定有可能是和硬件系統(tǒng)(例如SOC)上的信號(hào)處理邏輯相關(guān)的,我們可以考慮下面的HW block圖:
外設(shè)的中斷信號(hào)被送到“通用的中斷信號(hào)處理模塊”和“特定中斷信號(hào)接收模塊”。正常工作的時(shí)候,我們會(huì)turn on“通用的中斷信號(hào)處理模塊”的處理邏輯,而turn off“特定中斷信號(hào)接收模塊” 的處理邏輯。但是,在系統(tǒng)進(jìn)入睡眠狀態(tài)的時(shí)候,有可能“通用的中斷信號(hào)處理模塊”已經(jīng)off了,這時(shí)候,我們需要啟動(dòng)“特定中斷信號(hào)接收模塊”來(lái)接收中斷信號(hào),從而讓系統(tǒng)suspend-resume模塊(它往往是suspend狀態(tài)時(shí)候唯一能夠工作的HW block了)可以正常的被該中斷信號(hào)喚醒。一旦喚醒,我們最好是turn off“特定中斷信號(hào)接收模塊”,讓外設(shè)的中斷處理回到正常的工作模式,同時(shí),也避免了系統(tǒng)suspend-resume模塊收到不必要的干擾。
IRQ子系統(tǒng)提供了兩個(gè)接口函數(shù)來(lái)完成這個(gè)功能:enable_irq_wake()函數(shù)用來(lái)打開(kāi)該外設(shè)中斷線通往系統(tǒng)電源管理模塊(也就是上面的suspend-resume模塊)之路,另外一個(gè)接口是disable_irq_wake(),用來(lái)關(guān)閉該外設(shè)中斷線通往系統(tǒng)電源管理模塊路徑上的各種HW block。
調(diào)用了enable_irq_wake會(huì)影響系統(tǒng)suspend過(guò)程中的suspend_device_irqs處理,代碼如下:
static bool suspend_device_irq(struct irq_desc *desc)
{
……
if (irqd_is_wakeup_set(&desc->irq_data)) {
irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED);
return true;
}
省略Disable 中斷的代碼
}
也就是說(shuō),一旦調(diào)用enable_irq_wake設(shè)定了該設(shè)備的中斷作為系統(tǒng)suspend的喚醒源,那么在該外設(shè)的中斷不會(huì)被disable,只是被標(biāo)記一個(gè)IRQD_WAKEUP_ARMED的標(biāo)記。對(duì)于那些不是wakeup source的中斷,在suspend_device_irq 函數(shù)中會(huì)標(biāo)記IRQS_SUSPENDED并disable該設(shè)備的irq。在系統(tǒng)喚醒過(guò)程中(resume_device_irqs),被diable的中斷會(huì)重新enable。
當(dāng)然,如果在suspend的過(guò)程中發(fā)生了某些事件(例如wakeup source產(chǎn)生了有效信號(hào)),從而導(dǎo)致本次suspend abort,那么這個(gè)abort事件也會(huì)通知到PM core模塊。事件并不需要被立刻通知到PM core模塊,一般而言,suspend thread會(huì)在某些點(diǎn)上去檢查pending的wakeup event。
在系統(tǒng)suspend的過(guò)程中,每一個(gè)來(lái)自wakeup source的中斷都會(huì)終止suspend過(guò)程或者將系統(tǒng)喚醒(如果系統(tǒng)已經(jīng)進(jìn)入suspend狀態(tài))。但是,在執(zhí)行了suspend_device_irqs之后,普通的中斷被屏蔽了,這時(shí)候,即便HW觸發(fā)了中斷信號(hào)也無(wú)法執(zhí)行其interrupt handler。作為wakeup source的IRQ會(huì)怎樣呢?雖然它的中斷沒(méi)有被mask掉,但是其interrupt handler也不會(huì)執(zhí)行(這時(shí)候的HW Signal只是用來(lái)喚醒系統(tǒng))。唯一有機(jī)會(huì)執(zhí)行的interrupt handler是那些標(biāo)記IRQF_NO_SUSPEND flag的IRQ,因?yàn)樗鼈兊闹袛嗍冀K是enable的。當(dāng)然,這些中斷不應(yīng)該調(diào)用enable_irq_wake進(jìn)行喚醒源的設(shè)定。
四、Interrupts and Suspend-to-Idle
Suspend-to-idle (也被稱為"freeze" 狀態(tài))是一個(gè)相對(duì)比較新的系統(tǒng)電源管理狀態(tài),相關(guān)代碼如下:
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
……
各個(gè)設(shè)備的late suspend階段
各個(gè)設(shè)備的noirq suspend階段
if (state == PM_SUSPEND_FREEZE) {
freeze_enter();
goto Platform_wake;
}
……
}
Freeze和suspend的前面的操作基本是一樣的:首先凍結(jié)系統(tǒng)中的進(jìn)程,然后是suspend系統(tǒng)中的形形色色的device,不一樣的地方在noirq suspend完成之后,freeze不會(huì)disable那些non-BSP的處理器和syscore suspend階段,而是調(diào)用freeze_enter函數(shù),把所有的處理器推送到idle狀態(tài)。這時(shí)候,任何的enable的中斷都可以將系統(tǒng)喚醒。而這也就意味著那些標(biāo)記IRQF_NO_SUSPEND(其IRQ沒(méi)有在suspend_device_irqs過(guò)程中被mask掉)是有能力將處理器從idle狀態(tài)中喚醒(不過(guò),需要注意的是:這種信號(hào)并不會(huì)觸發(fā)一個(gè)系統(tǒng)喚醒信號(hào)),而普通中斷由于其IRQ被disable了,因此無(wú)法喚醒idle狀態(tài)中的處理器。
那些能夠喚醒系統(tǒng)的wakeup interrupt呢?由于其中斷沒(méi)有被mask掉,因此也可以將系統(tǒng)從suspend-to-idle狀態(tài)中喚醒。整個(gè)過(guò)程和將系統(tǒng)從suspend狀態(tài)中喚醒一樣,唯一不同的是:將系統(tǒng)從freeze狀態(tài)喚醒走的中斷處理路徑,而將系統(tǒng)從suspend狀態(tài)喚醒走的喚醒處理路徑,需要電源管理HW BLOCK中特別的中斷處理邏輯的參與。
五、IRQF_NO_SUSPEND 標(biāo)志和enable_irq_wake函數(shù)不能同時(shí)使用
針對(duì)一個(gè)設(shè)備,在申請(qǐng)中斷的時(shí)候使用IRQF_NO_SUSPEND flag,又同時(shí)調(diào)用enable_irq_wake設(shè)定喚醒源是不合理的,主要原因如下:
1、如果IRQ沒(méi)有共享,使用IRQF_NO_SUSPEND flag說(shuō)明你想要在整個(gè)系統(tǒng)的suspend-resume過(guò)程中(包括suspend_device_irqs之后的階段)保持中斷打開(kāi)以便正常的調(diào)用其interrupt handler。而調(diào)用enable_irq_wake函數(shù)則說(shuō)明你想要將該設(shè)備的irq信號(hào)設(shè)定為中斷源,因此并不期望調(diào)用其interrupt handler。而這兩個(gè)需求明顯是互斥的。
2、IRQF_NO_SUSPEND 標(biāo)志和enable_irq_wake函數(shù)都不是針對(duì)一個(gè)interrupt handler的,而是針對(duì)該IRQ上的所有注冊(cè)的handler的。在一個(gè)IRQ上共享喚醒源以及no suspend中斷源是比較荒謬的。
不過(guò),在非常特殊的場(chǎng)合下,一個(gè)IRQ可以被設(shè)定為wakeup source,同時(shí)也設(shè)定IRQF_NO_SUSPEND 標(biāo)志。為了代碼邏輯正確,該設(shè)備的驅(qū)動(dòng)代碼需要滿足一些特別的需求。
?
評(píng)論
查看更多