设备电源管理基础 【ChatGPT】

发布时间 2023-12-11 22:47:52作者: 摩斯电码

设备电源管理基础

版权

设备电源管理两种模型

  • 系统休眠模型

    驱动程序可以在系统进入“挂起”(也称为“挂起到RAM”)或“休眠”(对于带有磁盘的系统)等系统范围的低功耗状态时,将设备置于低功耗状态。

    设备、总线和类驱动程序通过实现各种特定角色的挂起和恢复方法来协作,以清洁地关闭硬件和软件子系统,然后重新激活它们而不丢失数据。

    一些驱动程序可以管理硬件唤醒事件,这些事件使系统退出低功耗状态。这个特性可以通过相关的 /sys/devices/.../power/wakeup 文件(对于以太网驱动程序,也可以使用 ethtool 使用的 ioctl 接口)来启用或禁用;启用它可能会消耗一些功耗,但可以让整个系统更频繁地进入低功耗状态。

  • 运行时电源管理模型

    在系统运行时,设备也可以独立于其他电源管理活动而置于低功耗状态。但是,设备通常不是彼此独立的(例如,除非所有子设备都已挂起,否则无法挂起父设备)。此外,根据设备所在的总线类型,可能需要对设备执行一些特定于总线的操作。

    在运行时将设备置于低功耗状态可能需要在系统范围的电源转换(挂起或休眠)期间进行特殊处理。

    因此,不仅设备驱动程序本身,还有适当的子系统(总线类型、设备类型或设备类)驱动程序和 PM 核心都参与了运行时电源管理。与系统休眠电源管理情况类似,它们需要通过实现各种特定角色的挂起和恢复方法来协作,以便清洁地关闭和重新激活硬件而不丢失数据或服务。

关于低功耗状态,除了它们非常依赖于系统和设备之外,很难有更多的说法。此外,如果足够多的设备在运行时进入低功耗状态,其效果可能与进入某种系统范围的低功耗状态(系统休眠)非常相似...而且存在协同效应,因此使用运行时电源管理的几个驱动程序可能会使系统进入更深层次的节能选项。

大多数挂起的设备将停止所有I/O操作:不再进行DMA或IRQ操作(除了唤醒事件),不再读取或写入数据,并且不再接受来自上游驱动程序的请求。但是,不同的总线或平台可能具有不同的要求。

硬件唤醒事件的示例包括来自实时时钟的闹钟、网络的唤醒LAN数据包、键盘或鼠标活动,以及介质的插入或拔出(适用于PCMCIA、MMC/SD、USB等)。

进入系统休眠状态的接口

  • 提供了用于子系统(总线类型、设备类型、设备类)和设备驱动程序的编程接口,允许它们参与所关注设备的电源管理。这些接口涵盖了系统休眠和运行时电源管理。

设备电源管理操作

设备电源管理操作在子系统级别和设备驱动程序级别都是通过定义和填充在include/linux/pm.h中定义的struct dev_pm_ops类型的对象来实现的。接下来将解释其中包含的方法的作用。目前,只需记住最后三个方法是特定于运行时电源管理,而其余方法用于系统范围的电源转换。

此外,至少对于某些子系统,还存在一种已弃用的“旧”或“传统”接口用于电源管理操作。这种方法不使用struct dev_pm_ops对象,仅适用于以有限方式实现系统休眠电源管理方法。因此,本文档中未对其进行描述,请直接参考源代码以获取更多信息。

子系统级方法

挂起和恢复设备的核心方法位于struct dev_pm_domain的ops成员指向的struct dev_pm_ops中,或者位于struct bus_type、struct device_type和struct class的pm成员指向的struct dev_pm_ops中。这些方法主要对于编写平台和总线基础设施的人员感兴趣,例如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”;主要例外是已使用ethtool设置了WoL(唤醒LAN)功能的电源按钮、键盘和以太网适配器。对于那些不会自行生成唤醒请求,而只是从一个总线转发唤醒请求到另一个总线的设备(如PCI Express端口),它也应默认为“enabled”。

