1 PCI设备的电源管理

PCI设备的电源管理包括系统PM和runtime PM。当进行系统电源管理时,比如 echo mem > /sys/power/state, 系统下所有的注册了系统电源管理接口的设备都被要求执行各自的接口函数。
runtime PM仅作用于某一特定设备。

1.1 设备相关的系统电源管理接口函数

设备相关的电源管理回调函数通过结构体struct dev_pm_ops,定义如下:

struct dev_pm_ops {
        int  (*prepare)        (struct device *dev);
        void (*complete)       (struct device *dev);
        int  (*suspend)        (struct device *dev);
        int  (*resume)         (struct device *dev);
        int  (*freeze)         (struct device *dev);
        int  (*thaw)           (struct device *dev);
        int  (*poweroff)       (struct device *dev);
        int  (*restore)        (struct device *dev);
        int  (*suspend_late)   (struct device *dev);
        int  (*resume_early)   (struct device *dev);
        int  (*freeze_late)    (struct device *dev);
        int  (*thaw_early)     (struct device *dev);
        int  (*poweroff_late)  (struct device *dev);
        int  (*restore_early)  (struct device *dev);
        int  (*suspend_noirq)  (struct device *dev);
        int  (*resume_noirq)   (struct device *dev);
        int  (*freeze_noirq)   (struct device *dev);
        int  (*thaw_noirq)     (struct device *dev);
        int  (*poweroff_noirq) (struct device *dev);
        int  (*restore_noirq)  (struct device *dev);
        int  (*runtime_suspend)(struct device *dev);
        int  (*runtime_resume) (struct device *dev);
        int  (*runtime_idle)   (struct device *dev);
};

