裝置電源管理基礎

版權:

© 2010-2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.

版權:

© 2010 Alan Stern <stern@rowland.harvard.edu>

版權:

© 2016 Intel Corporation

作者:

Rafael J. Wysocki <rafael.j.wysocki@intel.com>

Linux 中大部分程式碼是裝置驅動程式,因此 Linux 電源管理 (PM) 程式碼的大部分也是特定於驅動程式的。 大多數驅動程式做的很少; 其他驅動程式,尤其是用於具有小型電池的平臺(如手機)的驅動程式,將做很多。

本文概述了驅動程式如何與系統範圍的電源管理目標互動,重點介紹了與連線到驅動程式模型核心的所有事物共享的模型和介面。 閱讀它作為您使用任何特定驅動程式進行特定領域工作的基礎。

裝置電源管理的兩種模型

驅動程式將使用以下一種或兩種模型將裝置置於低功耗狀態

系統睡眠模型

驅動程式可以作為進入系統範圍的低功耗狀態(如“掛起”(也稱為“掛起到 RAM”)或(主要針對具有磁碟的系統)“休眠”(也稱為“掛起到磁碟”)的一部分,進入低功耗狀態。

這是裝置、匯流排和類驅動程式透過實現各種角色特定的掛起和恢復方法來協作完成的,以乾淨地關閉硬體和軟體子系統,然後在不丟失資料的情況下重新啟用它們。

一些驅動程式可以管理硬體喚醒事件,使系統退出低功耗狀態。 可以使用相關的 /sys/devices/.../power/wakeup 檔案啟用或停用此功能(對於乙太網驅動程式,ethtool 使用的 ioctl 介面也可用於此目的);啟用它可能會消耗一些電力,但可以讓整個系統更頻繁地進入低功耗狀態。

執行時電源管理模型

原則上,裝置也可以在系統執行時置於低功耗狀態,與其他電源管理活動無關。 但是,裝置通常不是彼此獨立的(例如,除非其所有子裝置都已掛起,否則父裝置無法掛起)。 此外,根據裝置所在的匯流排型別,可能需要為此目的對裝置執行一些匯流排特定的操作。 在執行時置於低功耗狀態的裝置在系統範圍的電源轉換(掛起或休眠)期間可能需要特殊處理。

由於這些原因,不僅裝置驅動程式本身,而且適當的子系統(匯流排型別、裝置型別或裝置類)驅動程式和 PM 核心都參與執行時電源管理。 與系統睡眠電源管理情況一樣,它們需要透過實現各種角色特定的掛起和恢復方法來進行協作,以便在不丟失資料或服務的情況下乾淨地關閉和重新啟用硬體。

關於這些低功耗狀態沒有太多可說的,除了它們是非常系統特定的,並且通常是裝置特定的。 此外,如果足夠的裝置(在執行時)已置於低功耗狀態,則效果可能非常類似於進入某些系統範圍的低功耗狀態(系統睡眠)......並且存在協同效應,因此幾個使用執行時 PM 的驅動程式可能會使系統進入一種狀態,在這種狀態下甚至可以使用更深層的節能選項。

大多數掛起的裝置將停止所有 I/O:沒有更多的 DMA 或 IRQ(除了喚醒事件),沒有更多的資料讀取或寫入,並且不再接受來自上游驅動程式的請求。 但是,給定的匯流排或平臺可能具有不同的要求。

硬體喚醒事件的示例包括來自即時時鐘的警報、網路喚醒 LAN 資料包、鍵盤或滑鼠活動以及介質插入或移除(對於 PCMCIA、MMC/SD、USB 等)。

進入系統睡眠狀態的介面

為子系統(匯流排型別、裝置型別、裝置類)和裝置驅動程式提供了程式設計介面,以允許它們參與其關注的裝置的電源管理。 這些介面涵蓋系統睡眠和執行時電源管理。

裝置電源管理操作

裝置電源管理操作,在子系統級別以及裝置驅動程式級別,透過定義和填充型別為 struct dev_pm_ops 的物件來實現,該物件在 include/linux/pm.h 中定義。 其中包含的方法的作用將在下面解釋。 現在,記住最後三種方法是特定於執行時電源管理,而其餘方法在系統範圍的電源轉換期間使用就足夠了。

至少對於某些子系統,還提供了一種已棄用的“舊”或“傳統”介面用於電源管理操作。 這種方法不使用 struct dev_pm_ops 物件,它僅適用於以有限的方式實現系統睡眠電源管理方法。 因此,本文件未對其進行描述,因此請直接參考原始碼以獲取有關它的更多資訊。

子系統級別方法

掛起和恢復裝置的核心方法位於 struct dev_pm_ops 中,該物件由 struct dev_pm_domainops 成員指向,或者由 struct bus_type、struct device_type 和 struct classpm 成員指向。 它們主要與為平臺和匯流排(如 PCI 或 USB)或裝置型別和裝置類驅動程式編寫基礎設施的人員相關。 它們也與裝置驅動程式的編寫者相關,這些裝置驅動程式的子系統(PM 域、裝置型別、裝置類和匯流排型別)未提供所有電源管理方法。

匯流排驅動程式根據硬體和使用它的驅動程式實現這些方法; PCI 的工作方式與 USB 不同,依此類推。 編寫子系統級驅動程式的人員不多; 大多數驅動程式程式碼都是建立在匯流排特定框架程式碼之上的“裝置驅動程式”。

有關這些驅動程式呼叫的更多資訊,請參見後面的描述; 它們在每個裝置上分階段呼叫,尊重驅動程式模型樹中的父子排序。

/sys/devices/.../power/wakeup 檔案

驅動程式模型中的所有裝置物件都包含控制系統喚醒事件處理(可以強制系統退出睡眠狀態的硬體訊號)的欄位。 這些欄位由匯流排或裝置驅動程式程式碼使用 device_set_wakeup_capable()device_set_wakeup_enable() 進行初始化,這些函式在 include/linux/pm_wakeup.h 中定義。

power.can_wakeup 標誌僅記錄裝置(及其驅動程式)是否可以物理支援喚醒事件。 device_set_wakeup_capable() 例程會影響此標誌。 power.wakeup 欄位是指向型別為 struct wakeup_source 的物件的指標,該物件用於控制裝置是否應使用其系統喚醒機制,以及用於將裝置發出的系統喚醒事件通知 PM 核心。 此物件僅存在於具有喚醒功能的裝置(即,can_wakeup 標誌已設定的裝置)上,並且由 device_set_wakeup_capable() 建立(或刪除)。

裝置是否能夠發出喚醒事件是一個硬體問題,核心負責跟蹤它。 相比之下,是否應該發出喚醒事件是一個策略決策,它由使用者空間透過 sysfs 屬性管理:power/wakeup 檔案。 使用者空間可以將“enabled”或“disabled”字串寫入其中,以分別指示該裝置是否應發出系統喚醒訊號。 僅當給定裝置存在 power.wakeup 物件時,此檔案才存在,並且它與該物件一起由 device_set_wakeup_capable() 建立(或刪除)。 從該檔案讀取將返回相應的字串。

對於大多數裝置,power/wakeup 檔案中的初始值為“disabled”; 主要例外是電源按鈕、鍵盤和乙太網介面卡,它們的 WoL(區域網喚醒)功能已使用 ethtool 設定。 對於不自行生成喚醒請求但僅將喚醒請求從一個匯流排轉發到另一個匯流排的裝置(如 PCI Express 埠),它也應預設為“enabled”。

僅當 power.wakeup 物件存在並且相應的 power/wakeup 檔案包含“enabled”字串時,device_may_wakeup() 例程才會返回 true。 子系統(如 PCI 匯流排型別程式碼)使用此資訊來檢視是否啟用裝置的喚醒機制。 如果裝置喚醒機制由驅動程式直接啟用或停用,它們也應使用 device_may_wakeup() 來決定在系統睡眠轉換期間該怎麼做。 但是,在任何情況下,都不希望裝置驅動程式直接呼叫 device_set_wakeup_enable()

應該注意的是,系統喚醒在概念上不同於執行時電源管理使用的“遠端喚醒”,儘管它可能受到相同物理機制的支援。 遠端喚醒是一項功能,允許低功耗狀態的裝置觸發特定的中斷,以發出它們應置於全功率狀態的條件訊號。 這些中斷可能用於也可能不用於發出系統喚醒事件訊號,具體取決於硬體設計。 在某些系統上,無法從系統睡眠狀態觸發它們。 在任何情況下,對於支援遠端喚醒的所有裝置和驅動程式,應始終為執行時電源管理啟用遠端喚醒。

/sys/devices/.../power/control 檔案

驅動程式模型中的每個裝置都有一個標誌,用於控制它是否受執行時電源管理的影響。 此標誌 runtime_auto 由匯流排型別(或通常為子系統)程式碼使用 pm_runtime_allow()pm_runtime_forbid() 初始化; 預設設定為允許執行時電源管理。

使用者空間可以透過將“on”或“auto”寫入裝置的 power/control sysfs 檔案來調整設定。 寫入“auto”會呼叫 pm_runtime_allow(),設定該標誌並允許驅動程式對裝置進行執行時電源管理。 寫入“on”會呼叫 pm_runtime_forbid(),清除該標誌,如果裝置處於低功耗狀態,則將其返回到全功率狀態,並阻止對裝置進行執行時電源管理。 使用者空間可以透過讀取該檔案來檢查 runtime_auto 標誌的當前值。

裝置的 runtime_auto 標誌對系統範圍的電源轉換的處理沒有影響。 特別是,即使裝置的 runtime_auto 標誌已清除,也可以(並且在大多數情況下應該並且將會)在系統範圍的轉換到睡眠狀態期間將裝置置於低功耗狀態。

有關執行時電源管理框架的更多資訊,請參見 I/O 裝置的執行時電源管理框架

呼叫驅動程式以進入和退出系統睡眠狀態

當系統進入睡眠狀態時,會要求每個裝置的驅動程式透過將其置於與目標系統狀態相容的狀態來掛起裝置。 這通常是某種“關閉”版本,但詳細資訊是特定於系統的。 此外,啟用喚醒的裝置通常會保持部分功能,以便喚醒系統。

當系統退出該低功耗狀態時,會要求裝置的驅動程式透過將其返回到全功率狀態來恢復它。 掛起和恢復操作始終一起進行,並且兩者都是多階段操作。

對於簡單的驅動程式,掛起可能會使用類程式碼停止裝置,然後在 suspend_noirq 期間儘可能“關閉”其硬體。 匹配的恢復呼叫將完全重新初始化硬體,然後重新啟用其類 I/O 佇列。

更節能的驅動程式可能會準備裝置以觸發系統喚醒事件。

呼叫序列保證

為了確保在掛起或恢復裝置時,需要與裝置通訊的橋接器和類似連結可用,因此裝置層次結構以自下而上的順序遍歷以掛起裝置。 自上而下的順序用於恢復這些裝置。

裝置層次結構的排序由設備註冊的順序定義:子裝置永遠不能在其父裝置之前註冊、探測或恢復; 並且不能在該父裝置之後移除或掛起。

策略是裝置層次結構應與硬體匯流排拓撲匹配。 [或者至少對於使用多個匯流排的裝置,應與控制匯流排匹配。] 特別是,這意味著如果裝置的父裝置正在掛起(即已被 PM 核心選擇為下一個要掛起的裝置)或已經掛起,以及在所有其他裝置都已掛起之後,設備註冊可能會失敗。 裝置驅動程式必須準備好應對這種情況。

系統電源管理階段

掛起或恢復系統分多個階段完成。 不同的階段用於掛起到空閒、淺層(待機)和深層(“掛起到 RAM”)睡眠狀態以及休眠狀態(“掛起到磁碟”)。 每個階段都涉及在下一個階段開始之前為每個裝置執行回撥。 並非所有匯流排或類都支援所有這些回撥,並且並非所有驅動程式都使用所有回撥。 各種階段始終在任務被凍結之後和解凍之前執行。 此外,*_noirq 階段在 IRQ 處理程式已停用的情況下執行(除了那些標有 IRQF_NO_SUSPEND 標誌的處理程式)。

所有階段都使用 PM 域、匯流排、型別、類或驅動程式回撥(即,在 dev->pm_domain->opsdev->bus->pmdev->type->pmdev->class->pmdev->driver->pm 中定義的方法)。 PM 核心將這些回撥視為互斥的。 此外,PM 域回撥始終優先於所有其他回撥,例如,型別回撥優先於匯流排、類和驅動程式回撥。 確切地說,以下規則用於確定在給定階段執行哪個回撥

  1. 如果 dev->pm_domain 存在,則 PM 核心將選擇由 dev->pm_domain->ops 提供的回撥以執行。

  2. 否則,如果 dev->typedev->type->pm 都存在,則將選擇由 dev->type->pm 提供的回撥以執行。

  3. 否則,如果 dev->classdev->class->pm 都存在,則將選擇由 dev->class->pm 提供的回撥以執行。

  4. 否則,如果 dev->busdev->bus->pm 都存在,則將選擇由 dev->bus->pm 提供的回撥以執行。

這允許 PM 域和裝置型別在必要時覆蓋由匯流排型別或裝置類提供的回撥。

PM 域、型別、類和匯流排回撥可以反過來呼叫儲存在 dev->driver->pm 中的裝置或驅動程式特定方法,但它們不必這樣做。

如果選擇執行的子系統回撥不存在,則 PM 核心將改為執行 dev->driver->pm 集中的相應方法(如果存在)。

進入系統掛起

當系統進入凍結、待機或記憶體睡眠狀態時,階段為:preparesuspendsuspend_latesuspend_noirq

  1. prepare 階段旨在透過阻止註冊新裝置來防止競爭; 如果可以隨意註冊新子裝置,則 PM 核心永遠不會知道裝置的所有子裝置都已掛起。 [相比之下,從 PM 核心的角度來看,可以隨時取消註冊裝置。] 與其他與掛起相關的階段不同,在 prepare 階段,裝置層次結構是自上而下遍歷的。

    ->prepare 回撥方法返回後,不得在裝置下注冊任何新子裝置。 該方法還可以以某種方式準備裝置或驅動程式以進行即將到來的系統電源轉換,但不應將裝置置於低功耗狀態。 此外,如果裝置支援執行時電源管理,則 ->prepare 回撥方法不得更新其狀態,以防以後需要從執行時掛起恢復它。

    對於支援執行時電源管理的裝置,prepare 回撥的返回值可用於向 PM 核心指示它可以安全地將裝置保留在執行時掛起狀態(如果已經執行時掛起),前提是裝置的所有後代也保留在執行時掛起狀態。 也就是說,如果 prepare 回撥返回一個正數,並且該裝置的所有後代也發生這種情況,並且所有這些後代(包括裝置本身)都已執行時掛起,則 PM 核心將跳過 suspendsuspend_latesuspend_noirq 階段,以及所有這些裝置後續裝置恢復的相應階段。 在這種情況下,在 ->prepare 回撥之後將呼叫的下一個回撥是 ->complete 回撥,它完全負責根據需要將裝置置於一致狀態。

    請注意,即使停用了裝置的執行時 PM,此直接完成程式也適用; 只有執行時 PM 狀態才重要。 由此可見,如果裝置具有系統睡眠回撥但不支援執行時 PM,則其 prepare 回撥絕不能返回正值。 這是因為所有此類裝置最初都設定為停用執行時 PM 的執行時掛起狀態。

    裝置驅動程式也可以使用 DPM_FLAG_NO_DIRECT_COMPLETEDPM_FLAG_SMART_PREPARE 驅動程式電源管理標誌來控制此功能。 [通常,它們在驅動程式與裝置進行探測時設定,方法是將它們傳遞給 dev_pm_set_driver_flags() 輔助函式。] 如果設定了這些標誌中的第一個標誌,則 PM 核心不會將上述直接完成程式應用於給定的裝置,因此也不會應用於其任何祖先。 第二個標誌(設定時)會通知中間層程式碼(匯流排型別、裝置型別、PM 域、類)它應該考慮驅動程式提供的 ->prepare 回撥的返回值,並且只有在驅動程式的回撥也返回正值時,它才能從其自身的 ->prepare 回撥中返回正值。

  2. ->suspend 方法應該停止裝置以阻止其執行 I/O。 它們還可能儲存裝置暫存器並將其置於適當的低功耗狀態,具體取決於裝置所在的匯流排型別,並且它們可能會啟用喚醒事件。

    但是,對於支援執行時電源管理的裝置,子系統(尤其是匯流排型別和 PM 域)提供的 ->suspend 方法必須遵循一項關於在呼叫其驅動程式的 ->suspend 方法之前可以對裝置執行的操作的附加規則。 也就是說,如果需要,它們可以透過呼叫 pm_runtime_resume() 為它們從執行時掛起狀態恢復裝置,但在那時不得以任何其他方式更新裝置的狀態(以防驅動程式需要在其 ->suspend 方法中從執行時掛起狀態恢復裝置)。 實際上,PM 核心透過在發出 ->prepare 回撥之前呼叫 pm_runtime_get_noresume()(並在發出 ->complete 回撥之後呼叫 pm_runtime_put())來阻止子系統或驅動程式在這些時間將裝置置於執行時掛起狀態。

  3. 對於許多裝置,將掛起分為“停止裝置”和“儲存裝置狀態”階段很方便,在這些情況下,suspend_late 旨在執行後者。 它始終在停用裝置的執行時電源管理後執行。

  4. suspend_noirq 階段發生在停用 IRQ 處理程式之後,這意味著在回撥方法執行時不會呼叫驅動程式的中斷處理程式。 ->suspend_noirq 方法應儲存先前未儲存的裝置暫存器的值,並最終將裝置置於適當的低功耗狀態。

    大多數子系統和裝置驅動程式不需要實現此回撥。 但是,允許裝置共享中斷向量的匯流排型別(如 PCI)通常需要它; 否則,驅動程式可能會在掛起階段遇到錯誤,方法是在其自身的裝置設定為低功耗後響應由其他裝置生成的共享中斷。

在這些階段結束時,驅動程式應停止所有 I/O 事務(DMA、IRQ),儲存足夠的可以重新初始化或恢復先前狀態的狀態(硬體需要),並將裝置置於低功耗狀態。 在許多平臺上,它們將關閉一個或多個時鐘源; 有時它們還會關閉電源或降低電壓。 [支援執行時 PM 的驅動程式可能已經執行了部分或全部這些步驟。]

如果 device_may_wakeup() 返回 true,則應準備好裝置以生成硬體喚醒訊號,以便在系統處於睡眠狀態時觸發系統喚醒事件。 例如,enable_irq_wake() 可能會識別連線到開關或其他外部硬體的 GPIO 訊號,並且 pci_enable_wake() 對 PCI PME 訊號執行類似的操作。

如果任何這些回撥返回錯誤,則系統將不會進入所需的低功耗狀態。 而是,PM 核心將透過恢復所有已掛起的裝置來撤消其操作。

退出系統掛起

從凍結、待機或記憶體睡眠狀態恢復時,階段包括:resume_noirq, resume_early, resume, complete

  1. ->resume_noirq 回撥方法應執行驅動程式中斷處理程式呼叫前所需的任何操作。這通常意味著撤消 suspend_noirq 階段的操作。如果匯流排型別允許裝置共享中斷向量(例如 PCI),則該方法應使裝置及其驅動程式進入一種狀態,在此狀態下,驅動程式可以識別裝置是否為傳入中斷的來源(如果有),並正確處理它們。

    例如,PCI 匯流排型別的 ->pm.resume_noirq() 將裝置置於全功率狀態(PCI 術語中的 D0),並恢復裝置的標準配置暫存器。然後,它呼叫裝置驅動程式的 ->pm.resume_noirq() 方法來執行特定於裝置的操作。

  2. ->resume_early 方法應為恢復方法的執行準備裝置。這通常涉及撤消前面 suspend_late 階段的操作。

  3. ->resume 方法應使裝置恢復到其執行狀態,以便它可以執行正常的 I/O。這通常涉及撤消 suspend 階段的操作。

  4. complete 階段應撤消 prepare 階段的操作。因此,與其他恢復相關階段不同,在 complete 階段期間,裝置層次結構是從下往上遍歷的。

    但是請注意,一旦 ->resume 回調發生,就可以在裝置下方註冊新的子裝置;沒有必要等到 complete 階段執行。

    此外,如果之前的 ->prepare 回撥返回一個正數,則該裝置可能在整個系統掛起和恢復過程中一直處於執行時掛起狀態(它的 ->suspend->suspend_late->suspend_noirq->resume_noirq->resume_early->resume 回撥可能已被跳過)。在這種情況下,->complete 回撥完全負責在系統掛起後將裝置置於一致狀態(如果需要)。[例如,它可能需要為此目的為裝置排隊一個執行時恢復請求。] 要檢查是否是這種情況,->complete 回撥可以查詢裝置的 power.direct_complete 標誌。如果在執行 ->complete 回撥時設定了該標誌,則使用了 direct-complete 機制,並且可能需要特殊操作才能使裝置在之後正常工作。

在這些階段結束時,驅動程式應與掛起之前一樣具有功能:可以使用 DMA 和 IRQ 執行 I/O,並且相關的時鐘已開啟。

但是,此處的詳細資訊可能再次是特定於平臺的。例如,某些系統支援多個“執行”狀態,並且恢復結束時生效的模式可能不是掛起之前的模式。這意味著某些時鐘或電源的可用性發生了變化,這很容易影響驅動程式的工作方式。

驅動程式需要能夠處理自呼叫所有掛起方法以來已重置的硬體,例如透過完全重新初始化。這可能是最困難的部分,也是受 NDA 文件和晶片勘誤表保護最多的部分。如果自執行掛起以來硬體狀態沒有改變,那麼這是最簡單的,但只有在進入的目標系統睡眠是 suspend-to-idle 時才能保證這一點。對於其他系統睡眠狀態,情況可能並非如此(對於 ACPI 定義的系統睡眠狀態(如 S3)通常不是這種情況)。

驅動程式還必須準備好注意到裝置已在系統斷電時移除,只要物理上可行。PCMCIA、MMC、USB、Firewire、SCSI 甚至 IDE 都是常見的匯流排示例,常見的 Linux 平臺會看到這種移除。驅動程式如何注意到並處理此類移除的詳細資訊目前是特定於匯流排的,並且通常涉及一個單獨的執行緒。

這些回撥可能會返回錯誤值,但 PM 核心會忽略此類錯誤,因為它除了在系統日誌中列印它們之外,無法做任何事情。

進入休眠

休眠系統比將其置於睡眠狀態更復雜,因為它涉及建立和儲存系統映象。因此,休眠有更多的階段,並且具有不同的回撥集。這些階段始終在任務凍結且釋放了足夠的記憶體後執行。

休眠的一般過程是使所有裝置靜止(“凍結”),在一切穩定時建立系統記憶體的映象,重新啟用所有裝置(“解凍”),將映象寫入永久儲存,最後關閉系統(“斷電”)。用於完成此操作的階段是:prepare, freeze, freeze_late, freeze_noirq, thaw_noirq, thaw_early, thaw, complete, prepare, poweroff, poweroff_late, poweroff_noirq

  1. prepare 階段在上面的“進入系統掛起”部分中討論。

  2. ->freeze 方法應使裝置靜止,使其不生成 IRQ 或 DMA,並且它們可能需要儲存裝置暫存器的值。但是,裝置不必處於低功耗狀態,並且為了節省時間,最好不要這樣做。此外,不應準備裝置以生成喚醒事件。

  3. freeze_late 階段類似於前面描述的 suspend_late 階段,除了不應將裝置置於低功耗狀態,也不應允許裝置生成喚醒事件。

  4. freeze_noirq 階段類似於前面討論的 suspend_noirq 階段,除了同樣不應將裝置置於低功耗狀態,也不應允許裝置生成喚醒事件。

此時,將建立系統映象。所有裝置都應處於非活動狀態,並且記憶體的內容應保持不變,以便該映象形成系統狀態的原子快照。

  1. thaw_noirq 階段類似於前面討論的 resume_noirq 階段。主要區別在於,其方法可以假定裝置處於與 freeze_noirq 階段結束時相同的狀態。

  2. thaw_early 階段類似於上面描述的 resume_early 階段。如果需要,其方法應撤消前面 freeze_late 的操作。

  3. thaw 階段類似於前面討論的 resume 階段。其方法應使裝置恢復到執行狀態,以便可以將其用於儲存映象(如果需要)。

  4. complete 階段在上面的“離開系統掛起”部分中討論。

此時,將儲存系統映象,然後需要為即將到來的系統關閉準備裝置。這很像在將系統置於 suspend-to-idle、shallow 或 deep sleep 狀態之前掛起它們,並且這些階段是相似的。

  1. prepare 階段在上面討論。

  2. poweroff 階段類似於 suspend 階段。

  3. poweroff_late 階段類似於 suspend_late 階段。

  4. poweroff_noirq 階段類似於 suspend_noirq 階段。

->poweroff->poweroff_late->poweroff_noirq 回撥應基本上與 ->suspend->suspend_late->suspend_noirq 回撥執行相同的操作。一個顯著的區別是,它們不需要儲存裝置暫存器值,因為這些暫存器應該已經在 freezefreeze_latefreeze_noirq 階段期間儲存。此外,在許多機器上,韌體將關閉整個系統的電源,因此回撥不必將裝置置於低功耗狀態。

離開休眠

從休眠狀態恢復比從保留主記憶體內容的睡眠狀態恢復更復雜,因為它需要在將系統映象載入到記憶體中並恢復休眠前的記憶體內容,然後才能將控制權傳遞迴映象核心。

雖然原則上可以透過引導載入程式將映象載入到記憶體中並恢復休眠前的記憶體內容,但實際上這是無法完成的,因為引導載入程式不夠智慧,並且沒有建立傳遞必要資訊的協議。因此,引導載入程式會將核心的一個新例項(稱為“恢復核心”)載入到記憶體中,並以通常的方式將控制權傳遞給它。然後,恢復核心讀取系統映象,恢復休眠前的記憶體內容,並將控制權傳遞給映象核心。因此,兩個不同的核心例項都參與了從休眠狀態恢復。實際上,恢復核心可能與映象核心完全不同:不同的配置甚至不同的版本。這對裝置驅動程式及其子系統具有重要的影響。

為了能夠將系統映象載入到記憶體中,恢復核心至少需要包含裝置驅動程式的一個子集,允許它訪問包含該映象的儲存介質,但它不需要包含映象核心中存在的所有驅動程式。載入映象後,需要準備由引導核心管理的裝置,以便將控制權傳遞迴映象核心。這與建立系統映象所涉及的初始步驟非常相似,並且以相同的方式完成,使用 preparefreezefreeze_noirq 階段。但是,受這些階段影響的裝置只是那些在恢復核心中具有驅動程式的裝置;其他裝置仍將處於引導載入程式將它們留下的任何狀態。

如果無法恢復休眠前的記憶體內容,恢復核心將透過上面描述的“解凍”過程,使用 thaw_noirqthaw_earlythawcomplete 階段,然後繼續正常執行。這種情況很少發生。大多數情況下,休眠前的記憶體內容會成功恢復,並且控制權會傳遞給映象核心,然後映象核心負責使系統恢復到工作狀態。

為了實現這一點,映象核心必須恢復裝置的休眠前功能。該操作很像從睡眠狀態喚醒(保留記憶體內容),但它涉及不同的階段:restore_noirq, restore_early, restore, complete

  1. restore_noirq 階段類似於 resume_noirq 階段。

  2. restore_early 階段類似於 resume_early 階段。

  3. restore 階段類似於 resume 階段。

  4. complete 階段在上面討論。

resume[_early|_noirq] 的主要區別在於,restore[_early|_noirq] 必須假定該裝置已被引導載入程式或恢復核心訪問和重新配置。因此,裝置的狀態可能與 freezefreeze_latefreeze_noirq 階段記住的狀態不同。甚至可能需要重置裝置並完全重新初始化它。在許多情況下,這種差異並不重要,因此可以將 ->resume[_early|_noirq]->restore[_early|_norq] 方法指標設定為相同的例程。然而,使用不同的回撥指標是為了防止出現實際很重要的情況。

電源管理通知程式

有些操作無法透過上面討論的電源管理回撥來執行,因為這些回調發生得太晚或太早。為了處理這些情況,子系統和裝置驅動程式可以註冊電源管理通知程式,這些通知程式在任務凍結之前和解凍之後被呼叫。一般來說,PM 通知程式適用於執行需要使用者空間可用的操作,或者至少不會干擾使用者空間的操作。

有關詳細資訊,請參閱 掛起/休眠通知程式

裝置低功耗(掛起)狀態

裝置低功耗狀態不是標準化的。一個裝置可能只能處理“開啟”和“關閉”,而另一個裝置可能支援十幾個不同版本的“開啟”(有多少引擎處於活動狀態?),以及一種比從完全“關閉”狀態更快恢復到“開啟”狀態的狀態。

某些匯流排定義了關於不同掛起狀態含義的規則。PCI 給出了一個例子:在掛起序列完成後,非舊版 PCI 裝置可能無法執行 DMA 或發出 IRQ,並且它發出的任何喚醒事件都將透過 PME# 匯流排訊號發出。此外,還有幾個 PCI 標準裝置狀態,其中一些是可選的。

相比之下,整合的片上系統處理器通常使用 IRQ 作為喚醒事件源(因此驅動程式會呼叫 enable_irq_wake()),並且可能能夠將 DMA 完成視為喚醒事件(有時 DMA 也可以保持活動狀態,只有 CPU 和一些外圍裝置會睡眠)。

此處的一些詳細資訊可能是特定於平臺的。系統可能具有在某些睡眠狀態下可以完全活動的裝置,例如使用 DMA 重新整理的 LCD 顯示器,而系統的大部分處於輕度睡眠狀態……並且它的幀緩衝區甚至可以透過 DSP 或其他非 Linux CPU 進行更新,而 Linux 控制處理器保持空閒。

此外,所採取的具體操作可能取決於目標系統狀態。一個目標系統狀態可能允許給定裝置非常有效;另一個可能需要硬關閉,並在恢復時重新初始化。並且兩個不同的目標系統可能會以不同的方式使用相同的裝置;上述 LCD 可能會在一個產品的“待機”狀態下處於活動狀態,但使用相同 SOC 的不同產品可能會以不同的方式工作。

裝置電源管理域

有時裝置共享參考時鐘或其他電源資源。在這些情況下,通常無法單獨將裝置置於低功耗狀態。相反,可以透過關閉共享電源資源來將共享電源資源的一組裝置同時置於低功耗狀態。當然,它們也需要透過開啟共享電源資源一起置於全功率狀態。具有此屬性的一組裝置通常被稱為電源域。電源域也可以巢狀在另一個電源域中。巢狀域被稱為父域的子域。

對電源域的支援透過 struct devicepm_domain 欄位提供。此欄位是指向型別為 struct dev_pm_domain 的物件的指標,該物件在 include/linux/pm.h 中定義,提供了一組電源管理回撥,類似於在所有電源轉換期間為給定裝置執行的子系統級別和裝置驅動程式回撥,而不是相應的子系統級別回撥。具體來說,如果裝置的 pm_domain 指標不為 NULL,則將執行它所指向的物件的 ->suspend() 回撥,而不是其子系統(例如匯流排型別)的 ->suspend() 回撥,其餘回撥的情況類似。換句話說,如果為給定裝置定義了電源管理域回撥,則電源管理域回撥始終優先於裝置子系統(例如匯流排型別)提供的回撥。

對裝置電源管理域的支援僅與需要在許多不同的電源域配置中使用相同的裝置驅動程式電源管理回撥並且希望避免將對電源域的支援合併到子系統級別回撥中(例如透過修改平臺匯流排型別)的平臺相關。其他平臺無需以任何方式實現它或考慮它。

裝置可以定義為 IRQ 安全,這向 PM 核心指示可以在停用中斷的情況下呼叫它們的執行時 PM 回撥(有關更多資訊,請參閱 I/O 裝置的執行時電源管理框架)。如果 IRQ 安全裝置屬於 PM 域,則將不允許該域的執行時 PM,除非該域本身被定義為 IRQ 安全。但是,只有當其中的所有裝置都是 IRQ 安全時,才將 PM 域定義為 IRQ 安全才有意義。此外,如果一個 IRQ 安全域具有父域,則只有當父域本身也是 IRQ 安全時,才允許父域的執行時 PM,並且還有一個額外的限制,即 IRQ 安全父域的所有子域也必須是 IRQ 安全的。

執行時電源管理

許多裝置能夠在系統仍在執行時動態斷電。此功能對於未使用的裝置很有用,並且可以在執行中的系統上節省大量電量。這些裝置通常支援一系列執行時電源狀態,這些狀態可能使用諸如“關閉”、“睡眠”、“空閒”、“活動”等名稱。在某些情況下(例如 PCI),這些狀態將部分地受到裝置使用的匯流排的約束,並且通常包括也在系統睡眠狀態下使用的硬體狀態。

當某些裝置由於執行時電源管理而處於低功耗狀態時,可以啟動系統範圍的電源轉換。系統睡眠 PM 回撥應識別這種情況並做出適當的反應,但必要的措施是特定於子系統的。

在某些情況下,可以在子系統級別做出決定,而在其他情況下,可以由裝置驅動程式來決定。在某些情況下,可能希望在系統範圍的電源轉換期間將掛起的裝置保持在該狀態,但在其他情況下,必須將其暫時恢復到全功率狀態,例如,以便可以停用其系統喚醒功能。這一切都取決於硬體以及所討論的子系統和裝置驅動程式的設計。

如果在系統範圍的轉換進入睡眠狀態期間需要從執行時掛起狀態恢復裝置,可以透過從裝置的驅動程式或其子系統(例如,匯流排型別或 PM 域)的 ->suspend 回撥(或與休眠相關的轉換的 ->freeze->poweroff 回撥)呼叫 pm_runtime_resume() 來完成。但是,子系統不得在呼叫裝置驅動程式的 ->suspend 回撥(或等效回撥)之前,從其 ->prepare->suspend 回撥(或等效回撥)中更改裝置的執行時狀態。

DPM_FLAG_SMART_SUSPEND 驅動程式標誌

某些匯流排型別和 PM 域具有在其 ->suspend 回撥中提前從執行時掛起狀態恢復所有裝置的策略,但如果裝置的驅動程式可以處理執行時掛起的裝置,則可能沒有必要這樣做。驅動程式可以透過在探測時在 power.driver_flags 中設定 DPM_FLAG_SMART_SUSPEND 來指示這一點,並藉助 dev_pm_set_driver_flags() 輔助例程。

設定該標誌會導致 PM 核心和中間層程式碼(匯流排型別、PM 域等)跳過驅動程式提供的 ->suspend_late->suspend_noirq 回撥,如果裝置在系統範圍掛起的這些階段中一直保持在執行時掛起狀態(對於系統休眠的“凍結”和“斷電”部分也類似)。[否則,對於同一個裝置,同一個驅動程式回撥可能會連續執行兩次,這通常是無效的。] 如果裝置的中間層系統範圍 PM 回撥存在,則它們負責跳過這些驅動程式回撥;否則,PM 核心將跳過它們。子系統回撥例程可以透過測試 dev_pm_skip_suspend() 輔助函式的返回值來確定它們是否需要跳過驅動程式回撥。

此外,如果設定了 DPM_FLAG_SMART_SUSPEND,如果在整個之前的“凍結”轉換過程中裝置一直處於執行時掛起狀態,則在休眠時會跳過驅動程式的 ->thaw_noirq->thaw_early 回撥。同樣,如果裝置的中間層回撥存在,則它們負責執行此操作,否則 PM 核心會處理它。

DPM_FLAG_MAY_SKIP_RESUME 驅動程式標誌

正如 I/O 裝置的執行時電源管理框架 中解釋的那樣,在從睡眠狀態恢復系統範圍的過程中,最容易將裝置置於全功率狀態。[有關此特定問題的更多資訊以及有關裝置執行時電源管理框架的更多資訊,請參閱該文件。] 但是,通常希望在系統轉換到工作狀態後將裝置保持在掛起狀態,尤其是如果這些裝置在之前的系統範圍掛起(或類似)轉換之前已處於執行時掛起狀態。

為此,裝置驅動程式可以使用 DPM_FLAG_MAY_SKIP_RESUME 標誌向 PM 核心和中間層程式碼指示,如果裝置可以在系統範圍的 PM 轉換到工作狀態後保持在掛起狀態,則它們允許跳過它們的“noirq”和“early”恢復回撥。是否是這種情況通常取決於給定系統掛起恢復週期之前裝置的狀態以及正在進行的系統轉換的型別。特別是,與休眠相關的“解凍”和“恢復”轉換根本不受 DPM_FLAG_MAY_SKIP_RESUME 的影響。[無論標誌設定如何,都會在“恢復”轉換期間發出所有回撥,並且在“解凍”轉換期間是否跳過任何驅動程式回撥取決於是否設定了 DPM_FLAG_SMART_SUSPEND 標誌(參見 上面)。此外,如果其任何子裝置都將恢復到全功率,則不允許裝置保持在執行時掛起狀態。]

在結合 power.may_skip_resume 狀態位(由 PM 核心在掛起型別轉換的“掛起”階段設定)考慮時,會考慮 DPM_FLAG_MAY_SKIP_RESUME 標誌。如果在後續的系統恢復轉換期間,驅動程式或中間層有理由阻止跳過驅動程式的“noirq”和“early”恢復回撥,則應在其 ->suspend->suspend_late->suspend_noirq 回撥中清除 power.may_skip_resume。[請注意,設定 DPM_FLAG_SMART_SUSPEND 的驅動程式需要在其 ->suspend 回撥中清除 power.may_skip_resume,以防跳過其他兩個。]

設定 power.may_skip_resume 狀態位以及 DPM_FLAG_MAY_SKIP_RESUME 標誌是必要的,但通常不足以跳過驅動程式的“noirq”和“early”恢復回撥。是否應該跳過它們可以透過評估 dev_pm_skip_resume() 輔助函式來確定。

如果該函式返回 true,則應跳過驅動程式的“noirq”和“early”恢復回撥,並且 PM 核心會將裝置的執行時 PM 狀態設定為“掛起”。否則,如果該裝置在之前的系統範圍掛起轉換期間執行時掛起,並且設定了其 DPM_FLAG_SMART_SUSPEND,則 PM 核心會將它的執行時 PM 狀態設定為“活動”。[因此,不設定 DPM_FLAG_SMART_SUSPEND 的驅動程式不應期望其裝置的執行時 PM 狀態在系統範圍恢復型別轉換期間從 PM 核心從“掛起”更改為“活動”。]

如果裝置未設定 DPM_FLAG_MAY_SKIP_RESUME 標誌,但設定了 DPM_FLAG_SMART_SUSPEND 標誌,並且驅動程式的“late”和“noirq”掛起回撥被跳過,那麼它的系統範圍的“noirq”和“early”恢復回撥(如果存在)會像往常一樣被呼叫,並且裝置的執行時PM狀態會被PM核心設定為“active”,然後才會啟用其執行時PM。在這種情況下,驅動程式必須能夠處理其系統範圍的恢復回撥與其 ->runtime_suspend 回撥(中間沒有 ->runtime_resume 和系統範圍的掛起回撥)背靠背呼叫的情況,並且裝置最終狀態必須反映該情況下的“active”執行時PM狀態。[請注意,如果驅動程式的 ->suspend_late 回撥指標指向與其 ->runtime_suspend 回撥指標相同的函式,並且其 ->resume_early 回撥指標指向與其 ->runtime_resume 回撥指標相同的函式,而驅動程式的其他系統範圍的掛起-恢復回撥都不存在,例如,這根本不是問題。]

同樣地,如果裝置設定了 DPM_FLAG_MAY_SKIP_RESUME,則其驅動程式的系統範圍的“noirq”和“early”恢復回撥可能會被跳過,而其“late”和“noirq”掛起回撥可能已經被執行(原則上,無論是否設定了 DPM_FLAG_SMART_SUSPEND)。 在這種情況下,驅動程式需要能夠處理其 ->runtime_resume 回撥與其“late”和“noirq”掛起回撥背靠背呼叫的情況。[例如,如果驅動程式同時設定了 DPM_FLAG_SMART_SUSPENDDPM_FLAG_MAY_SKIP_RESUME,並且對執行時PM和系統範圍的掛起/恢復使用相同的掛起/恢復回撥函式對,那就不是問題。]