device_may_wakeup()例程仅在power.wakeup对象存在且相应的power/wakeup文件包含“enabled”字符串时返回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->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)、挂起无 IRQ(suspend_noirq)。

  • 准备阶段旨在通过阻止注册新设备来防止竞争;如果可以随意注册新子设备,PM 核心将永远不知道设备的所有子设备是否已经被挂起。与此相反,从 PM 核心的角度来看,设备可以随时注销。与其他与挂起相关的阶段不同,在准备阶段期间,设备层次结构是自上而下遍历的。

    在 ->prepare 回调方法返回后,设备下方将不得注册新的子设备。该方法也可以以某种方式准备设备或驱动程序以进行即将到来的系统电源转换,但不应将设备置于低功耗状态。此外,如果设备支持运行时电源管理,则 ->prepare 回调方法在必要时不得更新其状态以便稍后从运行时挂起中恢复。

    对于支持运行时电源管理的设备,准备回调的返回值可用于指示给 PM 核心,如果所有设备的后代也返回了正数,并且所有这些设备(包括设备本身)都处于运行时挂起状态,那么 PM 核心可以安全地将设备保持在运行时挂起状态(如果已经运行时挂起)。换句话说,如果准备回调返回正数,并且所有后代设备也是如此,并且所有这些设备(包括设备本身)都处于运行时挂起状态,那么 PM 核心将跳过挂起、挂起晚期和挂起无 IRQ 阶段,以及所有这些设备的后续设备恢复的相应阶段。在这种情况下,->complete 回调将是在 ->prepare 回调之后调用的下一个回调,并且完全负责将设备放入适当的一致状态。

    请注意,即使设备已禁用运行时 PM,此直接完成过程也适用;只有运行时 PM 状态才重要。由此可知,如果设备具有系统睡眠回调但不支持运行时 PM,则其准备回调绝不能返回正数。这是因为所有这样的设备最初都被设置为具有禁用运行时 PM 的运行时挂起状态。

    设备驱动程序还可以通过使用 DPM_FLAG_NO_DIRECT_COMPLETE 和 DPM_FLAG_SMART_PREPARE 驱动程序电源管理标志来控制此功能。[通常,它们是在设备被探测时针对特定设备传递给 dev_pm_set_driver_flags() 辅助函数来设置的。]如果设置了第一个标志,PM 核心将不会对给定设备以及其任何祖先应用上述描述的直接完成过程。第二个标志在设置时通知中间层代码(总线类型、设备类型、PM 域、类别)应考虑驱动程序提供的 ->prepare 回调的返回值,并且只有在驱动程序的 ->prepare 回调也返回正数时,它才能从自己的 ->prepare 回调中返回正数。

  • ->suspend 方法应使设备静止以停止其进行 I/O。它们还可以保存设备寄存器并将其置于适当的低功耗状态,具体取决于设备所在的总线类型,并且它们可以启用唤醒事件。

    但是,对于支持运行时电源管理的设备,子系统提供的 ->suspend 方法(特别是总线类型和 PM 域)必须遵循有关在调用其驱动程序的 ->suspend 方法之前可以对设备进行的操作的附加规则。换句话说,如果必要,它们可以通过为其调用 pm_runtime_resume() 来从运行时挂起中恢复设备,但是它们在那时不得以任何其他方式更新设备的状态(以防驱动程序需要在其 ->suspend 方法中从运行时挂起中恢复设备)。事实上,PM 核心通过在发出 ->prepare 回调之前(并在发出 ->complete 回调之后)调用 pm_runtime_get_noresume() 来防止子系统或驱动程序在这些时间将设备置于运行时挂起状态,并调用 pm_runtime_put()。

  • 对于许多设备,将挂起分为“使设备静止”和“保存设备状态”两个阶段是方便的,在这种情况下,挂起晚期旨在执行后者。它始终在为设备禁用运行时电源管理后执行。

  • 挂起无 IRQ 阶段发生在禁用 IRQ 处理程序之后,这意味着在运行回调方法时不会调用驱动程序的中断处理程序。->suspend_noirq 方法应保存先前未保存的设备寄存器的值,并最终将设备置于适当的低功耗状态。

    大多数子系统和设备驱动程序无需实现此回调。但是,像 PCI 这样允许设备共享中断向量的总线类型通常需要它;否则,驱动程序可能会在挂起阶段遇到错误,因为在其设备被设置为低功耗状态后,可能会处理由其他设备生成的共享中断引发的错误。

