翻譯:Linux的電源管理架構

設備電源管理

Copyright (c) 2010 Rafael J. Wysocki<[email protected]>, Novell Inc.

Copyright (c) 2010 Alan Stern[email protected]

 

*************************************************************

本文由DroidPhone翻譯於2011.8.5

*************************************************************

 

 

Linux的源代碼裏,大部分都屬於設備驅動程序的代碼,因此,大多數電源管理(PM)的代碼也是存在於驅動程序當中。很多驅動程序可能只做了少量的工作,另外一些,例如使用電池供電的硬件平臺(移動電話等)則會在電源管理上做了大量的工作。

 

這份文檔對驅動程序如何與系統的電源管理部分交互做了一個大概的描述,尤其是關聯到驅動程序核心中的模型和接口的共享,建議從事驅動程序相關領域的人通過本文檔可以瞭解相關的背景知識。

 

設備電源管理的兩種模型

===================================

驅動程序可以使用其中一種模型來使設備進入低功耗狀態:

1. 系統睡眠模型:

驅動程序作爲一部分,跟隨系統級別的低功耗狀態,就像"suspend"(也叫做"suspend-to-RAM"),或者對於有硬盤的系統,可以進入"hibernation"(也叫做"suspend-to-disk")。

 

這種情況下,驅動程序,總線,設備類驅動一起,通過各種特定於設備的suspend和resume方法,清晰地關閉硬件設備和各個軟件子系統,然後在數據不被丟失的情況下重新激活硬件設備。

 

有些驅動程序可以管理硬件的喚醒事件,這些事件可以讓系統離開低功耗狀態。這一特性可以通過相應的/sys/devices/.../power/wakeup文件來開啓和關閉(對於Ethernet驅動程序,ethtool通過ioctl接口達到同樣的目的);使能該功能可能會導致額外的功耗,但他讓整個系統有更多的機會進入低功耗狀態。

 

2. Runtime 電源管理模型:

這種模型允許設備在系統運行階段進入低功耗狀態,原則上,他可以獨立於其他的電源管理活動。不過,通常設備之間不能單獨進行控制(例如,父設備不能進入suspend,除非他的所有子設備已經進入suspend狀態)。此外,依據不同的總線類型,可能必須做出一些特別的操作來達到目的。如果設備在系統運行階段進入了低功耗狀態,在系統級別的電源狀態遷移時(suspend或hibernation)就必須做出特別的處理。

 

正因爲這個原因,不僅僅設備驅動程序本身,相應的子系統(bus type,device type,device class)驅動程序和電源管理核心也會被捲入到rumtime電源管理的工作中來。比如當系統睡眠時,以上的各模塊必須互相合作來實現各種多樣的suspend和resume方法,以便讓硬件進入低功耗狀態,喚醒後繼續提供服務而不丟失數據。

 

對於低功耗狀態的定義,我們沒有太多可以說的,因爲他們通常特定於系統,甚至特定於某個設備。如果在系統運行狀態,足夠多的設備進入了低功耗狀態,這時的效果其實和進入了系統級別的低功耗狀態非常相像。這樣一些驅動程序可以利用rumtime電源管理讓系統進入一種類似深度省電的狀態。

 

大多數進入suspend狀態的設備會停止所有的I/O操作:不會有DMA或者IRQ請求(需要喚醒系統的除外),不會有數據的讀寫,不再接受上層驅動的請求。這對於不同的總線和平臺會有不同的要求。

 

關於硬件喚醒事件的一些例子:由RTC發起的鬧鐘,網絡數據包的到達,鍵盤或者鼠標的活動,媒體的插入或移除(PCMCIA,MMC/SD,USB,等等)。

 

進入系統睡眠狀態的接口

===================================================

內核爲各個子系統(bus type,device type, device class)和驅動程序提供了相應的編程接口,以便它們參與它們所關心的設備的電源管理。這些接口覆蓋了系統級別的睡眠和runtime級別的管理。

 

設備電源管理操作

===================================================

子系統和驅動程序的設備電源管理操作,都定義在dev_pm_ops結構中:

struct dev_pm_ops {

         int(*prepare)(struct device *dev);

         void(*complete)(struct device *dev);

         int(*suspend)(struct device *dev);

         int(*resume)(struct device *dev);

         int(*freeze)(struct device *dev);

         int(*thaw)(struct device *dev);

         int(*poweroff)(struct device *dev);

         int(*restore)(struct device *dev);

