设备电源管理基础

版权:

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

版权:

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

版权:

© 2016 英特尔公司

作者:

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(LAN 唤醒)功能已通过 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() 初始化;默认情况下允许运行时电源管理。

用户空间可以通过向设备的 power/control sysfs 文件写入 “on” 或 “auto” 来调整此设置。写入 “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 阶段,以及所有这些设备的后续设备恢复的相应阶段。在这种情况下,->complete 回调将是 ->prepare 回调之后调用的下一个回调,并且完全负责将设备置于适当的一致状态。

    请注意,即使该设备已禁用运行时 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_noirqresume_earlyresumecomplete

  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 回调时设置了该标志,则使用了直接完成机制,并且可能需要采取特殊操作才能使设备在之后正常工作。

在这些阶段结束时,驱动程序应与挂起之前一样正常工作:可以使用 DMA 和 IRQ 执行 I/O 操作,并且相关的时钟已打开。

但是,这里的详细信息可能再次特定于平台。例如,某些系统支持多个“运行”状态,并且在恢复结束时生效的模式可能不是挂起之前的模式。这意味着某些时钟或电源的可用性发生了变化,这很容易影响驱动程序的工作方式。

驱动程序需要能够处理自调用所有挂起方法以来已重置的硬件,例如通过完全重新初始化。这可能是最困难的部分,也是受 NDA 文档和芯片勘误表保护最多的部分。如果自执行挂起以来硬件状态没有更改,则最简单,但这只有在输入的目标系统睡眠状态是挂起-空闲状态时才能保证。对于其他系统睡眠状态,情况可能并非如此(对于 ACPI 定义的系统睡眠状态(如 S3)通常不是这样)。

驱动程序还必须准备好注意到在系统断电时已移除设备,只要这在物理上是可能的。PCMCIA、MMC、USB、Firewire、SCSI 甚至 IDE 是常见 Linux 平台将看到此类移除的总线示例。驱动程序如何注意和处理此类移除的详细信息目前特定于总线,并且通常涉及一个单独的线程。

这些回调可能会返回错误值,但 PM 核心会忽略此类错误,因为它除了在系统日志中打印它们之外无能为力。

进入休眠

使系统休眠比将其置于睡眠状态更复杂,因为它涉及创建和保存系统映像。因此,休眠有更多阶段,并且有一组不同的回调。这些阶段总是在任务被冻结并且释放了足够的内存之后运行。

休眠的总体过程是使所有设备静止(“冻结”),在一切都稳定时创建系统内存映像,重新激活所有设备(“解冻”),将映像写入永久存储,最后关闭系统(“断电”)。用于完成此操作的阶段是:preparefreezefreeze_latefreeze_noirqthaw_noirqthaw_earlythawcompletepreparepoweroffpoweroff_latepoweroff_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 阶段在上面的“离开系统挂起”部分中讨论。

此时,系统映像将被保存,然后需要为即将到来的系统关机准备设备。这很像在将系统置于挂起至空闲、浅层或深度睡眠状态之前将其挂起,并且阶段类似。

  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_noirqrestore_earlyrestorecomplete

  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 安全的,并且 IRQ 安全父域的所有子域也必须是 IRQ 安全的,才允许父域的运行时 PM。

运行时电源管理

许多设备能够在系统仍在运行时动态断电。此功能对于未使用的设备很有用,并且可以为正在运行的系统提供显著的节能效果。这些设备通常支持一系列运行时电源状态,可能会使用诸如“关闭”、“睡眠”、“空闲”、“活动”等名称。这些状态在某些情况下(例如 PCI)会受到设备使用的总线的限制,并且通常包括系统睡眠状态中也使用的硬件状态。

当一些设备由于运行时电源管理而处于低功耗状态时,可以启动系统范围的电源转换。系统睡眠 PM 回调应识别这种情况并做出适当的反应,但必要的操作特定于子系统。

在某些情况下,可以在子系统级别做出决定,而在其他情况下,则可能由设备驱动程序来决定。在某些情况下,可能希望在系统范围的电源转换期间将挂起的设备保留在该状态,但在其他情况下,必须临时将设备恢复到全功率状态,例如,以便禁用其系统唤醒功能。这完全取决于硬件以及子系统和设备驱动程序的设计。

如果需要在系统范围转换到睡眠状态期间从运行时挂起恢复设备,可以通过从设备驱动程序或其子系统(例如,总线类型或 PM 域)的 ->suspend 回调(或与休眠相关的转换的 ->freeze->poweroff 回调)中调用 pm_runtime_resume() 来完成。但是,子系统不得从其 ->prepare->suspend 回调(或等效项)调用设备驱动程序的 ->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 标志(请参阅上文)。此外,如果其任何子设备将恢复到全功率,则不允许设备保持在运行时挂起状态。]

DPM_FLAG_MAY_SKIP_RESUME 标志与电源管理核心在挂起类型转换的“挂起”阶段设置的 power.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”恢复回调,并且电源管理核心会将设备的运行时电源管理状态设置为“suspended”。否则,如果设备在之前的系统范围的挂起转换期间运行时挂起,并且其 DPM_FLAG_SMART_SUSPEND 已设置,则电源管理核心会将设备的运行时电源管理状态设置为“active”。[因此,未设置 DPM_FLAG_SMART_SUSPEND 的驱动程序不应期望其设备的运行时电源管理状态在系统范围的恢复类型转换期间由电源管理核心从“suspended”更改为“active”。]

如果未为设备设置 DPM_FLAG_MAY_SKIP_RESUME 标志,但设置了 DPM_FLAG_SMART_SUSPEND,并且驱动程序的“late”和“noirq”挂起回调被跳过,则其系统范围的“noirq”和“early”恢复回调(如果存在)将照常调用,并且电源管理核心会在启用设备的运行时电源管理之前将其运行时电源管理状态设置为“active”。在这种情况下,驱动程序必须准备好处理其系统范围的恢复回调与其 ->runtime_suspend 回调背靠背地调用(没有中间的 ->runtime_resume 和系统范围的挂起回调),并且设备最终状态必须反映这种情况下的“active”运行时电源管理状态。[请注意,如果驱动程序的 ->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,并使用相同的挂起/恢复回调函数对用于运行时电源管理和系统范围的挂起/恢复,则这不是问题。]