PCI電源管理

版權所有 (c) 2010 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.

PCI電源管理相關的概念和Linux核心介面的概述。基於Patrick Mochel <mochel@transmeta.com>(及其他人員)的先前工作。

本文件僅涵蓋特定於PCI裝置的電源管理方面。有關核心的裝置電源管理相關介面的一般描述,請參閱裝置電源管理基礎I/O裝置的執行時電源管理框架

1. PCI電源管理的硬體和平臺支援

1.1. 原生和基於平臺的電源管理

通常,電源管理是一種透過將裝置置於功耗較低的狀態(低功耗狀態)來節省能源的功能,但代價是降低了功能或效能。

通常,裝置在未充分利用或完全不活動時會被置於低功耗狀態。但是,當需要再次使用該裝置時,必須將其恢復到“完全功能”狀態(全功率狀態)。當裝置需要處理一些資料時,或者由於需要裝置處於活動狀態的外部事件(可能是由裝置本身發出訊號)而發生這種情況。

PCI裝置可以透過兩種方式置於低功耗狀態:使用PCI匯流排電源管理介面規範引入的裝置功能,或者藉助平臺韌體(例如ACPI BIOS)。在第一種方法中,即在下文中稱為原生PCI電源管理(原生PCI PM),裝置電源狀態的改變是由於將特定值寫入其標準配置暫存器之一而導致的。第二種方法要求平臺韌體提供核心可用於更改裝置電源狀態的特殊方法。

支援原生PCI PM的裝置通常可以生成稱為電源管理事件(PME)的喚醒訊號,以告知核心需要裝置處於活動狀態的外部事件。接收到PME後,核心應將傳送它的裝置置於全功率狀態。但是,PCI匯流排電源管理介面規範沒有定義任何將PME從裝置傳遞到CPU和作業系統核心的標準方法。假設平臺韌體將執行此任務,因此,即使已設定PCI裝置以生成PME,也可能需要準備平臺韌體以通知CPU來自裝置的PME(例如,透過生成中斷)。

反過來,如果平臺韌體提供的方法用於更改裝置的電源狀態,則平臺通常還提供一種方法來準備裝置以生成喚醒訊號。在這種情況下,通常還需要使用原生PCI PM機制準備裝置以生成PME,因為平臺提供的方法依賴於此。

因此,在許多情況下,必須同時使用原生和基於平臺的電源管理機制才能獲得所需的結果。

1.2. 原生PCI電源管理

PCI匯流排電源管理介面規範(PCI PM規範)是在PCI 2.1和PCI 2.2規範之間引入的。它定義了一個用於執行與電源管理相關的各種操作的標準介面。

對於傳統的PCI裝置,PCI PM規範的實現是可選的,但對於PCI Express裝置是強制性的。如果裝置支援PCI PM規範,則其PCI配置空間中有一個8位元組的電源管理功能欄位。此欄位用於描述和控制與原生PCI電源管理相關的標準功能。

PCI PM規範為裝置(D0-D3)和匯流排(B0-B3)定義了4種執行狀態。數字越高,裝置或匯流排在該狀態下消耗的功率就越少。但是,數字越高,裝置或匯流排返回到全功率狀態(分別為D0或B0)的延遲就越長。

規範定義了D3狀態的兩種變體。第一個是D3hot,被稱為軟體可訪問的D3,因為裝置可以被程式設計為進入該狀態。第二個是D3cold,是PCI裝置在移除電源電壓(Vcc)時所處的狀態。無法程式設計PCI裝置進入D3cold,儘管可能有一個可程式設計介面,用於將裝置所在的匯流排置於移除總線上所有裝置Vcc的狀態。

但是,Linux核心在編寫本文件時不支援PCI匯流排電源管理,因此本文件不涵蓋它。

請注意,每個PCI裝置都可以處於全功率狀態(D0)或D3cold,無論它是否實現了PCI PM規範。除此之外,如果裝置實現了PCI PM規範,則它必須支援D3hot以及D0。對D1和D2電源狀態的支援是可選的。

支援PCI PM規範的PCI裝置可以被程式設計為進入任何受支援的低功耗狀態(D3cold除外)。在D1-D3hot中,裝置的標準配置暫存器必須可供軟體訪問(即,裝置需要響應PCI配置訪問),儘管其I/O和記憶體空間已被停用。這允許以程式設計方式將裝置置於D0。因此,核心可以在D0和受支援的低功耗狀態(D3cold除外)之間來回切換裝置,並且裝置可能經歷的可能的電源狀態轉換如下

當前狀態 | 新狀態

D0 | D1, D2, D3

D1 | D2, D3

D2 | D3

D1, D2, D3 | D0

當為裝置提供電源電壓(即恢復電源)時,會發生從D3cold到D0的轉換。在這種情況下,裝置將以完整的通電覆位序列返回到D0,並且硬體會將通電預設值恢復到裝置,就像初始通電一樣。

支援PCI PM規範的PCI裝置可以被程式設計為在任何電源狀態(D0-D3)下生成PME,但它們不需要能夠從所有受支援的電源狀態生成PME。特別是,從D3cold生成PME的能力是可選的,並且取決於是否存在額外的電壓(3.3Vaux),允許裝置保持足夠活躍以生成喚醒訊號。

1.3. ACPI裝置電源管理

平臺韌體對PCI裝置電源管理的支援是系統特定的。但是,如果所討論的系統符合高階配置和電源介面(ACPI)規範,例如大多數基於x86的系統,則它應實現ACPI標準定義的裝置電源管理介面。

為此,ACPI BIOS提供了稱為“控制方法”的特殊函式,核心可以執行這些函式來執行特定任務,例如將裝置置於低功耗狀態。這些控制方法使用稱為ACPI機器語言(AML)的特殊位元組碼語言進行編碼,並存儲在機器的BIOS中。核心從BIOS載入它們,並使用AML直譯器根據需要執行它們,該直譯器將AML位元組碼轉換為計算和記憶體或I/O空間訪問。這樣,從理論上講,BIOS編寫者可以為核心提供一種以系統特定的方式執行取決於系統設計的操作的方法。

ACPI控制方法可以分為與任何特定裝置無關的全域性控制方法,以及必須為每個應該在平臺幫助下處理的裝置單獨定義的裝置控制方法。這意味著,特別是,ACPI裝置控制方法只能用於處理BIOS編寫者提前知道的裝置。用於裝置電源管理的ACPI方法屬於該類別。

