本文介绍Linux运行时I/O设备的电源管理框架。属于Linux内核文档的翻译。
原文:http://www.kernel.org/doc/Documentation/power/runtime_pm.txt
翻译:CoryXie <wenxue.xie@windriver.com>
1. 介绍
对I/O设备的运行时电源管理(运行时PM)的支持,是在电源管理的核心(PM core)下借助于以下方式实现的:
- 电源管理工作队列pm_wq,总线类型(bus types)和设备驱动(device drivers)可以把自己的PM相关的工作项(work items)置于其上。我们强烈建议,pm_wq用于对所有运行时PM相关的工作项进行排队,因为这使得他们能够与全系统的电源转换(power transitions)进行同步【挂起到RAM(suspend to RAM),休眠(hibernation),以及从系统睡眠状态恢复(resume)】。pm_wq是在include/linux/pm_runtime.h中声明的,定义在kernel /power/ main.c中。
- 在 “struct device”的“power” 成员中的一些运行时PM字段(这是struct dev_pm_info类型,在include/linux/pm.h中定义),可用于同步设备彼此之间的运行时PM操作。
- “struct dev_pm_ops”中的三个设备运行时PM回调函数(在include/linux/pm.h中定义)。
- 一组定义在drivers/base/power/runtime.c中的辅助函数,他们可以用于执行运行时PM操作,而在这样种方式下,他们之间的同步由PM核心负责照顾。鼓励在总线类型和设备驱动程序中使用这些函数。
下面描述在“struct dev_pm_ops” 中存在的运行时PM回调函数,设备运行时PM字段“struct dev_pm_info”,以及运行时PM核心辅助函数。
2. 设备运行时PM回调函数
在“struct dev_pm_ops”中有三个设备运行时PM回调函数:
struct dev_pm_ops {
...
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
...
};
->runtime_suspend(), -> runtime_resume()和 ->runtime_idle()回调函数会被PM核心针对下列类型执行:
- 设备类型(device type),
- 或设备类(device class)(如果该设备类型的struct dev_pm_ops对象不存在),
- 或给定设备的总线类型(bus type)(如果设备类型的struct dev_pm_ops,以及设备类的struct dev_pm_ops对象都不存在)
这就允许设备类型覆盖总线类型或类所提供的回调函数,如果有必要的话。
下面的文档中,总线类型,设备类型和类的回调函数都被称为子系统级的回调函数(subsystem-level callbacks)。
默认情况下,回调函数是在进程上下文中,允许中断的情况下被调用的。然而,子系统可以使用pm_runtime_irq_safe()辅助函数告诉PM核心,设备的 -> runtime_suspend()和 -> runtime_resume()回调函数应该在禁止中断的原子上下文中被调用(-> runtime_idle()仍然使用默认的方式调用)。这意味着,这些回调例程不得block 或者sleep;但同时也意味着在第4节末尾列出的同步辅助函数(synchronous helper functions),可以在中断处理程序或原子上下文中被使用。
子系统的挂起回调函数(suspend callback)_完全_负责_恰当地处理设备的挂起。它可以(但不是必须)包括执行自己的设备驱动程序的->runtime_suspend()回调(从PM核心的角度看,并不是必须要设备驱动实现 ->runtime_suspend()回调函数,只要子系统级的挂起回调函数知道怎么去处理设备就行)。
- 一旦子系统级的挂起回调函数(suspend callback)对给定设备成功地完成,PM核心就认为设备的确已经被挂起,但这并不意味着该设备真的已进入低功耗状态。这里的本意是,该设备将无法处理数据,且将无法与CPU和RAM通信,直到它的子系统级的恢复回调函数(resume callback)被执行。子系统级的挂起回调成功执行后,设备的运行时PM状态是“挂起的(suspended)”。
- 如果子系统级的挂起回调函数(suspend callback)返回-EBUSY或-EAGAIN,设备的运行时PM状态是“活跃的(active)”,这意味着该设备在此之后必须完全处于可运作状态。
- 如果子系统级的挂起回调函数返回一个不同于-EBUSY和-EAGAIN的错误代码,PM核心认为这是一个致命的错误,会拒绝针对该设备运行第4节所述的辅助函数,直到它的状态被直接设置为“活跃的(active)”,或“挂起的(suspended)”(PM核心提供了特殊的辅助函数用于此目的)。
特别的,如果为了能够适当地工作,驱动程序需要远程唤醒功能(即,设备请求使其电源状态变化的硬件机制,如PCI PME), 而device_run_wake()返回“false”的设备,则->runtime_suspend()应该返回-EBUSY。另一方面,对于device_run_wake()返回“true”的设备,且在子系统级挂起回调的执行过程中该设备进入了低功耗状态,我们期望设备的远程唤醒就已经被启动。一般情况下,所有在运行时被设置进入低功耗状态的输入设备应该启用远程唤醒。
子系统级的恢复回调函数(resume callback)要_完全_负责_处理设备的恢复,这可能(但不一定)包括执行自己的设备驱动程序的 ->runtime_resume()回调(从PM核心的角度看,并不是必须要在设备驱动程序中实现->runtime_resume()回调函数,只要子系统级的恢复回调知道怎样能处理设备就行)。
- 一旦子系统级的恢复回调(resume callback)已顺利完成,PM核心认为设备已处于完全可运作状态,这意味着该设备必须能够完成I/O操作。 然后,设备的运行时PM状态是“活跃的(active)”。
- 如果子系统级的恢复回调(resume callback)返回一个错误代码,PM核心认为这是一个致命的错误,会拒绝针对该设备运行第4节所述的辅助函数,直到其状态被直接设置为“活跃的(active)”或“挂起的(suspended)”(PM核心提供了特殊的辅助函数用于此目的)。
每当设备看起来空闲的时候【这是通过两个计数器来向PM核心指示的,设备使用计数(usage counter),以及设备的“活跃子设备”(active children)计数】,子系统级的空闲回调函数(idle callback)就会被PM核心执行。
- 如果任何一个计数器被减少到零(使用PM核心所提供的辅助函数),就检查另一个计数器。如果该计数器也等于零,PM核心就执行子系统级的空闲回调,使用设备作参数。
子系统级的空闲回调(idle callback)执行的操作是完全依赖于子系统本身的,但期望和建议的操作是,检查设备是否可以挂起(即挂起该设备的所有必要条件是否满足),且在这种情况下,为该设备排队一个挂起请求(queue up a suspend request)。这个回调函数返回的值将被PM核心忽略。
在第4节所述的PM核心所提供的辅助函数,保证对总线类型的运行时PM回调满足以下约束:
- 回调是互斥的(例如,对于同一个设备,禁止并行执行->runtime_suspend()和->runtime_resume(),或另一个->runtime_suspend()的实例);例外的情形是,-> runtime_suspend()或 -> runtime_resume()可以和-> runtime_idle()并行执行(虽然对同一设备,-> runtime_idle()将不会在任何其他回调正在执行时启动)。
- -> runtime_idle() 和-> runtime_suspend()只能对 “活跃的(active)”设备执行(即PM核心只会对运行时PM状态是“活跃的(active)” 的设备执行 ->runtime_idle() 和-> runtime_suspend())。
- ->runtime_idle()和->runtime_suspend()只能对其使用计数(usage counter)是零,且其“活跃子设备”(‘active' children)个数是零或“power.ignore_children”标志被设置的设备执行。
- ->runtime_resume()只能对“挂起(suspended)”状态的设备执行(即PM核心只会对运行时PM状态是“挂起(suspended)”的设备执行->runtime_resume())。
此外,由PM核心提供的辅助函数遵循以下规则:
- 如果 ->runtime_suspend()正将要执行,或有一个等待中的请求来执行它,对同一设备->runtime_idle()就不会被执行。
- 请求执行或安排执行 ->runtime_suspend(),将取消任何等待对相同设备的->runtime_idle()执行请求。
- 如果 ->runtime_resume()正将要执行,或有一个挂起的请求来执行它,相同设备的其他的回调将不会被执行。
执行->runtime_resume()请求,将取消任何对同一设备的等待中的(pending)或已被调度的(scheduled)回调执行请求,除了已被调度的自动挂起(autosuspend)。
3. 设备的运行时PM字段
以下是在'struct dev_pm_info'中的设备的运行时PM字段,定义在include / linux/ pm.h:
- struct timer_list suspend_timer;
用于调度(延迟的)挂起和自动休眠(suspend and autosuspend)请求的定时器。
- unsigned long timer_expires;
定时器到期时间,单位是jiffies(如果这异于零,则定时器正在运行,并将于该时间到期;否则定时器未运行)。
- struct work_struct work;
用于请求排队的工作结构(即pm_wq中工作项)。
- wait_queue_head_t wait_queue;
等待队列,当任何辅助函数需要等待另一个完成的时候使用。
- spinlock_t lock;
用于同步。
- atomic_t usage_count;
设备的使用计数。
- atomic_t child_count;
“活跃的(active)”的子设备的个数。
- unsigned int ignore_children;
如果置位,child_count的值将被忽略(但仍然要被更新)
- unsigned int disable_depth;
用于禁用辅助函数(如果该值等于零,它们正常工作),它的初始值是1(即运行时PM最初对所有设备都是禁用的)
- unsigned int runtime_error;
如果该值被设置,就表明有致命错误(在第2节中所述的回调函数返回的错误代码之一),因此辅助函数直到这个标志被清除之前将无法正常工作,这是失败的回调函数返回的错误代码。
- unsigned int idle_notification;
如果该值被设置,则->runtime_idle()正在被执行。
- unsigned int request_pending;
如果该值被设置,则有挂起的请求(即有工作项被排队在pm_wq中)
- enum rpm_request request;
挂起的请求类型(如果request_pending被设置时有效)。
- unsigned int deferred_resume;
当设备正在执行-> runtime_suspend()的时候,如果->runtime_resume()将要运行,而等待挂起操作完成并不实际,就会设置该值;这里的意思是“一旦你挂起完成,我就开始恢复”。
- unsigned int run_wake;
如果设备能够生成运行时唤醒事件,该值就被设置。
- enum rpm_status runtime_status;
设备的运行时PM状态; 此字段的初始值是RPM_SUSPENDED,这意味着PM核心认为每个设备最初都处于'挂起',不论其实际的硬件状态如何。
- unsigned int no_callbacks;
表示该设备不使用运行时PM回调(参见第8节),它只可能会被辅助函数pm_runtime_no_callbacks()修改。
- unsigned int irq_safe;
表示->runtime_suspend()和->runtime_resume()回调函数将在持有自旋锁并禁止中断的情况下被调用。
- unsigned int use_autosuspend;
表明该设备的驱动程序支持延迟的自动休眠功能(见第9节),它只可能被辅助函数pm_runtime{_dont}_use_autosuspend()修改。
- unsigned int timer_autosuspends;
表明PM核心应该在定时器到期时尝试进行自动休眠(autosuspend),而不是一个常规的挂起(normal suspend)。
- int autosuspend_delay;
延迟时间(以毫秒为单位),可用于自动休眠功能。
- unsigned long last_busy;
所有上述字段都是“structdevice”的成员“power”中的成员。
4. 运行时PM设备辅助函数
以下的运行时PM辅助函数被定义在drivers/base/power/runtime.c以及 include/linux/pm_runtime.h中:
- void pm_runtime_init(struct device * dev);
初始化dev_pm_info结构中的设备运行时PM字段。
- void pm_runtime_remove(struct device *dev);
确保设备的运行时PM在该设备从设备层次删除后将被禁用。
- int pm_runtime_idle(struct device *dev);
执行子系统级的设备空闲回调,返回0成功,或失败的错误代码,其中的-EINPROGRESS 表示->runtime_idle()已经在执行。
- int pm_runtime_suspend(struct device *dev);
对设备执行子系统级的挂起回调;返回0表示成功;如果设备的运行时PM状态已经是“挂起”则返回1;或失败时返回错误代码,其中,-EAGAIN或-EBUSY意味着企图在未来再次挂起设备是安全的。
- int pm_runtime_autosuspend(struct device*dev);
与pm_runtime_suspend()相同,除了考虑了自动休眠延迟时间;如果pm_runtime_autosuspend_expiration()说该延迟尚未到期,那么就会调度适当时间的自动休眠功能,并返回0。
- int pm_runtime_resume(struct device *dev);
对设备执行子系统级的恢复回调;返回0表示成功;如果设备的运行时PM状态已经是“活跃的(active)”就返回1;或失败时错误代码,其中-EAGAIN意味着在未来试图恢复设备可能是安全的;但应附加对‘power.runtime_error’进行检查。
- int pm_request_idle(struct device *dev);
对设备提交一个执行子系统级的空闲回调的请求(请求由一个pm_wq的工作项代表);返回0表示成功,或如果请求没有排队成功就返回错误代码。
- int pm_request_autosuspend(struct device*dev);
调度子系统级的挂起回调函数,使其在设备的自动休眠延迟(autosuspend delay)过期时执行;如果延迟已过期,则工作项立即被排队。
- int pm_schedule_suspend(struct device *dev,unsigned int delay);
调度在未来执行设备的子系统级的挂起回调,其中“delay”是在pm_wq上排队挂起回调工作项之前等待的时间,以毫秒为单位(如果“delay”是零,工作项马上进行排队);返回0表示成功;如果该设备的运行时PM状态已经是“挂起”时返回1;或在当请求没有被调度(或 “delay”为0时被排队)时返回错误代码;如果->runtime_suspend()的执行已经被调度但尚未到期,则“delay”的新值将被用来作为等待的时间。
- int pm_request_resume(struct device *dev);
对设备提交一个执行子系统级恢复回调的请求(该请求由一个pm_wq中的工作项代表);成功返回0;如果设备的运行时PM状态已经是”活跃的(active)“则返回1;或当请求没有被排上队时返回错误代码。
- void pm_runtime_get_noresume(struct device*dev);
递增设备的使用计数。
- int pm_runtime_get(struct device *dev);
递增设备的使用计数,运行pm_request_resume(dev),并返回其结果。
- int pm_runtime_get_sync(struct device *dev);
递增设备的使用计数,运行pm_runtime_resume(dev),并返回其结果。
- void pm_runtime_put_noidle(struct device*dev);
递减设备的使用计数。
- int pm_runtime_put(struct device *dev);
设备的使用计数减1,如果结果是0,则运行pm_request_idle(dev)并返回其结果。
- int pm_runtime_put_autosuspend(struct device*dev);
设备的使用计数减1,如果结果是0,则运行pm_request_autosuspend(dev)并返回其结果。
- int pm_runtime_put_sync(struct device *dev);
设备的使用计数减1,如果结果是0,则运行pm_runtime_idle(dev)并返回其结果。
- int pm_runtime_put_sync_suspend(struct device*dev);
设备的使用计数减1,如果结果是0,则运行pm_runtime_suspend(dev)并返回其结果。
- int pm_runtime_put_sync_autosuspend(structdevice *dev);
设备的使用计数减1,如果结果是0,则运行pm_runtime_autosuspend(dev)并返回其结果。
- void pm_runtime_enable(struct device *dev);
使能运行时PM的辅助函数,使其能运行第2节中所描述的设备的总线类型的运行时PM回调。
- int pm_runtime_disable(struct device *dev);
防止运行时PM辅助函数运行设备的子系统级的运行时PM回调,确保设备的所有等待中的运行时PM操作已完成或取消;如果有一个恢复请求正在等待,且为了满足该请求而执行设备的子系统级的恢复回调是必要的,则返回1;否则返回0。
- void pm_suspend_ignore_children(struct device*dev, bool enable);
设置/取消设备的power.ignore_children标志。
- int pm_runtime_set_active(struct device*dev);
清除设备的“power.runtime_error”标志,设置设备的运行时PM状态为”活跃的(active)“,并更新其父设备的”活跃子设备“计数(唯一有效的使用此函数的条件是,如果“power.runtime_error”被设置,或者“power.disable_depth”大于零);如果设备的父设备是不活跃的,且其“power.ignore_children”标志没有设置,该函数就会失败并返回错误代码。
- void pm_runtime_set_suspended(struct device *dev);
清除设备的“power.runtime_error”标志,设置设备的运行时PM状态为“挂起”,并恰当更新其父设备的“活跃的子设备”计数(此函数唯一有效的使用条件是,如果“power.runtime_error”被设置,或“power.disable_depth”大于零)。
- bool pm_runtime_suspended(struct device*dev);
如果该设备的运行时PM状态为“挂起”且其“power.disable_depth”字段等于0,返回true;否则返回false。
- void pm_runtime_allow(struct device *dev);
设置设备的power.runtime_auto标志,并递减其使用计数(用于/sys/devices/.../power/control接口,实际上允许使设备在运行时被电源管理)。
- void pm_runtime_forbid(struct device *dev);
取消设置设备的power.runtime_auto标志,并递增其使用计数(用于/sys/devices/.../power/control接口,实际上禁止设备在运行时被电源管理)。
- void pm_runtime_no_callbacks(struct device*dev);
设置设备的power.no_callbacks标志,并从/sys/devices/.../power中删除运行时PM属性(或防止设备在注册时添加他们)。
- void pm_runtime_irq_safe(struct device *dev);
设置设备的power.irq_safe标志,造成运行时PM挂起和恢复回调在禁止中断的情况下被调用(但不包括空闲回调)。
- void pm_runtime_mark_last_busy(struct device*dev);
设置power.last_busy字段为当前时间。
- void pm_runtime_use_autosuspend(struct device*dev);
设置power.use_autosuspend标志,使能自动休眠延迟。
- void pm_runtime_dont_use_autosuspend(structdevice *dev);
清除power.use_autosuspend标志,禁用自动休眠延迟。
- void pm_runtime_set_autosuspend_delay(structdevice *dev, int delay);
设置power.autosuspend_delay的值为“delay”(以毫秒为单位),如果“delay”是负的,则防止运行时挂起。
- unsigned longpm_runtime_autosuspend_expiration(struct device *dev);
基于power.last_busy和power.autosuspend_delay计算当前自动休眠延迟的到期时间;如果延迟时间是1000毫秒或更大,则到期时间四舍五入精确到秒(rounded up);如果延迟时间已经过期或power.use_autosuspend没有设置,则返回0;否则返回以jiffies计的过期时间。
从中断上下文中执行以下辅助函数是安全的:
- pm_request_idle()
- pm_request_autosuspend()
- pm_schedule_suspend()
- pm_request_resume()
- pm_runtime_get_noresume()
- pm_runtime_get()
- pm_runtime_put_noidle()
- pm_runtime_put()
- pm_runtime_put_autosuspend()
- pm_runtime_enable()
- pm_suspend_ignore_children()
- pm_runtime_set_active()
- pm_runtime_set_suspended()
- pm_runtime_suspended()
- pm_runtime_mark_last_busy()
- pm_runtime_autosuspend_expiration()
如果pm_runtime_irq_safe()为设备调用,则以下辅助函数也可以在中断上下文中使用:
- pm_runtime_suspend()
- pm_runtime_autosuspend()
- pm_runtime_resume()
- pm_runtime_get_sync()
- pm_runtime_put_sync_suspend()
5. 运行时PM初始化,设备检测和删除
最初,所有设备的运行时PM被禁用,这意味着第4节中描述的大部分的运行时PM辅助函数将返回-EAGAIN,直到为设备调用pm_runtime_enable()之后。
此外,所有设备的运行时PM的初始状态都是'挂起(suspended)',但它不一定反映实际的物理设备状态。因此,如果设备最初是活跃的(即,它能够处理I/O),其运行时PM状态必须在pm_runtime_set_active()的帮助之下,在为设备调用pm_runtime_enable()之前,被改变为“活跃”。
然而,如果该设备有父设备且其父设备的运行时PM是启用的,为设备调用pm_runtime_set_active()会影响其父设备,除非其父设备的“power.ignore_children”标志位被设置。也就是说,在这种情况下,使用PM核心的辅助函数,父设备不能在运行时被挂起,只要子设备的状态是“活跃的”,即使子设备的运行时PM还是禁用的(即pm_runtime_enable ()尚未对该子设备调用,或对该子设备已调用pm_runtime_disable())。出于这个原因,一旦pm_runtime_set_active()被为设备调用,pm_runtime_enable()也应该被尽早调用;否则其运行时PM状态应该在pm_runtime_set_suspended()的帮助下改回为“挂起”。
如果设备的默认初始运行时PM状态(即“挂起”)反映了实际设备状态,它的总线类型或它的驱动程序的->probe()回调函数将可能需要使用在第4节描述的PM核心的辅助函数唤醒它。在这种情况下,应使用pm_runtime_resume()。当然,为达此目的,在此之前,设备的运行时PM应通过调用pm_runtime_enable()被启动。
如果设备的总线类型或驱动程序的->probe()回调运行pm_runtime_suspend()或pm_runtime_idle()或与之对应的异步函数(asynchronous counterparts),他们将失败返回-EAGAIN,因为该设备的使用计数已经被驱动程序核心递增,然后再执行->probe()。尽管如此,可能仍然比较想要设备在->probe()完成后尽快被挂起,所以驱动那时候会核心采用pm_runtime_put_sync()来调用子系统级的设备空闲回调。
此外,在__device_release_driver()中,驱动核心可以防止运行时PM回调与总线通知(notifier)回调竞争,这是必要的,因为一些子系统使用通知(notifier)来进行影响运行时PM的操作。这是通过在driver_sysfs_remove()和BUS_NOTIFY_UNBIND_DRIVER通知之前调用pm_runtime_get_sync()来实现该目的的。如果设备已经处于挂起状态,这将恢复该设备,并会防止在这些例程正在执行时再次被挂起。
为了让总线类型和驱动程序在其->remove()例程中调用pm_runtime_suspend()将设备放到挂起状态,在__ device_release_driver()中驱动程序核心在运行BUS_NOTIFY_UNBIND_DRIVER通知后执行pm_runtime_put_sync()。这就需要总线类型和驱动程序避免其->remove()回调函数与运行时PM直接竞争,但也让驱动程序在处理设备的移除过程中有更多的灵活性。
通过将/sys/devices/.../power/control属性值改变为“on”,用户空间可以有效地禁止设备驱动程序进行运行时电源管理,这会导致pm_runtime_forbid()被调用。原则上,也可以使用这个机制有效地关闭运行时设备电源管理,直到用户空间打开它。也就是说,在初始化时,驱动程序可以确保设备的运行时PM状态是“活跃的(active)”,并调用pm_runtime_forbid()。应该指出的是,如果用户空间已经有意改变/sys/devices/.../power/control 的值为“自动”,让驱动在运行时进行设备的电源管理,驱动程序这样用pm_runtime_forbid()可能会让用户空间产生混淆。
6. 运行时PM和系统休眠
运行时PM和系统休眠(即,系统挂起和休眠,也被称为挂起到RAM和挂起到磁盘)以多种方式互相交互。如果系统休眠开始时设备处于活跃状态,那么一切都简单。但如果设备已挂起,会发生什么呢?
对于运行时PM和系统休眠,设备可能有不同的唤醒设置。例如,远程唤醒可能会在运行时PM中启用,但不允许系统休眠时启用(device_may_wakeup(dev)返回“false”)。当发生这种情况时,子系统级系统挂起回调(system suspend callback)负责改变设备的唤醒设定(它可能将这个责任交给设备驱动器的系统挂起例程)。为了做到这一点,可能需要先恢复设备,再挂起它。如果驱动程序对运行时挂起和系统休眠使用不同的电源级别或其他设置,也是如此。
在系统恢复时,设备一般应恢复到全功率状态,即使他们在系统休眠开始前已经被挂起。这有几个原因,包括:
- 该设备可能需要切换功率等级(power levels),唤醒设置等。
- 远程唤醒事件可能已被固件丢失。
- 该设备的子设备可能需要该设备以全功率运行,以恢复他们自己。
- 驱动程序对设备状态的想法可能与设备的物理状态不同。这可能在从休眠(hibernation)状态恢复时发生。
- 该设备可能需要进行复位。
- 即使设备被挂起,如果其使用计数>0,那么它仍然很可能会在不久的将来需要运行时恢复。
- 总是回到全功率是最简单的。
如果系统睡眠开始前设备已经被挂起,那么它的运行时PM状态将必须被更新,以反映实际的系统睡眠后的状态。做到这一点的方法是:
- pm_runtime_disable(dev);
- pm_runtime_set_active(dev);
- pm_runtime_enable(dev);
7. 通用的子系统回调函数
子系统可能希望通过使用PM核心提供的一套通用的,定义在driver/base/power/generic_ops.c中的电源管理回调函数,以节省代码空间:
- int pm_generic_runtime_idle(struct device*dev);
调用此设备的驱动程序提供的->runtime_idle()回调函数(如果有定义的话),并在该回调返回值是0或者回调没有定义的情况下,调用pm_runtime_suspend()。
- int pm_generic_runtime_suspend(struct device*dev);
调用此设备的驱动程序提供的 ->runtime_suspend()回调函数,并返回其结果,或如果该回调函数没有定义时返回-EINVAL。
- int pm_generic_runtime_resume(struct device*dev);
调用此设备的驱动程序提供的->runtime_resume()回调函数,并返回其结果,或如果该回调函数没有定义时返回-EINVAL。
- int pm_generic_suspend(struct device *dev);
如果该设备未在运行时被挂起,调用此设备的驱动程序提供的->suspend()回调函数,并 返回其结果,或如果该回调函数没有定义时返回-EINVAL。
- int pm_generic_resume(struct device *dev);
调用此设备的驱动程序提供的->resume()回调函数,且如果成功的话,改变设备的运行时PM状态为“活跃的”。
- int pm_generic_freeze(struct device *dev);
如果该设备未在运行时被挂起,调用此设备的驱动程序提供的-> freeze ()回调函数,并 返回其结果,或如果该回调函数没有定义时返回-EINVAL。
- int pm_generic_thaw(struct device *dev);
如果该设备未在运行时被挂起,调用此设备的驱动程序提供的-> thaw ()回调函数,并 返回其结果,或如果该回调函数没有定义时返回-EINVAL。
- int pm_generic_poweroff(struct device *dev);
如果该设备未在运行时被挂起,调用此设备的驱动程序提供的-> poweroff ()回调函数,并返回其结果,或如果该回调函数没有定义时返回-EINVAL。
- int pm_generic_restore(struct device *dev);
调用此设备的驱动程序提供的-> restore()回调函数,且如果成功的话,改变设备的运行时PM状态为“活跃的”。
这些函数可以被赋值给系统级dev_pm_ops结构体的下列回调函数指针:
- ->runtime_idle(),
- ->runtime_suspend(),
- ->runtime_resume(),
- ->suspend(),
- ->resume(),
- ->freeze(),
- ->thaw(),
- ->poweroff()
- ->restore()
如果子系统希望同时使用所有的这些函数,可以简单地将GENERIC_SUBSYS_PM_OPS宏(定义在include/linux/pm.h)赋值给其dev_pm_ops结构的指针。
希望使用相同的函数作为系统挂起(system suspend), 冻结(freeze),断电(poweroff)以及运行时挂起(run-time suspend),以及类似的,系统恢复(system resume),解冻(thaw),恢复(restore)和运行时恢复(run-timeresume)等回调函数的设备驱动程序,可以在定义在include/linux/pm.h中的UNIVERSAL_DEV_PM_OPS宏的帮助下做到这一点(可能是其最后一个参数设置为NULL)。
9. 自动休眠功能或自动延时挂起
改变设备的电源状态并不是免费的,它也需要时间和能耗。只有当有理由认为设备将保持在这种状态下大量的时间时,才应将设备置入低功耗状态。一个通常的启发式说法,一直没有怎么用的设备很可能继续保持在未使用状态,按照这个建议,驱动程序不应该允许设备在运行时挂起,直到他们处于非活跃状态已经有一段最低限度的时间。即使该启发式说法最终并非最佳,它仍然会阻止设备在低功耗和全功率状态之间迅速“反弹”。
术语“自动休眠(autosuspend)”是一个历史遗留下来的名字。这并不意味着该设备就会自动挂起(子系统或驱动程序仍然需要调用适当的PM例程),然而这意味着运行时挂起(run-time suspends)将自动被延迟,直到所需的一段时间空闲后。
不活跃(Inactivity)是根据power.last_busy字段来确定的。驱动程序应该在进行I/O后调用pm_runtime_mark_last_busy()来更新这个字段,通常是在刚要调用pm_runtime_put_autosuspend()之前。所需的空闲时间长度是一个策略问题。子系统可以在最初调用pm_runtime_set_autosuspend_delay()设置该长度,但设备注册后该长度应由用户空间控制,使用/sys/devices/.../power/autosuspend_delay_ms属性。
为了使用自动休眠(autosuspend),子系统或驱动程序必须调用pm_runtime_use_autosuspend()(最好是在注册设备之前),此后他们应该使用各种*_autosuspend()辅助函数,来代替非自动休眠的对应函数(non-autosuspend counterparts):
- 不用:pm_runtime_suspend 而用:pm_runtime_autosuspend;
- 不用:pm_schedule_suspend 而用:pm_request_autosuspend;
- 不用:pm_runtime_put 而用:pm_runtime_put_autosuspend;
- 不用:pm_runtime_put_sync 而用:pm_runtime_put_sync_autosuspend.
驱动程序可以继续使用非自动休眠功能辅助函数,他们会表现正常,而不把自动休眠延迟考虑进来。同样,如果power.use_autosuspend字段没有被设置,则自动休眠的辅助函数,使用起来就像是非自动休眠的对应函数(non-autosuspend counterparts)。
该实现非常适合用于异步中断上下文中。然而,这样的使用不可避免地涉及到竞争,这是由于PM核心不能同步 ->runtime_suspend()回调与I/O请求的到来。该同步必须由驱动程序使用其私有锁来完成。这里是一个原理性的伪代码示例:
foo_read_or_write(structfoo_priv *foo, void *data)
{
lock(&foo->private_lock);
add_request_to_io_queue(foo,data);
if(foo->num_pending_requests++ == 0)
pm_runtime_get(&foo->dev);
if(!foo->is_suspended)
foo_process_next_request(foo);
unlock(&foo->private_lock);
}
foo_io_completion(structfoo_priv *foo, void *req)
{
lock(&foo->private_lock);
if(--foo->num_pending_requests == 0) {
pm_runtime_mark_last_busy(&foo->dev);
pm_runtime_put_autosuspend(&foo->dev);
}else {
foo_process_next_request(foo);
}
unlock(&foo->private_lock);
/*Send req result back to the user ... */
}
intfoo_runtime_suspend(struct device *dev)
{
structfoo_priv foo = container_of(dev, ...);
intret = 0;
lock(&foo->private_lock);
if(foo->num_pending_requests > 0) {
ret= -EBUSY;
}else {
/*... suspend the device ... */
foo->is_suspended= 1;
}
unlock(&foo->private_lock);
returnret;
}
intfoo_runtime_resume(struct device *dev)
{
structfoo_priv foo = container_of(dev, ...);
lock(&foo->private_lock);
/*... resume the device ... */
foo->is_suspended= 0;
pm_runtime_mark_last_busy(&foo->dev);
if(foo->num_pending_requests > 0)
foo_process_requests(foo);
unlock(&foo->private_lock);
return0;
}
最重要的一点是,在foo_io_completion()要求自动休眠之后,foo_runtime_suspend()回调可能与foo_read_or_write()竞争。因此foo_runtime_suspend()必须检查是否有任何挂起的I/O请求(在持有私有锁的情况下),然后才允许挂起进行。
此外,power.autosuspend_delay字段可以由用户空间在任何时间改变。如果驱动程序关心这个,它可以在持有其私有锁的情况下在->runtime_suspend()回调内调用pm_runtime_autosuspend_expiration()。如果该函数返回非零值,那么该延误尚未过期,则该回调应该返回-EAGAIN。