         int(*suspend_noirq)(struct device *dev);

         int(*resume_noirq)(struct device *dev);

         int(*freeze_noirq)(struct device *dev);

         int(*thaw_noirq)(struct device *dev);

         int(*poweroff_noirq)(struct device *dev);

         int(*restore_noirq)(struct device *dev);

         int(*runtime_suspend)(struct device *dev);

         int(*runtime_resume)(struct device *dev);

         int(*runtime_idle)(struct device *dev);

};

這個結構在include/linux/pm.h中定義,它們的作用將會在接下來進行描述。現在,我們只要記住,最後三個方法是專門用於rumtime pm的,其他的則用於系統級別的電源狀態遷移。

 

某些子系統中,依然存在所謂“過時的”或“傳統的”電源管理操作接口,這種方式不會使用到dev_pm_ops結構,而且只適用於系統級別的電源管理方法,這邊文章裏將不會對它進行說明,如果要了解的話請直接查看內核的源代碼。

 

子系統級別(Subsystem-Level)方法

------------------------------------------------

設備進入suspend和resume的關鍵方法在bus_type結構、device_type結構和class結構的pm成員中,他是一個dev_pm_ops結構的指針。多數情況下,這些都是那些具體總線的體系結構(例如PCI或USB或某個設備類別和設備類)的維護者們來關注的部分。

 

總線驅動會適當地實現這些方法以供硬件和驅動程序使用它們;因爲PCI和USB有不同的工作方式。只有少數人會編寫subsystem-level的驅動程序;大多數的設備驅動程序是建立在各種特定總線架構的代碼之上。

 

有關這些調用,稍後會進行更詳盡的描述;它們將會順着父子形式的設備模型樹,一個設備一個設備地被調用。

 

/sys/devices/.../power/wakeup files

-------------------------------------------------

設備模型中的所有設備都有兩個標誌來控制喚醒事件(可使得設備或系統退出低功耗狀態)。設兩個標誌位由總線或者設備驅動用device_set_wakeup_capable()和device_set_wakeup_enable()來初始化,它們在include/linux/pm_wakeup.h中定義。

 

"can_wakeup"標誌表示設備(或驅動)物理上支持喚醒事件,device_set_wakeup_capable()函數會影響該標誌。"should_wakeup"標誌控制設備是否應該嘗試啓用他的喚醒機制。device_set_wakeup_enable()會影響該標誌。大部分的驅動程序不會主動修改它們的值。大多數設備的should_wakeup的初始值都被設爲false,也有例外,比如電源鍵、鍵盤和由ethtool設置了wake-on-LAN功能的網卡。

 

設備是否有能力發出喚醒事件是一個硬件的問題,內核只是負責持續地跟蹤這些事件的發生。另外一方面,一個有喚醒能力的設備是否應該發起喚醒事件則是一個策略問題,它是由用戶空間通過sysfs的屬性文件(power/wakeup)進行管理的。用戶空間可以寫入"enabled",或"disabled"來設置或清除shoule_wakeup標誌,相應地,讀取該文件時,如果can_wakeup標誌是true則返回對應的字符串,如果can_wakeup是false,則返回一個空字符串,以此來表明設備不支持喚醒事件。(需要注意的是,儘管返回空字符串,該文件的寫入依然會影響should_wakeup標誌)

 

只有當這兩個標誌都爲true時,device_may_wakeup()函數纔會返回true。當系統遷移到睡眠狀態時,驅動程序應該在讓設備進入低功耗狀態前通過這一函數檢查,確定是否啓用喚醒機制。不過,在rumtime電源管理模式下,不管設備和驅動程序是否都支持,也不管should_wakeup標誌是否設置,喚醒事件都會被使能。

 

/sys/devices/.../power/control files

------------------------------------------------

設備模型中的每個設備都有一個標誌位來控制它是否屬於runtime電源管理模式。這個叫runtime_auto的標誌由bus type(或其他子系統)用pm_rumtime_allow()或者是pm_rumtime_forbid()來初始化。默認值是允許rumtimepm的。

 

用戶空間可以通過向設備的sysfs文件power/control寫入"on"或者"auto"來修改該標誌位。寫入"auto"相當於調用了pm_rumtime_allow(),允許設備由驅動程序進行rumtimepm。寫入"on"相當於調用pm_rumtime_forbid(),標誌位被清除,設備將會從低功耗狀態返回全功率狀態,並且阻止設備進行runtime電源管理。用戶空間也可以讀取該文件來檢查runtime_auto的當前值。

 