ACPI規範假定裝置可以處於標記為D0,D1,D2和D3的四種電源狀態之一,這些狀態大致對應於原生PCI PM D0-D3狀態(儘管ACPI沒有考慮D3hot和D3cold之間的差異)。此外,對於裝置的每種電源狀態,都有一組電源資源,必須啟用這些電源資源才能將裝置置於該狀態。這些電源資源透過其自身的控制方法_ON和_OFF進行控制(即啟用或停用),必須分別為每個電源資源定義這些方法。

為了將裝置置於ACPI電源狀態Dx(其中x是0到3之間的數字,包括0和3),核心應(1)使用其_ON控制方法啟用此狀態下裝置所需的電源資源,以及(2)執行為裝置定義的_PSx控制方法。除此之外,如果要將裝置置於低功耗狀態(D1-D3)並且要從該狀態生成喚醒訊號,則必須在_PSx之前執行為其定義的_DSW(或_PSW,由ACPI 3.0替換為_DSW)控制方法。目標電源狀態下裝置不需要且任何其他裝置不再需要的電源資源應停用(透過執行其_OFF控制方法)。如果裝置的當前電源狀態為D3,則只能透過這種方式將其置於D0。

但是,通常在系統範圍內的轉換到睡眠狀態或返回到工作狀態期間會更改裝置的電源狀態。ACPI定義了四個系統睡眠狀態S1,S2,S3和S4,並將系統工作狀態表示為S0。通常,目標系統睡眠(或工作)狀態決定了裝置可以置於的最高電源(最低數字)狀態,核心應該透過執行裝置的_SxD控制方法來獲得此資訊(其中x是0到4之間的數字,包括0和4)。如果要求裝置從目標睡眠狀態喚醒系統,則可以將其置於的最低功率(最高數字)狀態也由系統的目標狀態確定。然後,核心應使用裝置的_SxW控制方法來獲取該狀態的編號。它還應該使用裝置的_PRW控制方法來了解需要啟用哪些電源資源才能使裝置能夠生成喚醒訊號。

1.4. 喚醒訊號

由PCI裝置生成的喚醒訊號,無論是作為原生PCI PME,還是在將裝置置於低功耗狀態之前執行_DSW(或_PSW)ACPI控制方法的結果,都必須被捕獲並進行適當的處理。如果在系統處於工作狀態(ACPI S0)時傳送它們,則應將其轉換為中斷,以便核心可以將生成它們的裝置置於全功率狀態,並處理觸發它們的事件。反過來,如果在系統睡眠時傳送它們,則應使系統的核心邏輯觸發喚醒。

在基於ACPI的系統上,由傳統PCI裝置傳送的喚醒訊號被轉換為ACPI通用事件(GPE),這些事件是來自系統核心邏輯的硬體訊號,用於響應需要執行的各種事件而生成。每個GPE都與一個或多個潛在有趣事件的來源相關聯。特別是,GPE可能與能夠發出喚醒訊號的PCI裝置相關聯。有關GPE和事件源之間連線的資訊記錄在系統的ACPI BIOS中,核心可以從中讀取該資訊。

如果系統ACPI BIOS已知的PCI裝置發出喚醒訊號,則將觸發與其關聯的GPE(如果存在)。與PCI橋關聯的GPE也可能會響應來自橋下某個裝置的喚醒訊號而被觸發(根橋也是如此),例如,來自系統ACPI BIOS未知的裝置的本機PCI PME可能會以這種方式處理。

當系統處於睡眠狀態時(即,當它處於ACPI S1-S4狀態之一時),可能會觸發GPE,在這種情況下,系統喚醒由其核心邏輯啟動(稍後可能會識別出作為訊號源導致系統喚醒的裝置)。在這種情況下使用的GPE稱為喚醒GPE。

但是,通常在系統處於工作狀態(ACPI S0)時也會觸發GPE,在這種情況下,系統的核心邏輯會生成系統控制中斷(SCI)以通知核心該事件。然後,SCI處理程式識別出導致生成中斷的GPE,這反過來又允許核心識別事件的來源(可能是發出喚醒訊號的PCI裝置)。用於通知核心在系統處於工作狀態時發生的事件的GPE稱為執行時GPE。

不幸的是,在非基於ACPI的系統上,沒有處理傳統PCI裝置傳送的喚醒訊號的標準方法,但是對於PCI Express裝置有一種標準方法。即,PCI Express基本規範引入了一種將本機PCI PME轉換為由根埠生成的中斷的本機機制。對於傳統的PCI裝置,本機PME是帶外的,因此它們是單獨路由的,並且不需要透過橋(原則上它們可以直接路由到系統的核心邏輯),但是對於PCI Express裝置,它們是帶內訊息,必須透過PCI Express層次結構,包括從裝置到根複合體的路徑上的根埠。因此,可以引入一種機制,根埠每當收到來自其下方裝置的PME訊息時,都會生成中斷。然後,傳送PME訊息的PCI Express請求者ID會記錄在根埠的一個配置暫存器中,中斷處理程式可以從中讀取該ID以識別裝置。[由與根複合體整合的PCI Express端點發送的PME訊息不會透過根埠,而是會導致根複合體事件收集器(如果存在)生成中斷。]

原則上,本機PCI Express PME訊號也可以在基於ACPI的系統上與GPE一起使用,但是要使用它,核心必須要求系統的ACPI BIOS釋放對根埠配置暫存器的控制。但是,ACPI BIOS不需要允許核心控制這些暫存器,如果它不允許,核心不得修改其內容。在這種情況下,核心當然不能使用本機PCI Express PME訊號。

2. PCI子系統和裝置電源管理

2.1. 裝置電源管理回撥

PCI子系統以多種方式參與PCI裝置的電源管理。首先,它在裝置電源管理核心(PM核心)和PCI裝置驅動程式之間提供了一箇中間程式碼層。具體來說,PCI子系統的struct bus_type物件pci_bus_type的pm欄位指向一個struct dev_pm_ops物件pci_dev_pm_ops,其中包含指向多個裝置電源管理回撥的指標