在这些阶段结束时,驱动程序应该已经停止了所有 I/O 事务(DMA、IRQ),保存了足够的状态,以便它们可以重新初始化或恢复先前的状态(硬件所需),并将设备置于低功耗状态。在许多平台上,它们将关闭一个或多个时钟源;有时它们还会关闭电源或降低电压。[支持运行时 PM 的驱动程序可能已经执行了这些步骤的一些或全部。]

如果 device_may_wakeup() 返回 true,则应准备设备以生成硬件唤醒信号,以在系统处于睡眠状态时触发系统唤醒事件。例如,enable_irq_wake() 可能会识别连接到开关或其他外部硬件的 GPIO 信号,而 pci_enable_wake() 对 PCI PME 信号执行类似的操作。

如果这些回调中的任何一个返回错误,系统将不会进入所需的低功耗状态。相反,PM 核心将通过恢复所有被挂起的设备来撤消其操作。

离开系统挂起

当从冻结、待机或内存睡眠状态恢复时,各个阶段为:恢复无 IRQ(resume_noirq)、早期恢复(resume_early)、恢复(resume)、完成(complete)。

  • ->resume_noirq 回调方法应在调用驱动程序的中断处理程序之前执行所需的任何操作。这通常意味着撤消挂起无 IRQ 阶段的操作。如果总线类型允许设备共享中断向量,例如 PCI,该方法应将设备及其驱动程序置于一种状态,以便驱动程序可以识别设备是否是传入中断的来源(如果有的话),并正确处理它们。

    例如,PCI 总线类型的 ->pm.resume_noirq() 方法将设备置于全功率状态(PCI 术语中的 D0)并恢复设备的标准配置寄存器。然后,它调用设备驱动程序的 ->pm.resume_noirq() 方法执行设备特定的操作。

  • ->resume_early 方法应为执行恢复方法准备设备。这通常涉及撤消先前挂起晚期阶段的操作。

  • ->resume 方法应将设备恢复到其操作状态,以便它可以执行正常的 I/O。这通常涉及撤消挂起阶段的操作。

  • 完成阶段应撤消准备阶段的操作。因此,与其他与恢复相关的阶段不同,在完成阶段期间,设备层次结构是自下而上遍历的。

    但是,只要发生 ->resume 回调,新的子设备就可以在设备下方注册;不需要等到完成阶段运行。

    此外,如果前面的 ->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 核心将忽略这些错误,因为除了在系统日志中打印它们之外,它无法对它们做任何事情。

进入休眠

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

休眠的一般过程是:静默所有设备("冻结"),在一切稳定时创建系统内存的映像,重新激活所有设备("解冻"),将映像写入永久存储,最后关闭系统("关机")。用于完成此过程的阶段有:准备、冻结、冻结晚期、冻结无IRQ、解冻无IRQ、解冻早期、解冻、完成、准备、关机、关机晚期、关机无IRQ。

  • 准备阶段在上面的"进入系统挂起"部分中已经讨论过。

  • ->freeze方法应该使设备静默,以便它不会产生中断请求(IRQ)或直接内存访问(DMA),并且可能需要保存设备寄存器的值。但是,设备不必处于低功耗状态,并且为了节省时间,最好不要这样做。此外,设备不应准备生成唤醒事件。

  • 冻结晚期阶段类似于前面描述的挂起晚期阶段,只是设备不应处于低功耗状态,也不应允许生成唤醒事件。

  • 冻结无IRQ阶段类似于前面讨论的挂起无IRQ阶段,只是设备不应处于低功耗状态,也不应允许生成唤醒事件。

此时创建了系统映像。在此期间,所有设备应处于非活动状态,并且在此期间内存的内容应保持不变,以便映像形成系统状态的原子快照。

  • 解冻无IRQ阶段类似于前面讨论的恢复无IRQ阶段。主要区别在于,它的方法可以假设设备处于与冻结无IRQ阶段结束时相同的状态。

  • 解冻早期阶段类似于上面描述的恢复早期阶段。如果需要,它的方法应撤消前面的冻结晚期阶段的操作。

  • 解冻阶段类似于前面讨论的恢复阶段。它的方法应将设备恢复到操作状态,以便在需要时可以用于保存映像。

  • 完成阶段在上面的"离开系统挂起"部分中已经讨论过。