設備的runtime_auto標誌不會影響系統級別電源狀態的遷移。特別注意的是,儘管runtime_auto標誌被清除,當系統級別的電源狀態遷移到睡眠狀態時,設備也會被帶入低功耗狀態。

 

關於runtime電源管理架構的更多信息,請參看Documentation/power/runtime_pm.txt。

 

調用驅動程序進入或退出系統睡眠狀態

==================================

當系統進入睡眠狀態,系統會要求設備驅動程序讓設備進入兼容於目標系統的一種狀態來掛起(suspend)設備。這通常是某種"off"狀態。具體情況都是特定於各系統的。另外,可喚醒的設備一般會保持部分功能以便適當的時候可以喚醒系統。

 

當系統退出低功耗狀態時,設備驅動程序被要求恢復(resume)設備讓他進入全電源狀態。suspend和resume動作總是一起發生的,兩者都可分爲多個不同的階段。

 

對於相對簡單的驅動程序,suspend可能在suspend_noirq階段使用上層的類代碼來停止設備並儘可能讓它們進入"off"狀態。喚醒時,相對應的resume調用會重新初始化硬件,然後重新激活他們的I/O活動。

 

對電源有特別需求的驅動程序可能會讓設備做出必要的準備,以便之後可以產生喚醒事件。

 

保證回調的順序

-------------------------------------

當設備進入suspend或resume時,因爲設備之間具備一定的橋接關係,爲了確保能夠正確地訪問它們,suspend時會在設備數中按照自底向上的順序進行,而resume時則是按照自頂向下的順序進行。

 

設備在設備數中的順序決定於設備註冊的順序:子設備永遠不能先於父設備進行註冊、探測或resume;也不能在父設備之後進行移除或掛起。

 

具體的策略就是設備數應該和硬件的總線拓撲結構相吻合。特別是,這就意味着當父設備正在進行掛起動作(例如,已經被pm的核心選爲下一個將要被掛起的設備)、或者已經掛起的情況下,註冊子設備就會失敗。設備驅動程序必須正確地處理這種情況。

 

系統電源管理中的各個階段

------------------------------------------------

suspend和resume是分階段完成的。Standby、Sleep(suspend-to-RAM)和hibernation(suspend-to-disk)會使用到不同的階段。在進入下一個階段之前,都需要爲每個設備調用屬於本階段的回調函數。不是所有的總線和設備類都會支持所有這些回調,也不是所有的驅動程序都要使用這些回調。有些階段需要凍結進程後,解凍進程前執行。此外,*_noirq階段需要在IRQ被關閉的情況下執行(除非他們被IRQ_WAKEUP標記)。

 

多數階段使用bus、type和class的回調(也就是定義在dev->bus->pm,dev->type->pm和dev->class->pm中)。不過prepare和complete階段是個例外,他們僅僅使用了bus的回調。當一個階段中有多個回調要執行時,按照以下順序調用,suspend時:<class,type,bus>,resume時:<bus,type,class>。比如,在suspend時將會執行以下調用順序:

         dev->class->pm.suspend(dev);

         dev->type->pm.suspend(dev);

         dev->bus->pm.suspend(dev);

相反,在resume階段,移至下一個設備之前,pm核心在當前設備按以下回調進行:

         dev->bus->pm.resume(dev);

         dev->type->pm.resume(dev);

         dev->class->pm.resume(dev);

這些回調可以反過來通過dev->driver->pm來調用設備或驅動特定的方法,但這不是必需的。

 

系統掛起(suspend)

------------------------------------

當系統進入standby或sleep狀態時,需要經歷以下階段:

prepare,suspend,suspend_noirq。

1. prepare階段主要是通過阻止新設備註冊來防止竟態的發生;如果此時要註冊子設備,PM的核心將會不知道一個設備的所有子設備已經被suspend。(相反,設備可以在任何時刻被註銷。)不像suspend其他的階段,prepare階段設備樹會自頂向下進行掃描。

 

prepare階段只使用了bus的回調。回調返回後,該設備的下面將不可以註冊新的子設備。回調方法也會讓設備或驅動爲將要到來的系統電源狀態遷移做出準備,但它不應該讓設備進入低功耗狀態。

 

2. suspend階段由suspend回調實現,它停止設備的一切I/O操作。它同時也可以保存設備的寄存器,依據設備所屬的總線類型,讓設備進入合適的低功耗狀態,同時可以使能喚醒事件。

 