const struct dev_pm_ops pci_dev_pm_ops = {
      .prepare = pci_pm_prepare,
      .complete = pci_pm_complete,
      .suspend = pci_pm_suspend,
      .resume = pci_pm_resume,
      .freeze = pci_pm_freeze,
      .thaw = pci_pm_thaw,
      .poweroff = pci_pm_poweroff,
      .restore = pci_pm_restore,
      .suspend_noirq = pci_pm_suspend_noirq,
      .resume_noirq = pci_pm_resume_noirq,
      .freeze_noirq = pci_pm_freeze_noirq,
      .thaw_noirq = pci_pm_thaw_noirq,
      .poweroff_noirq = pci_pm_poweroff_noirq,
      .restore_noirq = pci_pm_restore_noirq,
      .runtime_suspend = pci_pm_runtime_suspend,
      .runtime_resume = pci_pm_runtime_resume,
      .runtime_idle = pci_pm_runtime_idle,
};

這些回撥由PM核心在與裝置電源管理相關的各種情況下執行,它們反過來又執行PCI裝置驅動程式提供的電源管理回撥。它們還執行一些涉及PCI裝置標準配置暫存器的電源管理操作,這些暫存器裝置驅動程式不需要了解或關心。

表示PCI裝置的結構struct pci_dev包含這些回撥操作的多個欄位

struct pci_dev {
      ...
      pci_power_t     current_state;  /* Current operating state. */
      int             pm_cap;         /* PM capability offset in the
                                         configuration space */
      unsigned int    pme_support:5;  /* Bitmask of states from which PME#
                                         can be generated */
      unsigned int    pme_poll:1;     /* Poll device's PME status bit */
      unsigned int    d1_support:1;   /* Low power state D1 is supported */
      unsigned int    d2_support:1;   /* Low power state D2 is supported */
      unsigned int    no_d1d2:1;      /* D1 and D2 are forbidden */
      unsigned int    wakeup_prepared:1;  /* Device prepared for wake up */
      unsigned int    d3hot_delay;    /* D3hot->D0 transition time in ms */
      ...
};

它們還間接使用嵌入在struct pci_dev中的struct device的一些欄位。

2.2. 裝置初始化

PCI子系統與裝置電源管理相關的第一個任務是準備裝置進行電源管理,並初始化用於此目的的struct pci_dev的欄位。這發生在drivers/pci/中定義的兩個函式中,pci_pm_init()和pci_acpi_setup()。

第一個函式檢查裝置是否支援本機PCI PM,如果是這種情況,則其配置空間中電源管理功能結構的偏移量儲存在裝置struct pci_dev物件的pm_cap欄位中。接下來,該函式檢查裝置支援哪些PCI低功耗狀態,以及裝置可以從哪些低功耗狀態生成本機PCI PME。裝置的struct pci_dev的電源管理欄位和嵌入其中的struct device會相應地更新,並且裝置生成PME的功能將被停用。

第二個函式檢查是否可以在平臺韌體(例如ACPI BIOS)的幫助下準備裝置以發出喚醒訊號。如果是這種情況,則該函式將更新嵌入在裝置struct pci_dev中的struct device中的喚醒欄位,並使用韌體提供的方法來阻止裝置發出喚醒訊號。

此時,裝置已準備好進行電源管理。但是,對於無驅動程式的裝置,此功能僅限於在系統範圍內轉換到睡眠狀態和返回到工作狀態期間執行的一些基本操作。

2.3. 執行時裝置電源管理

PCI子系統在PCI裝置的執行時電源管理中起著至關重要的作用。為此,它使用I/O裝置的執行時電源管理框架中描述的通用執行時電源管理(執行時PM)框架。即,它提供子系統級別的回撥

pci_pm_runtime_suspend()
pci_pm_runtime_resume()
pci_pm_runtime_idle()

這些回撥由核心執行時PM例程執行。它還實現了處理來自低功耗狀態的PCI裝置的執行時喚醒訊號所需的整個機制,在編寫本文件時,該機制既適用於第1節中描述的本機PCI Express PME訊號,也適用於基於ACPI GPE的喚醒訊號。

首先,藉助pm_schedule_suspend()或pm_runtime_suspend()將PCI裝置置於低功耗狀態或掛起狀態,對於PCI裝置,它們會呼叫pci_pm_runtime_suspend()來完成實際工作。為此,裝置的驅動程式必須提供一個pm->runtime_suspend()回撥(參見下文),該回調由pci_pm_runtime_suspend()作為第一個操作執行。如果驅動程式的回撥成功返回,則儲存裝置的標準配置暫存器,準備裝置生成喚醒訊號,最後,將其置於目標低功耗狀態。

將裝置置於的低功耗狀態是可以發出喚醒訊號的最低功率(最高數字)狀態。發出喚醒訊號的確切方法取決於系統,並且由PCI子系統根據報告的裝置和平臺韌體的功能確定。為了準備裝置以發出喚醒訊號並將其置於選定的低功耗狀態,PCI子系統可以使用平臺韌體以及裝置的原生PCI PM功能(如果支援)。

期望裝置驅動程式的pm->runtime_suspend()回撥不會嘗試準備裝置發出喚醒訊號或將其置於低功耗狀態。驅動程式應將這些任務留給具有執行它們所需的所有資訊的PCI子系統。

藉助pm_request_resume()或pm_runtime_resume()將掛起的裝置恢復到“活動”狀態,對於PCI裝置,它們都會呼叫pci_pm_runtime_resume()。同樣,只有在裝置的驅動程式提供pm->runtime_resume()回撥(參見下文)時,這才有效。但是,在執行驅動程式的回撥之前,pci_pm_runtime_resume()會將裝置恢復到全功率狀態,阻止它在該狀態下發出喚醒訊號,並恢復其標準配置暫存器。因此,驅動程式的回撥無需擔心裝置恢復的PCI特定方面。

請注意,通常可以在兩種不同的情況下呼叫pci_pm_runtime_resume()。首先,可以在裝置驅動程式的請求下呼叫它,例如,如果有一些資料需要處理。其次,可以作為來自裝置本身的喚醒訊號的結果呼叫它(這有時稱為“遠端喚醒”)。當然,為此,喚醒訊號以第1節中描述的一種方式處理,並在識別出源裝置後最終轉換為PCI子系統的通知。