此时保存了系统映像,然后需要准备设备以进行即将到来的系统关闭。这与将系统置于挂起到空闲、浅度睡眠或深度睡眠状态之前暂停它们的方式非常相似,阶段也相似。

  • 准备阶段在上面已经讨论过。

  • 关机阶段类似于挂起阶段。

  • 关机晚期阶段类似于挂起晚期阶段。

  • 关机无IRQ阶段类似于挂起无IRQ阶段。

->poweroff、->poweroff_late和->poweroff_noirq回调应该做的事情与->suspend、->suspend_late和->suspend_noirq回调基本相同。值得注意的是,它们不需要存储设备寄存器的值,因为在冻结、冻结晚期或冻结无IRQ阶段应该已经存储了寄存器的值。此外,在许多机器上,固件将关闭整个系统,因此回调不需要将设备置于低功耗状态。

离开休眠

从休眠中恢复与从保留主存储器内容的睡眠状态中恢复相比,更复杂,因为它需要将系统映像加载到内存中,并在将控制权传递回映像内核之前恢复休眠前的内存内容。

尽管原则上,映像可能会由引导加载程序加载到内存中,并恢复休眠前的内存内容,但实际上这是不可能的,因为引导加载程序不够智能,也没有建立的协议来传递必要的信息。因此,引导加载程序将一个新的内核实例(称为"恢复内核")加载到内存中,并以通常的方式将控制权传递给它。然后,恢复内核读取系统映像,恢复休眠前的内存内容,并将控制权传递给映像内核。因此,在从休眠中恢复时涉及两个不同的内核实例。实际上,恢复内核可能与映像内核完全不同:不同的配置甚至不同的版本。这对设备驱动程序及其子系统有重要影响。

为了能够将系统映像加载到内存中,恢复内核需要包含至少一部分设备驱动程序,以使其能够访问包含映像的存储介质,尽管它不需要包含映像内核中存在的所有驱动程序。在加载映像后,由引导内核管理的设备需要准备好将控制权传递回映像内核。这与创建系统映像的初始步骤非常相似,并且以相同的方式完成,使用准备、冻结和冻结无IRQ阶段。但是,这些阶段影响的设备仅限于恢复内核中具有驱动程序的设备;其他设备仍处于引导加载程序留下的任何状态。

如果恢复休眠前的内存内容失败,恢复内核将按照上面描述的"解冻"过程进行,使用解冻无IRQ、解冻早期、解冻和完成阶段,然后继续正常运行。这种情况很少发生。通常情况下,休眠前的内存内容会成功恢复,并将控制权传递给映像内核,然后映像内核负责将系统恢复到工作状态。

为了实现这一点,映像内核必须恢复设备的休眠前功能。这个操作很像从睡眠状态唤醒(保留内存内容),尽管它涉及不同的阶段:恢复无IRQ、恢复早期、恢复、完成。

  • 恢复无IRQ阶段类似于恢复无IRQ阶段。

  • 恢复早期阶段类似于恢复早期阶段。

  • 恢复阶段类似于恢复阶段。

  • 完成阶段在上面已经讨论过。

resume[_early|_noirq]的主要区别在于,restore[_early|_noirq]必须假设设备已被引导加载程序或恢复内核访问和重新配置。因此,设备的状态可能与从冻结、冻结晚期和冻结无IRQ阶段记住的状态不同。设备甚至可能需要被重置和完全重新初始化。在许多情况下,这种差异并不重要,因此->resume[_early|_noirq]->restore[_early|_norq]方法指针可以设置为相同的例程。然而,为了防止出现实际上确实重要的情况,使用不同的回调指针。

电源管理通知器

在讨论的电源管理回调中,有一些操作无法在回调发生得太晚或太早时执行。为了处理这些情况,子系统和设备驱动程序可以注册电源管理通知器,在任务被冻结之前和解冻之后调用它们。一般来说,电源管理通知器适合执行需要用户空间可用的操作,或者至少不会干扰用户空间的操作。

有关详细信息,请参阅挂起/休眠通知器。

