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