由pm_runtime_idle()和pm_request_idle()為PCI裝置呼叫的pci_pm_runtime_idle()函式會執行裝置驅動程式的pm->runtime_idle()回撥(如果已定義),並且如果該回調沒有返回錯誤程式碼(或者根本不存在),則藉助pm_runtime_suspend()掛起裝置。有時,pci_pm_runtime_idle()由PM核心自動呼叫(例如,它在裝置剛剛恢復後立即呼叫),在這種情況下,期望在有意義的情況下掛起裝置。但是,通常PCI子系統實際上並不知道裝置是否真的可以掛起,因此它透過執行其pm->runtime_idle()回撥來讓裝置的驅動程式決定。

2.4. 系統範圍的電源轉換

存在幾種不同型別的系統範圍的電源轉換,在裝置電源管理基礎中進行了描述。每種轉換都需要以特定的方式處理裝置,並且PM核心為此目的執行子系統級別的電源管理回撥。這些回撥分階段執行,以便每個階段都涉及為屬於給定子系統的每個裝置執行相同的子系統級別回撥,然後再開始下一個階段。在凍結任務後,這些階段始終執行。

2.4.1. 系統掛起

當系統進入將保留記憶體內容的睡眠狀態時,例如ACPI睡眠狀態S1-S3之一,這些階段是

準備,掛起,掛起_noirq。

在這些階段中,分別使用以下PCI匯流排型別回撥

pci_pm_prepare()
pci_pm_suspend()
pci_pm_suspend_noirq()

pci_pm_prepare()例程首先借助pm_runtime_resume()將裝置置於“完全功能”狀態。然後,如果定義了裝置的驅動程式的pm->prepare()回撥(即,如果驅動程式的struct dev_pm_ops物件存在並且該物件中的prepare指標有效),則執行它。

pci_pm_suspend()例程首先檢查裝置的驅動程式是否實現了傳統的PCI掛起例程(參見第3節),如果是這種情況,則執行驅動程式的傳統掛起回撥(如果存在),並返回其結果。接下來,如果裝置的驅動程式未提供struct dev_pm_ops物件(其中包含指向驅動程式回撥的指標),則呼叫pci_pm_default_suspend(),該函式僅關閉裝置的匯流排主控能力並執行pcibios_disable_device()以停用它,除非該裝置是橋(此例程會忽略PCI橋)。接下來,執行裝置驅動程式的pm->suspend()回撥(如果已定義),如果其失敗,則返回其結果。最後,如果需要,呼叫pci_fixup_device()以應用與裝置相關的硬體掛起怪異。

請注意,對於PCI裝置,掛起階段是非同步執行的,因此,對於任何以已知方式不相互依賴的一對PCI裝置(即,從根橋到葉裝置的裝置樹中的任何路徑都包含它們),都可以並行執行pci_pm_suspend()回撥。

在呼叫suspend_device_irqs()之後執行pci_pm_suspend_noirq()例程,這意味著在執行此例程時不會呼叫裝置驅動程式的中斷處理程式。它首先檢查裝置的驅動程式是否實現了傳統的PCI掛起例程(第3節),如果是這種情況,則呼叫傳統的後期掛起例程並返回其結果(如果驅動程式的回撥尚未執行,則儲存裝置的標準配置暫存器)。其次,如果裝置驅動程式的struct dev_pm_ops物件不存在,則儲存裝置的標準配置暫存器,並且該例程返回成功。否則,執行裝置驅動程式的pm->suspend_noirq()回撥(如果存在),如果其失敗,則返回其結果。接下來,如果尚未儲存裝置的標準配置暫存器(之前執行的裝置驅動程式的回撥之一可能會執行此操作),則pci_pm_suspend_noirq()會儲存它們,準備裝置發出喚醒訊號(如果需要),並將其置於低功耗狀態。

將裝置置於的低功耗狀態是可以發出喚醒訊號的最低功率(最高數字)狀態,而系統處於目標睡眠狀態。就像上面描述的執行時PM情況一樣,發出喚醒訊號的機制取決於系統,並且由PCI子系統確定,PCI子系統還負責準備裝置以從系統的目標睡眠狀態發出適當的喚醒訊號。

通常不希望PCI裝置驅動程式(未實現傳統的電源管理回撥)準備裝置以發出喚醒訊號或將其置於低功耗狀態。但是,如果驅動程式的掛起回撥之一(pm->suspend()或pm->suspend_noirq())儲存了裝置的標準配置暫存器,則pci_pm_suspend_noirq()將假定該裝置已準備好發出喚醒訊號並由驅動程式置於低功耗狀態(然後假定驅動程式已為此目的使用了PCI子系統提供的輔助函式)。不鼓勵PCI裝置驅動程式這樣做,但是在某些罕見情況下,在驅動程式中這樣做可能是最佳方法。

2.4.2. 系統恢復

當系統正在經歷從已保留記憶體內容的睡眠狀態(例如ACPI睡眠狀態S1-S3之一)到工作狀態(ACPI S0)的轉換時,這些階段是

恢復_noirq,恢復,完成。

在這些階段中,分別執行以下PCI匯流排型別回撥

pci_pm_resume_noirq()
pci_pm_resume()
pci_pm_complete()

pci_pm_resume_noirq()例程首先將裝置置於全功率狀態,恢復其標準配置暫存器,並應用與裝置相關的早期恢復硬體怪異(如果需要)。這是無條件完成的,無論裝置的驅動程式是否實現了傳統的PCI電源管理回撥(這樣,所有PCI裝置都處於全功率狀態,並且在恢復期間首次呼叫其中斷處理程式時,已恢復其標準配置暫存器,這允許核心避免驅動程式的裝置仍處於掛起狀態而處理共享中斷時出現問題)。如果裝置的驅動程式實現了傳統的PCI電源管理回撥(參見第3節),則執行傳統的早期恢復回撥並返回其結果。否則,執行裝置驅動程式的pm->resume_noirq()回撥(如果已定義),並返回其結果。

pci_pm_resume()例程首先檢查是否已恢復裝置的標準配置暫存器,如果沒有恢復,則恢復它們(這僅在失敗的掛起期間的錯誤路徑中才是必需的)。接下來,應用與裝置相關的恢復硬體怪異(如果需要),並且如果裝置的驅動程式實現了傳統的PCI電源管理回撥(參見第3節),則執行驅動程式的傳統恢復回撥並返回其結果。否則,裝置的喚醒訊號機制將被阻止,並且將執行其驅動程式的pm->resume()回撥(如果已定義)(然後將返回回撥的結果)。