3. suspend_noirq階段發生在IRQ被禁止之後,這意味着該回調運行期間,驅動程序的中斷處理代碼不會被調用。回調方法可以保存上一階段沒有保存的寄存器並最終讓設備進入相應的低功耗狀態。

    大多數子系統(subsystem)和驅動程序不需要實現這一回調。不過,某些允許設備共享中斷向量的總線類型,例如PCI,通常需要這一回調;否則,當本設備已經進入低功耗時另一個與他共享中斷的設備感知中斷的發生,驅動程序將會發生錯誤。

 

這些階段結束後,驅動程序必須停止所有的I/O事務(DMA,IRQs),保存足夠的狀態信息以便它們能被重新初始化或回覆之前的狀態(按應將的需要而定),然後讓設備進入低功耗狀態。很多平臺上,它們會關閉某些時鐘;有時還會關閉電源或者是降低電壓。(支持rumtime pm的驅動可能已經提前完成部分或所有的步驟。)

如果device_may_wakeup(dev)返回true,設備準備好產生硬件喚醒信號以便觸發一個系統喚醒事件來喚醒已經進入睡眠狀態的系統。例如,enable_irq_wakeup()可以讓一個連接到某個開關或外部硬件的GPIO被捕捉,pci_enable_wake()則響應類似PCI PME等信號。

 

只要這些回調中的一個返回錯誤,系統不會進入所述的低功耗狀態,而是由pm的核心對已經suspend的設備發起resume動作進行回退。

 

退出系統掛起(resume)

----------------------------------------

當系統退出standby或sleep狀態時,需要經歷以下階段:

resume_noirq,resume,complete。

1. resume_noirq回調方法應該執行所有在中斷處理程序被調用前的必須動作。這通常意味着撤銷suspend_noirq階段所做的動作。如果總線類型允許共享中斷向量,例如PCI,該回調方法應該使設備和驅動能夠識別自身是否是中斷源,如果是,還要能正確地處理。

    例如,對於PCI總線,bus->pm.resume_noirq()讓設備進入全電源狀態(PCI中稱作D0),並回復設備的標準配置寄存器。然後,調用設備驅動程序的 ->pm.resume_noirq()方法來執行特定於設備的動作。

 

2. resume回調方法讓設備回到他的工作狀態,以便它能執行正常的I/O。這通常等同於執行suspend階段的撤銷工作。

 

3. complete階段僅僅使用bus的回調。該方法應該撤銷prepare階段所做出的動作。不過請注意,新設備可能在resume回調返回後立刻被註冊,而不必等到complete階段完成。

 

這些階段結束後,驅動應該和suspend之前一樣:I/O能通過DMA或IRQs執行,相應的時鐘被打開。儘管在系統睡眠之前,設備因爲runtime pm已經處於低功耗狀態之下,在這之後設備還是應該回到全電源狀態。有很多原因說明爲什麼要這樣做,詳細的討論請參考:Documentation/power/runtime_pm.txt。

 

不過,到這以後,具體還是會特定於平臺的。例如,一些系統支持多種"run"狀態,resume後的模式可能不同於suspend之前。可能是某些時鐘或電源的改變,這些都會很容易影響到驅動程序如何工作。

 

驅動程序需要能夠處理在suspend回調被調用後硬件被複位的情況,例如需要徹底地重新初始化。這可能是最困難的部分,實現細節可能會受到NDA等文檔和chip errata的保護。最簡單的情況是硬件的狀態自suspend被執行後沒有改變過,單這是不能保證的(實際上,這通常都不成立)。

 

不管物理上是否可能,驅動程序也要準備被知會系統power-down期間設備被移除。在Linux中,PCMCIA,MMC,USB,Firewire,SCSI甚至IDE都是可移除的例子。具體的關於驅動程序如何被知會,和處理這種移除事件的工作是特定於總線的,而且通常有單獨的線程來處理。

 

進入Hibernation

--------------------------------------

省略......................................

 

退出Hibernation

--------------------------------------

省略......................................

 

系統設備

----------------------------------------

系統設備(sysdevs)遵循稍微不同的API,它們可以在以下文件中找到:

   include/linux/sysdev.h

   drivers/base/sys.c

系統設備要在中斷關閉的情況下進行suspend,並且要在其他設備被掛起之後執行,喚醒時,它們會先於其他設備被resume,當然也是在關中斷的情況下。這些動作都特別的"sysdev_driver"階段發生,該階段僅會對系統設備起作用。

 

