设备电源管理基础¶
- 版权:
© 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_domain
的 ops
成员指向,或者由 struct bus_type
、struct device_type 和 struct class
的 pm
成员指向。 它们主要与为平台和总线(如 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->ops
、dev->bus->pm
、dev->type->pm
、dev->class->pm
或 dev->driver->pm
中定义的方法)。 PM 核心将这些回调视为互斥的。 此外,PM 域回调始终优先于所有其他回调,例如,类型回调优先于总线、类和驱动程序回调。 确切地说,以下规则用于确定在给定阶段执行哪个回调
如果
dev->pm_domain
存在,则 PM 核心将选择由dev->pm_domain->ops
提供的回调以执行。否则,如果
dev->type
和dev->type->pm
都存在,则将选择由dev->type->pm
提供的回调以执行。否则,如果
dev->class
和dev->class->pm
都存在,则将选择由dev->class->pm
提供的回调以执行。否则,如果
dev->bus
和dev->bus->pm
都存在,则将选择由dev->bus->pm
提供的回调以执行。
这允许 PM 域和设备类型在必要时覆盖由总线类型或设备类提供的回调。
PM 域、类型、类和总线回调可以反过来调用存储在 dev->driver->pm
中的设备或驱动程序特定方法,但它们不必这样做。
如果选择执行的子系统回调不存在,则 PM 核心将改为执行 dev->driver->pm
集中的相应方法(如果存在)。
进入系统挂起¶
当系统进入冻结、待机或内存睡眠状态时,阶段为:prepare
、suspend
、suspend_late
、suspend_noirq
。
prepare
阶段旨在通过阻止注册新设备来防止竞争; 如果可以随意注册新子设备,则 PM 核心永远不会知道设备的所有子设备都已挂起。 [相比之下,从 PM 核心的角度来看,可以随时取消注册设备。] 与其他与挂起相关的阶段不同,在prepare
阶段,设备层次结构是自上而下遍历的。在
->prepare
回调方法返回后,不得在设备下注册任何新子设备。 该方法还可以以某种方式准备设备或驱动程序以进行即将到来的系统电源转换,但不应将设备置于低功耗状态。 此外,如果设备支持运行时电源管理,则->prepare
回调方法不得更新其状态,以防以后需要从运行时挂起恢复它。对于支持运行时电源管理的设备,prepare 回调的返回值可用于向 PM 核心指示它可以安全地将设备保留在运行时挂起状态(如果已经运行时挂起),前提是设备的所有后代也保留在运行时挂起状态。 也就是说,如果 prepare 回调返回一个正数,并且该设备的所有后代也发生这种情况,并且所有这些后代(包括设备本身)都已运行时挂起,则 PM 核心将跳过
suspend
、suspend_late
和suspend_noirq
阶段,以及所有这些设备后续设备恢复的相应阶段。 在这种情况下,在->prepare
回调之后将调用的下一个回调是->complete
回调,它完全负责根据需要将设备置于一致状态。请注意,即使禁用了设备的运行时 PM,此直接完成程序也适用; 只有运行时 PM 状态才重要。 由此可见,如果设备具有系统睡眠回调但不支持运行时 PM,则其 prepare 回调绝不能返回正值。 这是因为所有此类设备最初都设置为禁用运行时 PM 的运行时挂起状态。
设备驱动程序也可以使用
DPM_FLAG_NO_DIRECT_COMPLETE
和DPM_FLAG_SMART_PREPARE
驱动程序电源管理标志来控制此功能。 [通常,它们在驱动程序与设备进行探测时设置,方法是将它们传递给dev_pm_set_driver_flags()
辅助函数。] 如果设置了这些标志中的第一个标志,则 PM 核心不会将上述直接完成程序应用于给定的设备,因此也不会应用于其任何祖先。 第二个标志(设置时)会通知中间层代码(总线类型、设备类型、PM 域、类)它应该考虑驱动程序提供的->prepare
回调的返回值,并且只有在驱动程序的回调也返回正值时,它才能从其自身的->prepare
回调中返回正值。
->suspend
方法应该停止设备以阻止其执行 I/O。 它们还可能保存设备寄存器并将其置于适当的低功耗状态,具体取决于设备所在的总线类型,并且它们可能会启用唤醒事件。但是,对于支持运行时电源管理的设备,子系统(尤其是总线类型和 PM 域)提供的
->suspend
方法必须遵循一项关于在调用其驱动程序的->suspend
方法之前可以对设备执行的操作的附加规则。 也就是说,如果需要,它们可以通过调用pm_runtime_resume()
为它们从运行时挂起状态恢复设备,但在那时不得以任何其他方式更新设备的状态(以防驱动程序需要在其->suspend
方法中从运行时挂起状态恢复设备)。 实际上,PM 核心通过在发出->prepare
回调之前调用pm_runtime_get_noresume()
(并在发出->complete
回调之后调用pm_runtime_put()
)来阻止子系统或驱动程序在这些时间将设备置于运行时挂起状态。对于许多设备,将挂起分为“停止设备”和“保存设备状态”阶段很方便,在这些情况下,
suspend_late
旨在执行后者。 它始终在禁用设备的运行时电源管理后执行。
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
。
->resume_noirq
回调方法应执行驱动程序中断处理程序调用前所需的任何操作。这通常意味着撤消suspend_noirq
阶段的操作。如果总线类型允许设备共享中断向量(例如 PCI),则该方法应使设备及其驱动程序进入一种状态,在此状态下,驱动程序可以识别设备是否为传入中断的来源(如果有),并正确处理它们。例如,PCI 总线类型的
->pm.resume_noirq()
将设备置于全功率状态(PCI 术语中的 D0),并恢复设备的标准配置寄存器。然后,它调用设备驱动程序的->pm.resume_noirq()
方法来执行特定于设备的操作。
->resume_early
方法应为恢复方法的执行准备设备。这通常涉及撤消前面suspend_late
阶段的操作。
->resume
方法应使设备恢复到其运行状态,以便它可以执行正常的 I/O。这通常涉及撤消suspend
阶段的操作。
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
。
prepare
阶段在上面的“进入系统挂起”部分中讨论。
->freeze
方法应使设备静止,使其不生成 IRQ 或 DMA,并且它们可能需要保存设备寄存器的值。但是,设备不必处于低功耗状态,并且为了节省时间,最好不要这样做。此外,不应准备设备以生成唤醒事件。
freeze_late
阶段类似于前面描述的suspend_late
阶段,除了不应将设备置于低功耗状态,也不应允许设备生成唤醒事件。
freeze_noirq
阶段类似于前面讨论的suspend_noirq
阶段,除了同样不应将设备置于低功耗状态,也不应允许设备生成唤醒事件。
此时,将创建系统镜像。所有设备都应处于非活动状态,并且内存的内容应保持不变,以便该镜像形成系统状态的原子快照。
thaw_noirq
阶段类似于前面讨论的resume_noirq
阶段。主要区别在于,其方法可以假定设备处于与freeze_noirq
阶段结束时相同的状态。
thaw_early
阶段类似于上面描述的resume_early
阶段。如果需要,其方法应撤消前面freeze_late
的操作。
thaw
阶段类似于前面讨论的resume
阶段。其方法应使设备恢复到运行状态,以便可以将其用于保存镜像(如果需要)。
complete
阶段在上面的“离开系统挂起”部分中讨论。
此时,将保存系统镜像,然后需要为即将到来的系统关闭准备设备。这很像在将系统置于 suspend-to-idle、shallow 或 deep sleep 状态之前挂起它们,并且这些阶段是相似的。
prepare
阶段在上面讨论。
poweroff
阶段类似于suspend
阶段。
poweroff_late
阶段类似于suspend_late
阶段。
poweroff_noirq
阶段类似于suspend_noirq
阶段。
->poweroff
、->poweroff_late
和 ->poweroff_noirq
回调应基本上与 ->suspend
、->suspend_late
和 ->suspend_noirq
回调执行相同的操作。一个显着的区别是,它们不需要存储设备寄存器值,因为这些寄存器应该已经在 freeze
、freeze_late
或 freeze_noirq
阶段期间存储。此外,在许多机器上,固件将关闭整个系统的电源,因此回调不必将设备置于低功耗状态。
离开休眠¶
从休眠状态恢复比从保留主内存内容的睡眠状态恢复更复杂,因为它需要在将系统镜像加载到内存中并恢复休眠前的内存内容,然后才能将控制权传递回镜像内核。
虽然原则上可以通过引导加载程序将镜像加载到内存中并恢复休眠前的内存内容,但实际上这是无法完成的,因为引导加载程序不够智能,并且没有建立传递必要信息的协议。因此,引导加载程序会将内核的一个新实例(称为“恢复内核”)加载到内存中,并以通常的方式将控制权传递给它。然后,恢复内核读取系统镜像,恢复休眠前的内存内容,并将控制权传递给镜像内核。因此,两个不同的内核实例都参与了从休眠状态恢复。实际上,恢复内核可能与镜像内核完全不同:不同的配置甚至不同的版本。这对设备驱动程序及其子系统具有重要的影响。
为了能够将系统镜像加载到内存中,恢复内核至少需要包含设备驱动程序的一个子集,允许它访问包含该镜像的存储介质,但它不需要包含镜像内核中存在的所有驱动程序。加载镜像后,需要准备由引导内核管理的设备,以便将控制权传递回镜像内核。这与创建系统镜像所涉及的初始步骤非常相似,并且以相同的方式完成,使用 prepare
、freeze
和 freeze_noirq
阶段。但是,受这些阶段影响的设备只是那些在恢复内核中具有驱动程序的设备;其他设备仍将处于引导加载程序将它们留下的任何状态。
如果无法恢复休眠前的内存内容,恢复内核将通过上面描述的“解冻”过程,使用 thaw_noirq
、thaw_early
、thaw
和 complete
阶段,然后继续正常运行。这种情况很少发生。大多数情况下,休眠前的内存内容会成功恢复,并且控制权会传递给镜像内核,然后镜像内核负责使系统恢复到工作状态。
为了实现这一点,镜像内核必须恢复设备的休眠前功能。该操作很像从睡眠状态唤醒(保留内存内容),但它涉及不同的阶段:restore_noirq
, restore_early
, restore
, complete
。
restore_noirq
阶段类似于resume_noirq
阶段。
restore_early
阶段类似于resume_early
阶段。
restore
阶段类似于resume
阶段。
complete
阶段在上面讨论。
与 resume[_early|_noirq]
的主要区别在于,restore[_early|_noirq]
必须假定该设备已被引导加载程序或恢复内核访问和重新配置。因此,设备的状态可能与 freeze
、freeze_late
和 freeze_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 device
的 pm_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_SUSPEND
和 DPM_FLAG_MAY_SKIP_RESUME
,并且对运行时PM和系统范围的挂起/恢复使用相同的挂起/恢复回调函数对,那就不是问题。]