像上面描述的掛起階段一樣,對於PCI裝置,恢復階段是非同步執行的,這意味著如果兩個PCI裝置以已知方式不相互依賴,則可以並行地為它們執行pci_pm_resume()例程。

pci_pm_complete()例程僅執行裝置驅動程式的pm->complete()回撥(如果已定義)。

2.4.3. 系統休眠

系統休眠比系統掛起複雜,因為它需要建立一個系統映像並將其寫入永續性儲存介質。該映像是原子建立的,並且在此之前,所有裝置都被靜止或凍結。

在釋放足夠的記憶體後(在編寫本文件時,映像建立需要至少50%的系統RAM可用),裝置的凍結在以下三個階段中執行

準備,凍結,凍結_noirq

這些階段對應於PCI匯流排型別回撥

pci_pm_prepare()
pci_pm_freeze()
pci_pm_freeze_noirq()

這意味著準備階段與系統掛起的準備階段完全相同。但是,其他兩個階段是不同的。

pci_pm_freeze()例程與pci_pm_suspend()非常相似,但是它執行裝置驅動程式的pm->freeze()回撥(如果已定義),而不是pm->suspend(),並且它不應用與掛起相關的硬體怪異。對於以已知方式不相互依賴的不同PCI裝置,它是非同步執行的。

反過來,pci_pm_freeze_noirq()例程類似於pci_pm_suspend_noirq(),但是它呼叫裝置驅動程式的pm->freeze_noirq()例程而不是pm->suspend_noirq()。它也不會嘗試準備裝置以發出喚醒訊號並將其置於低功耗狀態。不過,如果驅動程式的回撥之一尚未儲存裝置的標準配置暫存器,則會儲存它們。

建立映像後,必須儲存它。但是,此時所有裝置都已凍結,並且它們無法處理I/O,而它們的I/O處理能力顯然是映像儲存所必需的。因此,必須將它們恢復到完全功能狀態,這將在以下階段中完成

解凍_noirq,解凍,完成

分別使用以下PCI匯流排型別回撥

pci_pm_thaw_noirq()
pci_pm_thaw()
pci_pm_complete()

分別地。

其中第一個 pci_pm_thaw_noirq() 與 pci_pm_resume_noirq() 類似。它將裝置置於完全供電狀態並恢復其標準配置暫存器。它還會執行裝置驅動程式的 pm->thaw_noirq() 回撥(如果已定義),而不是 pm->resume_noirq()。

pci_pm_thaw() 例程與 pci_pm_resume() 類似,但它執行裝置驅動程式的 pm->thaw() 回撥而不是 pm->resume()。它針對已知方式中不相互依賴的不同 PCI 裝置非同步執行。

完整階段與系統恢復的階段相同。

儲存映象後,需要在系統進入目標睡眠狀態(基於 ACPI 系統的 ACPI S4)之前斷開裝置的電源。這分三個階段完成:

prepare, poweroff, poweroff_noirq

其中準備階段與系統掛起完全相同。其他兩個階段分別類似於掛起和掛起_noirq 階段。它們對應的 PCI 子系統級別回撥是

pci_pm_poweroff()
pci_pm_poweroff_noirq()

類似於 pci_pm_suspend() 和 pci_pm_suspend_noirq(),儘管它們不嘗試儲存裝置的標準配置暫存器。

2.4.4. 系統恢復

系統恢復需要將休眠映象載入到記憶體中,並在恢復休眠前的系統活動之前恢復休眠前的記憶體內容。

裝置電源管理基礎知識中所述,休眠映象由核心的一個新例項(稱為引導核心)載入到記憶體中,該核心又由引導載入程式以通常的方式載入和執行。引導核心載入映象後,需要將其自身的程式碼和資料替換為儲存在映象中的“休眠”核心(稱為映象核心)的程式碼和資料。為此,所有裝置都像在休眠期間建立映象之前一樣被凍結,處於

準備,凍結,凍結_noirq

上述階段。但是,受這些階段影響的裝置只是那些在引導核心中具有驅動程式的裝置;其他裝置將仍然處於引導載入程式留下的任何狀態。

如果恢復休眠前的記憶體內容失敗,引導核心將執行上述“解凍”過程,使用 thaw_noirq、thaw 和 complete 階段(這隻會影響在引導核心中具有驅動程式的裝置),然後繼續正常執行。

如果成功恢復了休眠前的記憶體內容(這是通常的情況),則控制權將傳遞給映象核心,然後映象核心負責使系統恢復到工作狀態。為了實現這一點,它必須恢復裝置的休眠前功能,這很像從記憶體睡眠狀態喚醒,儘管它涉及不同的階段:

restore_noirq, restore, complete

其中前兩個分別類似於上述的 resume_noirq 和 resume 階段,並對應於以下 PCI 子系統回撥:

pci_pm_restore_noirq()
pci_pm_restore()

這些回撥的工作方式類似於 pci_pm_resume_noirq() 和 pci_pm_resume(),但它們執行裝置驅動程式的 pm->restore_noirq() 和 pm->restore() 回撥(如果可用)。

完成階段的執行方式與系統恢復期間完全相同。

3. PCI 裝置驅動程式和電源管理

3.1. 電源管理回撥

PCI 裝置驅動程式透過提供由上述 PCI 子系統的電源管理例程執行的回撥,以及透過控制其裝置的執行時電源管理來參與電源管理。

在撰寫本文時,有兩種方法可以為 PCI 裝置驅動程式定義電源管理回撥,建議的方法是基於使用裝置電源管理基礎知識中描述的 dev_pm_ops 結構,以及“舊式”方法,其中使用了來自struct pci_driver的 .suspend() 和 .resume() 回撥。但是,舊式方法不允許定義執行時電源管理回撥,並且實際上不適合任何新的驅動程式。因此,本文件未涵蓋它(請參閱原始碼以瞭解更多資訊)。

建議所有 PCI 裝置驅動程式定義一個struct dev_pm_ops物件,其中包含指向電源管理 (PM) 回撥的指標,這些回撥將由 PCI 子系統的 PM 例程在各種情況下執行。指向驅動程式的struct dev_pm_ops物件的指標必須分配給其struct pci_driver物件中的 driver.pm 欄位。一旦發生這種情況,struct pci_driver中的“舊式”PM 回撥將被忽略(即使它們不是 NULL)。