因此,在suspend_noirq(freeze_noirq,poweroff_noirq)階段之後,當非啓動(non-boot)的CPUs都被關閉而且剩下的CPU的IRQs也被關閉的情況下,這時候就會啓動sysdev_driver.suspend階段,接下來系統進入睡眠狀態(對於hibernation是系統映像被創建)。resume期間的順序就是:sysdev_driver.resume階段執行,開啓啓動用CPU的IRQ,打開其他非啓動CPU,然後開始resume_noirq階段。

 

實際進入和退出系統級別低功耗狀態的代碼有時候會調用一些只有boot firmware(bios?bootloader?)才知道的硬件操作,然後保留CPU運行某一軟件(從RAM或者FLASH中)來監控系統和管理喚醒序列。

 

設備低功耗(suspend)狀態

------------------------------------------

設備的低功耗狀態並沒有標準可言。某個設備可以只處理"on"和"off",但另一個設備可能支持一打不同版本的"on"(多少個引擎被激活?),加上一個可以比徹底"off"更快地回到"on"的狀態。

 

一些總線對不同的suspend狀態定義了一些規則。PCI可以給出一個例子來:suspend的序列完成後,一個非傳統(non-legacy)德爾PCI設備不可以執行DMA或發出IRQs,而且喚醒事件要通過PME#總線信號發出。還定義了幾個PCI標準的設備狀態,其中一些狀態可以只是作爲選項。

 

相反,集成度較高的SOC處理器經常使用IRQs作爲喚醒源(因此驅動要調用enable_irq_wake()),而且可以用DMA的完成中斷作爲喚醒事件(有時DMA能保持激活,只是CPU和一些外設進入睡眠)。

 

這裏有些細節可以是特定於平臺的。在某些睡眠狀態下,系統可以有部分設備保持激活,例如系統輕度睡眠時,LCD顯示器會使用DMA繼續進行刷新,frame buffer甚至可能有DSP或者另外的非Linux的CPU來刷新,而運行Linux的CPU卻可以處於idle狀態。

 

再有,依賴於不同的目標系統的狀態,一些特殊的事情可能發生。一些目標系統狀態可以允許設備有很多的操作活動,另一些目標系統狀態也許會要求硬關機然後再resume時重新初始化。而且,兩個不同的目標系統可以按不同的方法使用相同的設備;就像上面提到的LCD那樣,他可以在一個產品的"standby"下保持在激活狀態,但另一個使用同樣SOC的不同產品可能就會有不一樣的工作方式。

 

電源管理通知消息

------------------------------

有些操作在上面討論的電源管理回調方法中是不能被開展的,因爲回調發生時已經太晚或者太早。爲了處理這些情況,子系統和驅動程序可以註冊電源管理通知,以便在進程被凍結之前或者是解凍之後調用某個操作。一般來說,PM通知機制適合於執行用戶空間可以利用的活動,或者至少不至於干擾到用戶空間的活動。

 

詳細說明可以參考文檔Documentation/power/notifiers.txt。

 

runtime電源管理

======================

許多設備能夠在系統運行時動態地關閉,這個特性對那些已經沒被使用的設備特別有用,而且能讓運行中的系統有更高效地節約能源。這些設備通常支持一定範圍的runtime電源狀態,例如"off","sleep","idle","active"等等,這些狀態有時會被設備所使用的總線所約束,而且通常會包含系統級別睡眠所用到的硬件狀態。

 

系統級別電源狀態遷移可以在某些設備因爲rumtimepm而進入低功耗狀態的情況下開始。系統睡眠的PM回調應該要能識別這種情況並用適當的方法重新激活他們,不過這些動作都是特定於各個子系統的。

 

有時候這會由子系統這一級別來決定,有時候也會讓設備驅動程序自己決定,在系統級別的電源狀態遷移時可以讓一個已經suspend的設備保留注意一狀態,另一些情況則可能會臨時讓設備回到全電源狀態,例如爲了禁止它喚醒系統的能力。這些都依賴於具體的硬件和子系統的設計,是驅動程序要關注的問題。

 

當系統從睡眠狀態喚醒的過程中,最好是讓設備回到全電源狀態,解釋請參考文檔Documentation/power/runtime_pm.txt。該文檔有更詳細的關於這些問題的討論,也解釋了runtime電源管理的通用架構。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章