这里对部分函数进行简单解释:

  • 函数prepare(): 它主要是阻止子设备在它返回之前被注册,一旦prepare()返回成功,驱动子系统和内核其他部分可以认为是不会产生新的probe()方法。若prepare()检测到它无法处理的情况(比如注册的子设备已经在进行中),它会返回-EAGAIN, 所以PM核心会再次执行来处理这种竞争情况。这种方法后面可跟随以下suspend回调的其中一种:suspend()/freeze()/poweroff()。若是suspend to memory或suspend to standby,prepare()的返回值表明PM核心让设备在runtime suspend。若prepare()返回正值,PM核心知道当前设备在runtime suspended状态,在转换过程中它可能会离开这种状态,同时所有子设备也一样。如果这种情况发生,complete()会在prepare()后直接执行。PM核心在开始所有子设备的suspend回调之前执行子系统级别的prepare(), 因此在执行prepare()时设备可以认为是可用的,或能够对runtime resume等请求做回应。但设备驱动不应该对用户空间的有效性做假设,在prepare()时发送firmware请求时无效的。同样在GFP_KERNEL模式下在prepare()中分配大量的内存也是不允许的。为了修复这些限制,驱动可能需要在freeze task之前执行suspend或hibernation notifier。
  • 函数complete():它执行的操作与prepare()相反。在resume过程执行,它在如下resume回调之一后面执行:resume()/thaw()/restore()。也在驱动的suspend回调失败后执行。若PM核心对所有设备执行合适的resume回调后会执行系统级complete()。若prepare()返回正值,它仅需执行complete()来resume。在这种情况下需要complete()做保证设备功能所有事情。最后complete()可以检查power.direct_complete标志来了解是否之前的suspend和resume回调已经执行。
  • 函数suspend():在系统进入睡眠状态时被执行,在这种睡眠状态是内存中的内容需要保存。具体执行的内容依赖于设备的子系统(PM DOMAIN, device type, class or bus type),但在子系统级的suspend()返回后设备需要quiescent, 所以不能做任何IO 或 DMA。子系统级的suspend()在complete()之后执行。
  • 函数suspend_late():suspend()后面的操作。对于很多设备来说,suspend_late()与runtime suspend回调是同一回调路径。
  • 函数resume():在从休眠状态唤醒后执行。具体执行的动作依赖于设备子系统,驱动期望重新工作,能够恢复对硬件事件和软件请求的回复。resume()回调的执行依赖platform和子系统。对于大多数platform, 在resume()时对资源如时钟的有效性没有限制。resume()在resume_noirq()后执行。
  • 函数resume_early():执行resume()之前的准备工作。对于很多设备,resume_early()与runtime resume回调执行相同的路径。
  • 函数freeze():Hibernation特有的接口,在创建hibernation image之前被执行。与suspend()相似,但它不能使能设备来发射wakeup信号或修改电源状态。大多数子系统的freeze()是为了保存设备的设置到内存,然后再restore()中来用。freeze()在prepare()后执行。
  • 函数freeze_late():freeze()后续操作。与suspend_late()类似,但不能使能设备发送wakeup事件或切换电源状态。
  • 函数thaw():Hibernation特有的接口,在创建hibernation image后或创建失败后执行。freeze()执行操作的相反动作。设备在执行freeze()之前是可以工作的。thaw()在thaw_noirq()之后被执行。
  • 函数thaw_early():执行thaw()之前的准备工作。与freeze_late()操作相反。
  • 函数poweroff():Hibernation特有的接口,在保存image后执行。与suspend()类似,但它不需要在内存中保存设备的设置。在prepare()之后执行poweroff()。
  • 函数poweroff-late():poweroff()后续动作。与suspend_late()类似,但它不需要在内存中保存设备的设置。
  • 函数restore():Hibernation特有的接口,在从hibernation image中恢复内存中内容,与resume()类似。
  • 函数restore_early():在restore()之前执行,与resume_early()类似。
  • 函数suspend_noirq():完成suspend()。执行一些suspend设备的额外操作, 包括一些可能与中断处理产生竞争的操作,在suspend_noirq()执行时可以保证中断处理不会执行。在suspend_noirq()执行后设备进入低功耗状态。如果设备可以产生系统wakeup信号并且能够唤醒系统,它应该在此时配置。但是,依赖于platform和设备子系统,suspend()和suspend_late()也允许将设备进入低功耗状态和配置产生wakeup信号,因此定义suspend_noirq()也并不是必须的。
  • 函数resume_noirq():执行resume前的准备工作,执行一些resume设备的额外操作, 包括一些可能与中断处理产生竞争的操作,在resume_noirq()执行时可以保证中断处理不会执行。
  • 函数freeze_noirq():完成freeze()操作。执行一些freeze设备的额外操作, 包括一些可能与中断处理产生竞争的操作,在freeze_noirq()执行时可以保证中断处理不会执行。设备的电源状态不应该被freeze()或freeze_noirq()或freeze_late(),这些函数不能来配置wakeup信号。
  • 函数thaw_noirq():在thaw()之前执行。执行一些thaw设备的额外操作, 包括一些可能与中断处理产生竞争的操作,在thaw_noirq()执行时可以保证中断处理不会执行。
  • 函数poweroff_noirq():在poweroff后执行。与suspend_noirq()类似,但它不需要在内存中保存设备的设置。
  • 函数restore_noirq():在restore之前执行。执行一些restore设备的额外操作, 包括一些可能与中断处理产生竞争的操作,在restore_noirq()执行时可以保证中断处理不会执行。
  • 函数runtime_suspend():让设备准备进入不能与CPU和RAM通信的状态。这不是说设备进入低功耗状态。比如,设备与link相连,在Link后,当link准备关闭时,设备可能仍保持全功率。如果设备确实进入低功耗并且有产生wakeup事件的能力,远端的wakeup被使能。
  • 函数runtime_resume():根据硬件产生的WAKEUP事件或软件的请求将设备进入全功率状态。如果有必要,将设备进入全功率状态并恢复寄存器,此时设备是完全正常工作的。
  • 函数runtime_idle():设备将进入inactive状态,在所有条件满足时可能进入低功耗状态。检查这些条件,如果合适的话,让PM核心发出suspend请求。

对于设备驱动来说,并不是所有电源管理接口函数都需要定义,一般对于系统PM, suspend()/resume()是必须的,对于runtime PM, runtime_suspend()/runtime_resume()是必须的,其他接口根据实际情况来定。

1.2 PCI设备的系统电源管理

对于PCI设备,suspend过程如下所示:

graph TD
subgraph suspend
A[prepare]-->B[suspend]
B[suspend]-->C[suspend_late]
C[suspend_late]-->D[suspend_noirq]
end

各函数如下所示:

  • pci_pm_prepare
  • pci_pm_suspend
  • pci_pm_suspend_late
  • pci_pm_suspend_noirq

对于PCI设备,resume过程如下所示:

graph TD
A[resume_noirq]-->B[resume_early]
B[resume_early]-->C[resume]

对应各函数如下所示:

  • pci_pm_resume_noirq
  • pci_pm_resume_early
  • pci_pm_resume

##未完待续