struct dev_pm_ops中的 PM 回撥不是強制性的,如果未定義它們(即struct dev_pm_ops的相應欄位未設定),PCI 子系統將以簡化的預設方式處理裝置。但是,如果定義了它們,則期望它們的行為如以下小節所述。

3.1.1. prepare()

prepare() 回撥在系統掛起、休眠期間(當即將建立休眠映象時)、儲存休眠映象後斷電以及系統恢復期間(當休眠映象剛載入到記憶體中時)執行。

僅當驅動程式的裝置具有通常可以隨時註冊的子裝置時,此回撥才是必需的。在這種情況下,prepare() 回撥的作用是防止裝置的任何新子裝置在執行 resume_noirq()、thaw_noirq() 或 restore_noirq() 回撥之一之前被註冊。

除此之外,prepare() 回撥可能會執行一些操作來準備掛起裝置,儘管它不應該分配記憶體(如果需要額外的記憶體來掛起裝置,則必須提前預先分配,例如在掛起/休眠通知程式中描述的掛起/休眠通知程式中)。

3.1.2. suspend()

僅在系統掛起期間,在為系統中的所有裝置執行 prepare() 回撥之後,才會執行 suspend() 回撥。

預期此回撥會使裝置靜止,並準備將其置於 PCI 子系統的低功耗狀態。不需要(實際上甚至不建議)PCI 驅動程式的 suspend() 回撥儲存裝置的標準配置暫存器,準備喚醒系統或將其置於低功耗狀態。所有這些操作都可以由 PCI 子系統很好地處理,而無需驅動程式的參與。

但是,在某些罕見的情況下,在 PCI 驅動程式中執行這些操作很方便。然後,應使用pci_save_state()pci_prepare_to_sleep()pci_set_power_state()來分別儲存裝置的標準配置暫存器,準備系統喚醒(如果必要)並將其置於低功耗狀態。此外,如果驅動程式呼叫pci_save_state(),則 PCI 子系統將不會為其裝置執行pci_prepare_to_sleep()pci_set_power_state(),因此驅動程式負責以適當的方式處理裝置。

在執行 suspend() 回撥時,可以呼叫驅動程式的中斷處理程式來處理來自裝置的中斷,因此依賴於驅動程式處理中斷的能力的所有與掛起相關的操作都應在此回撥中執行。

3.1.3. suspend_noirq()

僅在系統掛起期間,在為系統中的所有裝置執行 suspend() 回撥之後,以及在 PM 核心停用裝置中斷之後,才會執行 suspend_noirq() 回撥。

suspend_noirq() 和 suspend() 之間的區別在於,在 suspend_noirq() 執行時,不會呼叫驅動程式的中斷處理程式。因此,suspend_noirq() 可以執行如果在 suspend() 中執行會導致競爭條件的操作。

3.1.4. freeze()

freeze() 回撥是特定於休眠的,並且在兩種情況下執行:在休眠期間,在為所有裝置執行 prepare() 回撥之後,以準備建立系統映象;以及在恢復期間,在從永續性儲存載入系統映象到記憶體中,並且為所有裝置執行 prepare() 回撥之後。

此回撥的作用類似於上述的 suspend() 回撥的作用。事實上,只有在驅動程式承擔將裝置置於低功耗狀態的責任時,它們才需要不同。

在這些情況下,freeze() 回撥不應準備裝置系統喚醒或將其置於低功耗狀態。不過,它或 freeze_noirq() 應使用pci_save_state()儲存裝置的標準配置暫存器。

3.1.5. freeze_noirq()

freeze_noirq() 回撥是特定於休眠的。它在休眠期間執行,在為所有裝置執行 prepare() 和 freeze() 回撥之後,以準備建立系統映象;以及在恢復期間執行,在從記憶體中載入系統映象之後,並且為所有裝置執行 prepare() 和 freeze() 回撥之後。它始終在 PM 核心停用裝置中斷之後執行。

此回撥的作用類似於上述的 suspend_noirq() 回撥的作用,並且很少需要定義 freeze_noirq()。

freeze_noirq() 和 freeze() 之間的區別類似於 suspend_noirq() 和 suspend() 之間的區別。

3.1.6. poweroff()

poweroff() 回撥是特定於休眠的。在將休眠映象儲存到永續性儲存之後,系統即將斷電時執行。在呼叫 poweroff() 之前,將為所有裝置執行 prepare() 回撥。

此回撥的作用類似於上述的 suspend() 和 freeze() 回撥的作用,儘管它不需要儲存裝置暫存器的內容。特別是,如果驅動程式想要自己將裝置置於低功耗狀態,而不是允許 PCI 子系統執行此操作,則 poweroff() 回撥應使用pci_prepare_to_sleep()pci_set_power_state()來準備裝置系統喚醒並將其置於低功耗狀態,但不需要儲存裝置的標準配置暫存器。

3.1.7. poweroff_noirq()

poweroff_noirq() 回撥是特定於休眠的。在為系統中的所有裝置執行 poweroff() 回撥之後執行。

此回撥的作用類似於上述的 suspend_noirq() 和 freeze_noirq() 回撥的作用,但它不需要儲存裝置暫存器的內容。

poweroff_noirq() 和 poweroff() 之間的區別類似於 suspend_noirq() 和 suspend() 之間的區別。

3.1.8. resume_noirq()

僅在系統恢復期間,在 PM 核心啟用非引導 CPU 之後,才會執行 resume_noirq() 回撥。在 resume_noirq() 執行時,不會呼叫驅動程式的中斷處理程式,因此此回撥可以執行如果由 resume() 執行可能會導致競爭條件的操作。

由於 PCI 子系統無條件地將所有裝置置於系統恢復的 resume_noirq 階段中的完全供電狀態,並恢復其標準配置暫存器,因此通常不需要 resume_noirq()。通常,它僅應用於執行如果由 resume() 執行會導致競爭條件的操作。

3.1.9. resume()

僅在系統恢復期間,在為系統中的所有裝置執行 resume_noirq() 回撥之後,並且在 PM 核心啟用裝置中斷之後,才會執行 resume() 回撥。