设备低功耗(挂起)状态

设备的低功耗状态并不是标准的。一个设备可能只处理“开”和“关”,而另一个设备可能支持十几种不同版本的“开”(有多少个引擎是活动的?),再加上一个比完全“关”更快返回到“开”的状态。

一些总线定义了有关不同挂起状态的规则。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 安全,否则将禁止 PM 域的运行时 PM。但是,只有当所有该域中的设备都是 IRQ 安全的时,才有意义定义 PM 域为 IRQ 安全。此外,如果 IRQ 安全域有父域,则只有当父域本身也是 IRQ 安全的时,才允许父域的运行时 PM,还有一个额外的限制是 IRQ 安全父域的所有子域也必须是 IRQ 安全的。

运行时电源管理

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

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

在某些情况下,决定可能是在子系统级别做出的,而在其他情况下,可能留给设备驱动程序决定。在某些情况下,可能希望在系统范围的电源转换期间将挂起的设备保持在该状态,但在其他情况下,可能需要将设备暂时放回全功率状态,例如以便临时禁用其系统唤醒功能。这一切取决于硬件和所涉及的子系统和设备驱动程序的设计。

如果需要在系统范围的睡眠状态转换期间从运行时挂起中恢复设备,可以通过在设备的驱动程序或其子系统(例如总线类型或 PM 域)的 ->suspend 回调(或者与休眠相关的 ->freeze 或 ->poweroff 回调)中调用 pm_runtime_resume() 来完成。但是,在调用设备驱动程序的 ->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 回调(类似地,在系统休眠的“freeze”和“poweroff”部分也是如此)。[否则,同一设备的相同驱动程序回调可能会连续执行两次,这在一般情况下是无效的。] 如果设备的中间层系统范围 PM 回调存在,则它们负责跳过这些驱动程序回调;如果不存在,则 PM 核心负责跳过它们。子系统回调例程可以通过测试从 dev_pm_skip_suspend() 辅助函数的返回值来确定它们是否需要跳过驱动程序回调。

此外,设置 DPM_FLAG_SMART_SUSPEND 后,如果设备在先前的“freeze”转换期间保持在运行时挂起状态,那么在休眠期间会跳过驱动程序的 ->thaw_noirq 和 ->thaw_early 回调。同样,如果设备的中间层回调在设备上存在,它们负责执行此操作,否则 PM 核心会处理。

DPM_FLAG_MAY_SKIP_RESUME 驱动程序标志

在从睡眠状态进行系统范围恢复时,最简单的方法是将设备置于全功率状态,如 I/O 设备的运行时电源管理框架 中所述。然而,通常希望在系统范围 PM 转换到工作状态后将设备保持在挂起状态,特别是如果这些设备在先前的系统范围挂起之前已经处于运行时挂起状态。为此,设备驱动程序可以使用 DPM_FLAG_MAY_SKIP_RESUME 标志向 PM 核心和中间层代码指示,如果设备可以在系统范围 PM 转换到工作状态后保持在挂起状态,则允许跳过其“noirq”和“early”恢复回调。无论是否这种情况通常取决于给定系统挂起-恢复周期中设备的状态以及正在进行的系统转换类型。特别是,与休眠相关的“thaw”和“restore”转换根本不受 DPM_FLAG_MAY_SKIP_RESUME 的影响。[无论标志设置如何,在“restore”转换期间都会发出所有回调,而在“thaw”转换期间是否跳过任何驱动程序回调取决于 DPM_FLAG_SMART_SUSPEND 是否设置(参见上文)。此外,如果任何驱动程序的子设备将被返回到全功率状态,则不允许设备保持在运行时挂起状态。]

在挂起类型转换的“suspend”阶段期间,DPM_FLAG_MAY_SKIP_RESUME 标志将与 PM 核心设置的 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”恢复回调,并且 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 之前将设备的运行时 PM 状态设置为“活动”。在这种情况下,驱动程序必须准备好处理其系统范围的恢复回调与其 ->runtime_suspend 回调(没有中间的 ->runtime_resume 和系统范围的挂起回调)连续执行,并且设备的最终状态必须在这种情况下反映出“活动”运行时 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 和系统范围挂起/恢复。]