此回撥負責恢復裝置的掛起前配置並使其恢復到完全正常執行狀態。在 resume() 返回後,裝置應該能夠以通常的方式處理 I/O。

3.1.10. thaw_noirq()

thaw_noirq() 回撥是特定於休眠的。在建立系統映象並且 PM 核心啟用非引導 CPU 之後,在休眠的 thaw_noirq 階段執行。如果在系統恢復期間載入休眠映象失敗,也可能會執行此回撥(然後在啟用非引導 CPU 之後執行)。在 thaw_noirq() 執行時,不會呼叫驅動程式的中斷處理程式。

此回撥的作用類似於 resume_noirq() 的作用。這兩個回撥之間的區別在於,thaw_noirq() 在 freeze() 和 freeze_noirq() 之後執行,因此通常不需要修改裝置暫存器的內容。

3.1.11. thaw()

thaw() 回撥是特定於休眠的。在為系統中的所有裝置執行 thaw_noirq() 回撥之後,並且在 PM 核心啟用裝置中斷之後,執行此回撥。

此回撥負責恢復裝置的預凍結配置,以便在 thaw() 返回後,它將以通常的方式工作。

3.1.12. restore_noirq()

restore_noirq() 回撥是特定於休眠的。它在休眠的 restore_noirq 階段執行,此時引導核心已將控制權傳遞給映象核心,並且映象核心的 PM 核心已啟用非引導 CPU。

此回撥類似於 resume_noirq(),但有一個例外,即它不能對裝置以前的狀態做出任何假設,即使已知 BIOS(或通常是平臺韌體)可以在掛起恢復週期中保留該狀態。

對於絕大多數 PCI 裝置驅動程式,resume_noirq() 和 restore_noirq() 之間沒有區別。

3.1.13. restore()

restore() 回撥是特定於休眠的。在為系統中的所有裝置執行 restore_noirq() 回撥之後,並且在 PM 核心啟用裝置驅動程式的中斷處理程式以供呼叫之後,執行此回撥。

此回撥類似於 resume(),就像 restore_noirq() 類似於 resume_noirq() 一樣。因此,restore_noirq() 和 restore() 之間的區別類似於 resume_noirq() 和 resume() 之間的區別。

對於絕大多數 PCI 裝置驅動程式,resume() 和 restore() 之間沒有區別。

3.1.14. complete()

在以下情況下執行 complete() 回撥:

  • 在系統恢復期間,在為所有裝置執行 resume() 回撥之後,

  • 在休眠期間,在儲存系統映象之前,在為所有裝置執行 thaw() 回撥之後,

  • 在系統恢復期間,當系統返回到其休眠前狀態時,在為所有裝置執行 restore() 回撥之後。

如果在將休眠映象載入到記憶體中失敗(在這種情況下,在為引導核心中具有驅動程式的所有裝置執行 thaw() 回撥之後執行),也可能會執行此回撥。

此回撥是完全可選的,但如果 prepare() 回撥執行需要反轉的操作,則可能需要此回撥。

3.1.15. runtime_suspend()

runtime_suspend() 回撥特定於裝置執行時電源管理(執行時 PM)。當裝置即將掛起(即,靜止並置於低功耗狀態)時,PM 核心的執行時 PM 框架會執行此回撥。

此回撥負責凍結裝置並準備將其置於低功耗狀態,但它必須允許 PCI 子系統執行掛起裝置所需的所有 PCI 特定操作。

3.1.16. runtime_resume()

runtime_resume() 回撥特定於裝置執行時 PM。當裝置即將恢復(即,置於完全供電狀態並程式設計為正常處理 I/O)時,PM 核心的執行時 PM 框架會執行此回撥。

此回撥負責在 PCI 子系統將裝置置於完全供電狀態後,恢復裝置的正常功能。在 runtime_resume() 返回後,裝置應該能夠以通常的方式處理 I/O。

3.1.17. runtime_idle()

runtime_idle() 回撥特定於裝置執行時 PM。每當根據 PM 核心的資訊可能需要掛起裝置時,PM 核心的執行時 PM 框架都會執行此回撥。特別是,如果在裝置恢復是由於虛假事件而發生的情況下,會在 runtime_resume() 返回後立即自動執行此回撥。

此回撥是可選的,但如果未實現此回撥或此回撥返回 0,則 PCI 子系統將為裝置呼叫 pm_runtime_suspend(),這反過來會導致驅動程式的 runtime_suspend() 回撥被執行。

3.1.18. 將多個回撥指標指向一個例程

儘管原則上可以將前述各小節中描述的每個回撥定義為單獨的函式,但通常可以將struct dev_pm_ops的兩個或更多成員指向同一個例程。有一些方便的宏可用於此目的。

DEFINE_SIMPLE_DEV_PM_OPS() 宣告一個struct dev_pm_ops物件,其中一個掛起例程由 .suspend()、.freeze() 和 .poweroff() 成員指向,一個恢復例程由 .resume()、.thaw() 和 .restore() 成員指向。此struct dev_pm_ops中的其他函式指標未設定。

DEFINE_RUNTIME_DEV_PM_OPS() 類似於 DEFINE_SIMPLE_DEV_PM_OPS(),但它還會將 .runtime_resume() 指標設定為 pm_runtime_force_resume(),並將 .runtime_suspend() 指標設定為 pm_runtime_force_suspend()。

SYSTEM_SLEEP_PM_OPS() 可用於struct dev_pm_ops的宣告中,以指示一個掛起例程將由 .suspend()、.freeze() 和 .poweroff() 成員指向,一個恢復例程將由 .resume()、.thaw() 和 .restore() 成員指向。

3.1.19. 電源管理的驅動程式標誌

PM 核心允許裝置驅動程式設定標誌,這些標誌會影響核心本身和包括 PCI 匯流排型別在內的中間層程式碼對裝置電源管理的處理。應使用 dev_pm_set_driver_flags() 函式在驅動程式探測時設定一次這些標誌,此後不應直接更新它們。

DPM_FLAG_NO_DIRECT_COMPLETE 標誌會阻止 PM 核心使用直接完成機制,如果裝置在系統掛起開始時處於執行時掛起狀態,則可以跳過裝置掛起/恢復回撥。這也會影響裝置的所有祖先,因此只有在絕對必要時才應使用此標誌。

DPM_FLAG_SMART_PREPARE 標誌會導致 PCI 匯流排型別僅當裝置的驅動程式提供的 ->prepare 回撥返回正值時,才從 pci_pm_prepare() 返回正值。這允許驅動程式動態地選擇不使用直接完成機制(而設定 DPM_FLAG_NO_DIRECT_COMPLETE 意味著永久退出)。

DPM_FLAG_SMART_SUSPEND 標誌告訴 PCI 匯流排型別,從驅動程式的角度來看,在系統掛起期間可以安全地將裝置保留在執行時掛起狀態。這會導致 pci_pm_suspend()、pci_pm_freeze() 和 pci_pm_poweroff() 避免從執行時掛起恢復裝置,除非有特定於 PCI 的原因需要這樣做。此外,如果裝置在正在進行的系統範圍轉換的“延遲”階段保持執行時掛起狀態,則會導致 pci_pm_suspend_late/noirq() 和 pci_pm_poweroff_late/noirq() 提前返回。此外,如果裝置在 pci_pm_resume_noirq() 或 pci_pm_restore_noirq() 中處於執行時掛起狀態,則其執行時 PM 狀態將更改為“活動”(因為它將轉到 D0)。

設定 DPM_FLAG_MAY_SKIP_RESUME 標誌意味著驅動程式允許在系統範圍轉換到工作狀態之後可以將裝置保留在掛起狀態時跳過其“noirq”和“early”恢復回撥。PM 核心會考慮此標誌以及裝置的 power.may_skip_resume 狀態位,該狀態位由 pci_pm_suspend_noirq() 在某些情況下設定。如果 PM 核心確定應跳過驅動程式的“noirq”和“early”恢復回撥,則 dev_pm_skip_resume() 輔助函式將返回“true”,這將導致 pci_pm_resume_noirq() 和 pci_pm_resume_early() 提前返回,而無需接觸裝置和執行驅動程式回撥。

3.2. 裝置執行時電源管理

除了提供裝置電源管理回撥之外,PCI 裝置驅動程式還負責控制其裝置的執行時電源管理(執行時 PM)。

PCI 裝置執行時 PM 是可選的,但建議 PCI 裝置驅動程式至少在存在一種可靠的方法來驗證裝置是否未使用的情況下實現它(例如,當網路電纜與乙太網介面卡斷開連線或沒有裝置連線到 USB 控制器時)。

為了支援 PCI 執行時 PM,驅動程式首先需要實現 runtime_suspend() 和 runtime_resume() 回撥。它可能還需要實現 runtime_idle() 回撥,以防止裝置在 runtime_resume() 回撥返回後立即再次掛起(或者,runtime_suspend() 回撥必須檢查裝置是否真的應該掛起,如果不是這種情況,則返回 -EAGAIN)。

PCI 核心預設情況下啟用 PCI 裝置的執行時 PM。PCI 裝置驅動程式無需啟用它,也不應嘗試這樣做。但是,pm_runtime_init() 會阻止它,該函式執行 pm_runtime_forbid() 輔助函式。此外,在執行裝置驅動程式提供的探測回撥之前,local_pci_probe() 會增加每個 PCI 裝置的執行時 PM 使用計數器。

如果 PCI 驅動程式實現了執行時 PM 回撥並打算使用 PM 核心和 PCI 子系統提供的執行時 PM 框架,則需要在其探測回撥函式中遞減裝置的執行時 PM 使用計數器。如果它不這樣做,則該裝置的計數器將始終不同於零,並且它永遠不會在執行時掛起。最簡單的方法是呼叫 pm_runtime_put_noidle(),但如果驅動程式想要立即安排自動掛起,例如,它可以為此目的呼叫 pm_runtime_put_autosuspend()。通常,它只需要呼叫一個函式,該函式從其探測例程中遞減裝置使用計數器,以使執行時 PM 適用於該裝置。

重要的是要記住,驅動程式的 runtime_suspend() 回撥可能會在使用計數器遞減後立即執行,因為使用者空間可能已經透過 sysfs 導致 pm_runtime_allow() 輔助函式解除阻塞裝置執行時 PM 執行,因此驅動程式必須準備好應對這種情況。

但是,驅動程式本身不應呼叫 pm_runtime_allow()。相反,它應該讓使用者空間或某些特定於平臺的程式碼來執行此操作(使用者空間可以透過 sysfs 執行此操作,如上所述),但它必須準備好在呼叫 pm_runtime_allow() 後立即正確處理裝置的執行時 PM(這可能隨時發生,甚至在載入驅動程式之前)。

當驅動程式的刪除回撥執行時,它必須平衡在探測時遞減裝置的執行時 PM 使用計數器。因此,如果它已在其探測回撥中遞減了計數器,則必須在其刪除回撥中執行 pm_runtime_get_noresume()。[由於核心會在執行驅動程式的刪除回撥之前執行裝置的執行時恢復並增加裝置的使用計數器,因此裝置的執行時 PM 在刪除執行期間實際上是被停用的,並且所有增加裝置使用計數器的執行時 PM 輔助函式實際上等效於 pm_runtime_get_noresume()。]

執行時 PM 框架的工作原理是處理掛起或恢復裝置或檢查裝置是否空閒的請求(在這種情況下,隨後請求掛起它們是合理的)。這些請求由放置到電源管理工作佇列 pm_wq 中的工作項表示。儘管 PM 核心自動排隊電源管理請求的情況很少(例如,在處理恢復裝置的請求後,PM 核心會自動排隊一個請求以檢查裝置是否空閒),但裝置驅動程式通常負責為其裝置排隊電源管理請求。為此,它們應使用 PM 核心提供的執行時 PM 輔助函式,這些函式在I/O 裝置的執行時電源管理框架中討論。

也可以同步掛起和恢復裝置,而無需將請求放置到 pm_wq 中。在大多數情況下,這也是由使用 PM 核心為此目的提供的輔助函式的驅動程式完成的。

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

4. 資源

PCI 本地匯流排規範,修訂版 3.0

PCI 匯流排電源管理介面規範,修訂版 1.2

高階配置和電源介面 (ACPI) 規範,修訂版 3.0b

PCI Express 基礎規範,修訂版 2.0

裝置電源管理基礎知識

I/O 裝置的執行時電源管理框架