Documentation/power/runtime_pm.txt译稿

Documentation/power/runtime_pm.txt
Runtime Power Management Framework for I/O Devices
I/O设备的运行时电源管理框架
(C) 2009-2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. 
(C) 2010 Alan Stern <stern@rowland.harvard.edu>

1. 简介 

   对于I/O设备的运行时电源管理(runtime PM)的支持,在PM core 级别通过以下方法提供:

   总线类型和设备驱动能够将他们的PM相关的事项放在电源管理工作队列 pm_wq里面.强烈建议
所有和runtime PM相关的work items都挂到pm_wq工作队列中去,因为这允许他们与系统范围的
电源状态转换同步(suspend to RAM, hibernation 以及 从系统睡眠状态唤醒). pm_wq 的
声明在 include/linux/pm_runtime.h 和 kernel/power/main.c 中.

   很多device结构中的'power'成员(类型为struct dev_pm_info)的runtime PM fields 
可以被用于同步runtime PM 操作 

   "struct dev_pm_ops"结构中的三个device runtime PM callbacks.

   定义在drivers/base/power/runtime.c中的一系列辅助函数,这些函数被用于以这样一种
方式执行运行时电源管理的操作: 
   他们之间的同步是由PM core负责,bus type和device driver被鼓励来使用这些函数;
   runtime PM 回调函数出现在struct dev_pm_ops里面;
"struct dev_pm_info"的device runtime PM域和runtime PM提供的核心辅助函数在稍后
进行描述.

2. 设备的运行时电源管理回调 

'struct dev_pm_ops'结构中定义了三种device runtime 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 core为设备所属的
子系统执行,他们(三个回调)可能属于下面的任何一种: 

  1. 设备的PM域, 如果设备的PM域对象,dev->pm_domain存在.
  2. 设备的device type, 如果dev->type和dev->type->pm都存在.
  3. 设备的class, 如果dev->class和dev->class->pm都存在.
  4. 设备的bus type,如果dev->bus和dev->bus->pm都存在.

    如果按照上面规则选择的子系统没有提供相关的回调,PM core将会直接调用保存在dev->pm中
对应驱动的回调(如果存在的话). PM core总是按照上面顺序检测使用哪个回调,所以回调的优先级从
高到低为:PM domain > device type > class > bus type . 除此而外, 高优先级的总是先于
低优先级的.PM domain, bus type, device type 和 class 回调在后面会被称为子系统级别的回调.

    默认的, 这些回调函数经常是在中断使能的进程上下文被调用. 然而,在关中断的原子上下文中,
pm_runtime_irq_safe()辅助函数能被用来告诉PM core,为给定设备运行runtime_suspend(), 
runtime_resume() 和 runtime_idle()回调是安全的. 这说明前述回调例程一定不能阻塞或者睡
眠,但是它也意味着在中断上下文或者普通的原子上下文设备可以使用section 4列出的同步辅助函数

    子系统级别的suspend回调,如果存在的话,要对酌情处理设备的挂起负全责的.这可以但不必包括
执行设备驱动自己的->runtime_suspend()回调(从PM core的角度看,只要子系统级别的suspend
回调知道该对设备做什么在设备驱动里面实现一个->runtime_suspend()回调是不必要的)

    一旦子系统级别的回调(或者驱动的suspend回调,如果直接被调用)对给定设备成功完成的话,PM 
core就认为设备已经suspend了,这不一定意味着他已经被置为低电状态.但是他被希望意味着在resume
回调执行前设备不会处理数据,也不会和CPUs和RAM通信.成功执行suspend回调之后,设备的runtime 
PM状态为'suspended’

    如果suspend回调返回 -EBUSY或者 -EAGIN,设备的runtime PM状态仍然保持为'active',这
意味着后面设备一定是完全可操作的.

    如果suspend回调返回的错误码不是-EBUSY或者-EAGIN, PM core把这当作一个致命错误,并且
将会拒绝为设备执行section 4描述辅助函数,直到他(PM core)的状态被设为'active'或者
'suspend',(PM core为这个目的提供了特殊的辅助函数)。特殊的,如果驱动要求远程唤醒能力(例如:
硬件机制允许设备请求改变其电源状态,例如PCI IME)以利正常运行,并且device_run_wake()为设
备返回'false',那么runtime_suspend()应该返回-EBUSY.另一方面,如果device_run_wake()为
设备返回'true'并且设备在执行suspend回调期间被置为低电状态,就认为设备将会被使能远程唤醒.通
常,远程唤醒应该对所有在运行时被置为低电状态的input devices使能。系统级的resume回调,如果
存在的话,对酌情处理设备的resume负全责. 这可以,但是不必包括执行设备自己的runtime_resuem
回调(从PM core的角度看,只要子系统级别的resume回调直到要对设备做什么他(PM core)不必在设
备驱动中实现一个,runtime_resume()回调)

    一旦子系统级别的回调(或者驱动的resume回调,如果存在的话)成功完成,PM core认为设备是
fully operational的,这意味这设备必须能够完成需要的I/O操作.此时,runtime PM的状态就是
'active'.

    如果resume回调返回一个错误码, PM core 把它当作一个致命错误,并且将会拒绝运行
section 4描述的设备的辅助函数.直到他(PM core)的状态被直接置为'active'或者'suspended'
(通过PM core为此提供的特殊的辅助函数).idle回调函数(一个子系统界别的回调函数,如果存在的
话,异或是一个驱动中的idle回调)每当设备表现为dile的时候就会被PM core执行,设备是否idle
在PM core中由两个计数器表征,设备的引用计数和处于'active'状态的子设备的引用计数. 

    如果这些计数器中的任意一个使用PM core提供的函数递减到0,另一个计数器就会被检测,如果两
个计数器也是0,那么PM core就会执行idle回调函数,以device作为其参数.idle回调函数执行的
动作完全依赖于所考虑的子系统(或者设备).但是期望的或者推荐的动作是检测设备是否可以被suspend
(例如: 如果suspend设备需要满足的所有的必要条件都被满足)并且在这种情况下去排队一个设备的
suspend请求.这个回调的返回值被PM core忽略.section 4描述的PM core提供的辅助函数,保证
后面关于同一个设备的runtime PM回调的限制会被满足,

(1)回调函数是相互排斥的(例如: runtime_resume()和runtime_suspend()或者同一个设备的另一
    个runtime_suspend()实例是不能并行执行的), runtime_suspend()或者
    runtime_resume()可以和runtime_idle()并行执行这种情况除外(尽管runtime_idle()在任
    何其他的回调函数为相同设备执行的时候不会被启动)    

(2) runtime_idle() and runtime_suspend() 只能为处于'active'状态的设备执行(例如: 
    PM core只会为runtime PM status为'active'的设备执行runtime_idle()或者
    runtime_suspend())

(3) runtime_idle() and runtime_suspend()只能为引用计数为0并且处于'active'状态的子设
     备数为0的设备或者'power.ignore_children'标志被设置的设备执行.

(4) runtime_resume()只能为'suspended'的设备执行(例如: PM core将只会为runtime PM状态
     为'suspended'的设备执行runtime_resume())

除此而外,PM core提供的辅助函数遵循如下规则:

    如果 runtime_suspend() 将要被执行或者有一个挂起的执行runtime_suspend()的请求 
    runtime_idle()将不会对同一个设备执行.

    一个执行或者调度执行runtime_suspend()的请求将会取消任何挂起的对同一个设备的执行
    runtime_idle()请求

    如果runtime_resume()将要被执行或或者有挂起的执行runtime_resume()的请求,其他的
    回调将不会对同一个设备执行

    一个执行runtime_resume()的请求将会取消任何挂起的或者被调度的,针对同一个设备执行其他
    回调的请求  


3. 运行时电源管理的设备域

下面的device runtime PM fields出现在'struct dev_pm_info'结构里面, 此定义在include/linux/pm.h文件里面:

  struct timer_list suspend_timer;
    - 用于调度(延迟的)挂起的或者自动挂起的请求.

  unsigned long timer_expires;
    - 时钟超时事件,以jiffies为单位(如果不是0,说明时钟正在运行并且将会在那个时间超时,否则(等于0),时钟没有在运行)

  struct work_struct work;
    - 用于排队请求的work结构(pm_wq队列里面的work items) 

  wait_queue_head_t wait_queue;
    - 如果任何一个辅助函数需要等待另一个辅助函数完成,则使用此等待队列

  spinlock_t lock;
    - 用于同步的锁  

  atomic_t usage_count;
    - 设备的引用计数

  atomic_t child_count;
    - 处以'active'状态的子设备的计数

  unsigned int ignore_children;
    - 如果设置了,子设备计数的值会被忽略.

  unsigned int disable_depth;
    - 用于禁止辅助函数()(如果此值为0则辅助函数正常工作);它的初始值为1.
      (例如: runtime PM 初始对所有设备都是被禁止的)

  unsigned int runtime_error;
    - 如果设置了,当有一个致命错误的时候(回调函数之一返回错误码,如section 2所述),
      直到这个标志被清除, 辅助函数才会会工作. 这是失败的回调函数返回的错误码.
       
  unsigned int idle_notification;
    - 如果设置了 runtime_idle()就会被执行.

  unsigned int request_pending;
    - 如果设置了,说明有挂起的请求(例如: 排队在pm_wq工作队列中的一个work item)

  enum rpm_request request;
    - 挂起的请求的类型(如果设置了request_pending被设置)

  unsigned int deferred_resume;
    - 当runtime_suspend()正在被执行的时候,
      如果runtime_resume()将要对那个设备执行,并且等待suspend完成是不实际的,
      则设置此标记; 意味着一suspend就resume.

  unsigned int run_wake;
    - 如果设备能够产生运行时唤醒事件


  enum rpm_status runtime_status;
    - 设备的runtime PM status;
      这个域的初值为RPM_SUSPEND,这意味这每一个设备初始被PM core看作'suspended'.
      不考虑他的真实的硬件状态.


  unsigned int runtime_auto;
    - 如果设置了,代表用户空间已经允许设备驱动在运行时通过 /sys/devices/.../power/control
      接口去管理设备电源; 他只能通过pm_runtime_allow()和pm_runtime_forbid()辅助函数被修改. 


  unsigned int no_callbacks;
    - 代表设备不会使用runtime PM回调(参考section 8);
      他只可以被pm_runtime_no_callbacks()辅助函数修改.

  unsigned int irq_safe;
    - 代表 runtime_suspend()和runtime_resume()回调将会在“持自旋锁/关中断”的情况下被调用。

  unsigned int use_autosuspend;
    - 代表设备的驱动支持延迟自动挂起(参考section 9); 他只可以被
       pm_runtime{_dont}_use_autosuspend()修改.


  unsigned int timer_autosuspends;
    - 代表当时钟超时时PM core应该试图执行一个自动挂起,而不是一个普通的suspend.


  int autosuspend_delay;
    - 延迟时间(以毫秒为单位),用于autosuspend.

  unsigned long last_busy;
    -pm_runtime_mark_last_busy()辅助函数上一次为此设备被调用的时间(以jiffies为单位).
      用于计算autosuspend的空闲周期

所有上面的各个域都是'struct device’的'power'成员的成员.

4. 运行时电源管理设备辅助函数 

后面的runtime PM 辅助函数定义于 drivers/base/power/runtime.c 和 include/linux/
pm_runtime.h

  void pm_runtime_init(struct device *dev);
    - 初始化'struct dev_pm_info'结构中设备的runtime PM 域.

  void pm_runtime_remove(struct device *dev);
    - 确保在从设备树中移除设备的时候,设备的runtime PM将会被禁止.

  int pm_runtime_idle(struct device *dev);
    - 执行对设备的子系统级别的idle回调;成功返回0,失败返回错误码,这里EINPROGRESS
      意味着runtime_idle()已经在执行了

  int pm_runtime_suspend(struct device *dev);
    - 对设备执行子系统级别的suspend回调;成功返回0,如果设备的runtime PM 状态
       已经是'suspended'返回 1,或者失败返回错误码. 这里-EAGAIN或者-EBUSY意味着
       将来再次试图挂起设备是安全的, -EACCES意味着'power.disable_depth'标志不是0. 

  int pm_runtime_autosuspend(struct device *dev);
    - 和pm_runtime_suspend()相同,希望autosuspend延迟被考虑在内.
       如果pm_runtime_autosuspend_expiration()显示延迟还没有超时, 
       那么一个autosuspend被调度到一个合适的时间执行并返回0

  int pm_runtime_resume(struct device *dev);
    - 对设备执行子系统级别的resume回调;成功返回0,如果设备的runtime PM状态已经是'active'返回1,
      或者失败了返回错误码.这里-EAGAIN意味着将来再次试图唤醒设备是安全的,但是'power.runtime_error'
      应该再次被检查, 并且 -EACCESS意味着'power.runtime_error'不是0.

  int pm_request_idle(struct device *dev);
    - 提交一个请求来对设备执行子系统级别的idle回调(请求有一个pm_wq的work item代表);
       成功返回0,如果请求还没有被排队返回错误码.


  int pm_request_autosuspend(struct device *dev);
    - 当autosuspend延迟已经超时的时候,为设备调度子系统级别的autosuspend回调;
      如果延迟已经超时,那么work item立即被挂入队列.

  int pm_schedule_suspend(struct device *dev, unsigned int delay);
    - 调度将来对设备的子系统级别的suspend回调的执行,这里'delay'是一个suspend work item
      挂入pm_wq队列之前所等待的时间.以毫秒为单位.(如果'delay'是0, work item立即被挂入队列);
      成功返回0,如果设备的PM runtime status已经是'suspended'返回1, 如果请求还没被调度返回
       错误码(或者已经挂入队列但'delay'是0),如果runtime_suspend()已经被调度并且还没有超时,
      'delay'的新值将被用作要等待的时间。


  int pm_request_resume(struct device *dev);
    - 提交一个请求对设备执行子系统级别的resume回调(请求由pm_wq的一个work item表征),成功
       返回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);
    - 减少设备引用计数;如果结果是0,则运行pm_request_idle(dev)并且返回他的结果.

  int pm_runtime_put_autosuspend(struct device *dev);
    - 减少设备的引用计数;如果结果是0,则运行pm_request_autosuspend(dev)并且返回他的结果. 

  int pm_runtime_put_sync(struct device *dev);
    - 减少设备的引用计数;如果结果是0,则运行pm_runtime_idle(dev)并且返回他的结果.

  int pm_runtime_put_sync_suspend(struct device *dev);
    - 减少设备的引用计数;如果结果是0,则运行run pm_runtime_suspend(dev)并且返回他的结果.

  int pm_runtime_put_sync_autosuspend(struct device *dev);
    - 减少设备的引用计数;如果结果是0,则运行pm_runtime_autosuspend(dev)并且返回他的结果.

  void pm_runtime_enable(struct device *dev);
    - 减少设备的'power.disable_depth'域;如果减到0,运行时PM 辅助函数能够执行section 2 描述的
       设备的系统级的回调函数.

  int pm_runtime_disable(struct device *dev);
    - 增加设备的'power.disable_depth'域(如果此域原来的值是0,这会阻止系统级别的runtime 
       PM callbacks对设备执行),确保所有挂起的runtime PM operations on the device要么
         完成要没被取消;如果有一个resume请求挂起则返回1,并且有必要对设备执行子系统级别的resume回调,
        来满足请求,否则返回0.

  int pm_runtime_barrier(struct device *dev);
    - 检查是否有挂起该设备的resume请求,如果有的话则resume(同步地)设备,取消任何其他关于该设备的
       挂起的runtime PM请求并且等待所有的rnutime PM请求进入完成的过程中;
      如果有挂起的resume请求,并且有必要对设备执行子系统级的resume回调来满足该请求的话则返回1,
       否则返回0

  void pm_suspend_ignore_children(struct device *dev, bool enable);
    - set/unset the power.ignore_children flag of the device
    - 设置/取消 设备的power.ignore_children标志 

  int pm_runtime_set_active(struct device *dev);
    - 清除设备的'power.runtime_error'标志,设置设备的runtime PM状态到'active'并更新他的父设备
        的'active'子设备的引用计数到合适的值。(仅当'power.runtime_error'被设置或者
        'power.disable_depth'大于0才有效);如果设备有'not active'并且其
        'power.ignore_children'标志没有设置的父设备,此函数将会失败并且返回错误码

  void pm_runtime_set_suspended(struct device *dev);
    - 清除设备的'power.runtime_error'标志,设置设备的运行时电源管理状态为'suspended'并酌情更新
       其父设备的'active'子设备计数(仅当'power.runtime_error'被设置或者'power.disable_depth' 
      > 0 使用这个函数才有效.)

  bool pm_runtime_suspended(struct device *dev);
    - 如果设备的运行时电源管理状态是'suspended'并且其'power.disable_depth'域等于0 返回真,否则
       返回假

  bool pm_runtime_status_suspended(struct device *dev);
    - 如果设备的rnutime PM状态是'suspended'返回真

  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清除runtime电源管理属性.
      (或者阻止他们在设备注册的时候被添加)

  void pm_runtime_irq_safe(struct device *dev);
    - 为设备设置power.irq_safe标志,引起runtime-PM回调在关中断的情况下被调用

  void pm_runtime_mark_last_busy(struct device *dev);
    - 设置power.last_busy域为当前时间.

  void pm_runtime_use_autosuspend(struct device *dev);
    - 设置power.use_autosuspend标志,使能autosuspend延迟. 

  void pm_runtime_dont_use_autosuspend(struct device *dev);
    - 清除power.use_autosuspend标志,禁止autosuspend延迟.

  void pm_runtime_set_autosuspend_delay(struct device *dev, int delay);
    - 设置power.autosuspend_delay值为'delay'(以毫秒为单位);如果'delay'为负值,则
       runtime suspends会被阻止.

  unsigned long pm_runtime_autosuspend_expiration(struct device *dev);
    - 在但前autosuspend延迟周期将要超过的时候计算时间,以power.last_busy和
       power.autosuspend_delay为基础;如果延迟时间为1000ms或者大于超时时间,则延迟时间
       将被圆整为以second为单位的值,如果延迟周期已经超过或者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_idle() 
      pm_runtime_suspend()
      pm_runtime_autosuspend() 
      pm_runtime_resume() 
      pm_runtime_get_sync() 
      pm_runtime_put_sync() 
      pm_runtime_put_sync_suspend() 
      pm_runtime_put_sync_autosuspend()


5. 运行时电源管理初始化, 设备探测和移除

    最初,runtime PM 对所有设备都是禁用的. 这意味着section 4中描述的大部分runtime PM 辅助
函数将返回-EAGIN,直到pm_runtime_enable()被调用.

    除此而外,所有设备的runtime PM的初始状态是'suspended',但是,它不需要反映设备实际的物理状
态. 这样,如果设备最初是active(例如:他能够处理I/O),它的runtime PM 状态必须在
pm_runtime_set_active()帮助下,在为设备调用pm_runtime_enable()之前,被变更为'active'。

    然而,如果设备有父设备,并且父设备的runtime PM被使能了,为设备调用pm_runtime_set_active()
将会影响父设备,除非父设备的'power.ignore_children'标志被设置. 换句话说,既然那样,只要子设备
的状态是'active',即使子设备的runtime PM还仍然处于禁用状态(例如: pm_runtime_enable()还未
被子设备调用或者已经对设备调用pm_runtime_enable()),父设备就不能够在运行时挂起.由于这个原因,
一旦pm_runtime_set_active()已经对设备调用,pm_runtime_enable()也应该尽快被调用,或者其
runtime PM状态应该被函数pm_runtime_set_suspend()改回'suspended'状态.

    如果默认的最初的设备的runtime PM反映了设备的实际状态,它的bus type的或者驱动的probe()
回调函数将可能被调用并以section 4 描述的PM core的辅助函数来唤醒他.如果是那样的话, 
pm_runtime_resume()应该被使用. 当然,对于这个目的, 设备的runtime PM必须通过调用调用
pm_runtime_resume()尽早使能.

    如果设备bus type的或者driver的probe()回调函数运行pm_runtime_suspend()或者
pm_runtime_idle()或者他们的异步版本,他们将会失败并返回-EAGAIN, 因为设备的使用计数在执行
probe()之前被驱动核心增加,但是,probe()之后就立刻挂起设备是必要的,因此驱动核心使用
pm_runtime_put_sync()来为设别调用子系统级别的idle回调.

    除此而外,驱动核心阻止runtime PM 回调和__device_release_driver()中的总线通知回调发生
竞争,这是必要的,因为通知函数被一些子系统用来执行一些影响runtime PM 功能的操作.他通过在调用
driver_sysfs_remove()和BUS_NOTIFY_UNBIND_DRIVER通知函数之前调用pm_runtime_get_sync()
来做到这一点,如果他在suspended状态,这会恢复设备如果设备是处于挂起状态,并且当这些例程被执行的
时候阻止设备再次被挂起. 

    为了允许bus type和驱动通过从他们的remove()例程调用pm_runtime_suspend()将设备置于
suspended状态,驱动核心在执行__device_release_driver()中的BUS_NOTIFY_UNBIND_DRIVER
通知函数之后执行pm_runtime_put_sync().这要求bus types和驱动使得他们的remove()回调避免
和runtime PM直接竞争,但是他也在设备驱动的移除过程中允许设备的处理有更大的灵活性

    用户空间能够通过改变/sys/devices/.../power/control属性为on来有效的禁止设备驱动来管理
他的电源,这使得pm_runtime_forbid()被调用. 原则上,这种机制也可能被驱动使用,来有效的关闭设备
的runtime电源管理,直到用户空间开启他.也就是说, 在驱动初始化期间,驱动能够确保设备的运行时电源
管理状态为'active',并且调用pm_runtime_forbid().然而应该注意到:如果用户空间已经有意地改变
了/sys/devices/.../power/control的值为"auto"来允许驱动在运行时管理设备的电源,
驱动可能通过以这种方式使用pm_runtime_forbid()来造成混淆。

6. 运行时电源管理和系统睡眠
    运行时电源管理和系统睡眠(例如: 系统挂起和睡眠,也就是众所周知的suspend-to-RAM和
suspend-to-disk)以多种方式彼此交互.如果系统startup的时候,一个设备是active的.所有的东
西都是直接的.但是如果设备已经suspend将会发生什么呢?

    对于runtime PM和system PM,设备可能有不同的唤醒设置.例如,runtime suspend可以使能
远程唤醒,但是许系统睡眠则不可以使能远程唤醒.(device_may_wakeup(dev)返回false).当这种
情况发生之后,子系统级别的system suspend回调负责改变设备的唤醒设置(它可以留给设备驱动级
别的suspend例程). 可能有必要恢复设备并再次将其挂起来做到这一点(子系统级别的system 
suspend回调负责改变设备的唤醒设置),如果驱动使用不同的power level或者对runtime suspend
和system sleep使用其他设置也是一样的。

    在system resume期间,最简单的方式是把所有设备恢复成full power状态,即便他们已经在系统
suspend开始之前被suspend. 这么做有如下理由,包括:
    设备可能需要转换power levels和唤醒设置等;
    远程唤醒时间可能已经被firmware丢失;
    设备的子设备可能需要设备处于full power状态以便唤醒自己 
    设备看来的设备状态可能和设备物理状态不一致.这种情况可能在从hibernation状态恢复的过程中出现
    设备可能需要重启
    尽管设备被挂起了,但如果他的实用计数 >0,那么很可能他在不久的将来会需要一个runtime resume
 

    如果设备在system suspend开始之前已经被挂起,并且他在resume期间被恢复到full power状态,然
后他的runtime PM状态将不得不被更新到反应实际system sleep之后的状态.做到这一点的方法是:

	pm_runtime_disable(dev);
	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);

    PM core在调用suspend()回调之前 总是增加运行时使用计数 并且在调用resume()回调的时候减少
它,因此像这样临时禁止运行时电源管理将不会导致任何runtime suspend attempts成为永久的损失.如果
resume()返回之后使用计数归零,runtime_idle()回调将照常被调用.

    然而,在某些系统中,system sleep不是从全局firmware或者硬件操作进入的.相反,所有的硬件组件
在kernel的协调下直接进入低电状态. 然后系统睡眠状态可以有效地从硬件组件的最终状态跟踪,并且系统从那
个状态被硬件或者完全在kernel控制下的相似机制唤醒。结果,kernel永远无法交出控制权,并且在resume
期间所有设备的状态它都确切的知道。如果是这样的话并且没有上面的哪种情况发生(特别的,如果系统不是从
hibernation状态下唤醒),他可能会更加有效地使已经挂起的设备在system suspended开始之前保持在
suspended状态.

    PM core通过如下操作,尽其所能来减少runtime PM和system suspend/resume/hibernation回调
之间,竟态发生的可能性:
    除了在执行子系统级别的suspend()回调之后为每个设备调用pm_runtime_disable()
在系统suspend期间,子系统级别的suspend()回调执行之前,它还为每一个设备调用 
pm_runtime_get_noresume() 和  pm_runtime_barrier().
    在系统resume期间,他在执行子系统级的resume()回调函数前后,分别为每个设备调用
pm_runtime_enable() 和 pm_runtime_put_sync()    


7. 普通子系统回调

    子系统可能希望通过使用PM core提供的系列通用电源管理回调函数保存代码空间.这些函数在
driver/base/power/generic_ops.c中定义

int pm_generic_runtime_idle(struct device *dev);
    - 如果驱动的runtime_idle()定义了,调用设备驱动提供的runtime_idle()回调,如果返回值为0
      或者回调函数没有被定义则为设备调用pm_runtime_suspend().
   
int pm_generic_runtime_suspend(struct device *dev);
    - 调用设备驱动提供的runtime_suspend()回调并返回其结果.或者,如果没有定义的话返回-EAGAIN

int pm_generic_runtime_resume(struct device *dev);
    - 调用设备驱动提供的runtime_resume()回调并返回其结果,或者,如果没有定义, 则返回-EINVAL   

int pm_generic_suspend(struct device *dev);
    - 如果设备没有在运行时挂起,调用设备驱动提供的suspend()回调并返回其结果,或者,
      如果没有定义的话返回0.

int pm_generic_suspend_noirq(struct device *dev);
    - 如果 pm_runtime_suspend(dev) 返回"false",调用设备驱动提供的suspend_irq(),
      并返回其结果,或者没有定义的话返回0.

int pm_generic_resume(struct device *dev);
    - 调用设备驱动提供的resume()回调,并且如果成功则修改设备的运行时电源状态为'active'

int pm_generic_resume_noirq(struct device *dev);
    - 调用设备驱动提供的resume_noriq()回调

int pm_generic_freeze(struct device *dev);
    - 如果设备没有在运行时挂起,调用设备驱动提供的freeze()回调并返回起结果.
      或者如果没有定义的话返回0.

int pm_generic_freeze_noirq(struct device *dev);
    - 如果pm_runtime_suspended(dev)返回"false",调用设备驱动提供的freeze_noirq()
      并返回其结果,或者如果没有定义的话返回0. 

int pm_generic_thaw(struct device *dev);
    - 如果设备没有在运行时被挂起,调用设备驱动提供的thaw()回调,并返回其结果.或者没有定义的话返回0.

int pm_generic_thaw_noirq(struct device *dev);
    - 如果pm_runtime_suspended(dev)返回"false",调用设备驱动提供的thaw_noirq()
      回调并返回其结果,或者如果没有定义返回0.

int pm_generic_poweroff(struct device *dev);
    - 如果设备在运行时没有被挂起,调用驱动提供的power_off()回调并返回其结果,或者如果没有定义
       返回0

int pm_generic_poweroff_noirq(struct device *dev);
    - 如果pm_runtime_suspended(dev)返回"false",运行设备驱动提供的
        poweroff_noirq()回调并返回其结果,如果没有定义返回0.

int pm_generic_restore(struct device *dev);
    - 调用设备驱动提供的restore()回调,如果成功的话修改设备的电源管理状态为'active'.

int pm_generic_restore_noirq(struct device *dev);
    - 调用设备驱动提供的restore_noirq()回调


    runtime_idle(), 
    runtime_suspend(),
    runtime_resume(), 
    suspend(), 
    suspend_noirq(), 
    resume(),
    resume_noirq(), 
    freeze(), 
    freeze_noirq(), 
    thaw(), 
    thaw_noirq(),
    poweroff(), 
    poweroff_noirq(), 
    restore(), 
    restore_noirq() 
这些函数可以被指定给子系统级别的dev_pm_ops结构中的回调函数指针.



这些函数能够被指定给 
runtime_idle,runtime_suspend,runtime_resume,suspend,suspend_noirq,resume,
resume_noirq,freeze,freeze_noirq,thaw,thaw_noirq,poweroff, poweroff_noirq,
restore, restore_noirq 
dev_pm_ops结构中的子系统级别的回调函数指针.


如果子系统想要同时使用所有这些函数,他能够简单地将GENERIC_SUBSYS_PM_OPS宏指定给他的
dev_pm_ops结构指针,该宏定义在 include/linux/pm.h中.希望使用和system suspend,freeze, 
poweroff和runtime suspend回调相同的函数的设备驱动(对于system resume,thaw,restore
和runtime resume也一样)能够在UNIVERSAL_DEV_PM_OPS宏的帮助下取得这个目标(同时使用'所有'
这些函数的目标)


8. "无回调" 设备


    一些设备只是其父设备的逻辑子设备,并且不能自己进行电源管理.(声明的例子是一个USB接口,
整个USB设备能够进入低电模式或者发送唤醒请求,但是对于单个的结构,两个都不可能).这些设备的
驱动不需要runtime PM 回调;如果回调确实存在,runtime_suspend()和runtime_resume(),
总是会返回0其他什么也不做,而runtime-idle()总是调用pm_runtime_suspend().

   子系统能够通过调用pm_runtime_no_callbacks()把这些设备告诉PM core.这应该在设备结构
被初始化之后并且在其注册之前完成(尽管在设备注册之后也OK).此例程将设置设备的power.no_call-
backs标志,并且阻止非调试runtime PM sysfs 属性被创建.

    当power.no_callbacks被设置的时候,PM core将不会调用runtime_idle(),runtime_sus-
pend()或者runtime_resume()回调.反之,他会认为suspends和resumes总是成功,并且idle的设
备应该被suspend结果,PM core将永远不会将运行时电源状态的改变直接通知子系统或者驱动.相反,父
设备的驱动必须负责将父设备的电源状态改变告诉设备驱动.


9. 自动挂起,或者自动延迟的挂起

    改变设备的电源状态不是没有代价的;需要耗费时间和能量.一个设备只有在有某种理由认为他将长时
间保持在那个状态的情况下才应该被置于低电状态. 一个普遍的观点认为一段时间没有使用的设备倾向于
保持不被使用的状态.在这种建议下,驱动应该允许设备在超过了某个最小周期之后,在运行时挂起.甚至
当这种观点最终发现不是最佳的,他仍然会阻止设备在low-power和full-power之间跳变。


    术语auto-suspend是一个历史残余,它并不是指设备自动被suspend(子系统或者驱动仍然要调用合
适的PM例程);而是指runtime suspend将会自动被延迟直到超过了要求的inactivity周期.
inactivity是基于power.last_busy域来确定的.在执行I/O之后,典型的是就在调用
pm_runtime_put_autosuspend()之前,驱动应该调用pm_runtime_mark_last_busy()来更新这个
域. 要求的inactivity周期的长度是一个策略问题,子系统能够调用
pm_runtime_set_autosuspend_delay()来对这个长度赋初值。但是在设备注册之后,长度应该被用
户空间利用/sys/devices/.../power/autosuspend_delay_ms_attribute来控制。



    为了使用autosuspend,子系统或者驱动必须调用pm_runtime_use_autosuspend()(在设备注册
前更合适),并且在这之后,他们应该使用各种*_autosuspend()辅助函数而不是使用对应的非
autosuspend函数.

	Instead of: pm_runtime_suspend    use: pm_runtime_autosuspend;
	Instead of: pm_schedule_suspend   use: pm_request_autosuspend;
	Instead of: pm_runtime_put        use: pm_runtime_put_autosuspend; 
        Instead of: pm_runtime_put_sync   use: pm_runtime_put_sync_autosuspend.

    驱动也可以继续使用非autosuspend辅助函数;他们将正确执行,不把autosuspend延迟考虑在内.
同样的,如果power.use_autosuspend域没有被设置,那么autosuspend辅助函数将和对应的非
autosuspend函数表现应该是类似的.


    在某些情况下,一个驱动或者子系统可能想要阻止一个设备立即autosuspend,尽管使用计数是0并
且autosuspend延迟时间已经超过。如果runtime_suspend()回调返回-EAGIN或者-EBUSY,如果下
一次autosuspend延迟超时时间是在将来(和他通常会做的一样,如果回调调用
pm_runtime_mark_last_busy())PM core将会自动重新调度autosuspend.runtime_suspend()
回调不能自己执行这个reschduling,因为没有任何种类的suspend请求会在设备suspending的时候
被接受(例如:当这个回调正在执行的时候).

    这些实现很适于中断上下文的异步使用,然而这种用法不可避免的包含了竞争,因为PM core不能用
I/O请求的到来同步runtime_suspend()回调。这个同步必须由驱动以其私有的锁来进行处理.下面是
图解的伪代码:

foo_read_or_write(struct foo_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(struct foo_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 ... */ 
}

int foo_runtime_suspend(struct device *dev)
{ 
    struct foo_priv foo = container_of(dev, ...);
    int ret = 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);
    return ret; 
}
int foo_runtime_resume(struct device *dev)
{ 
    struct foo_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);
    return 0; 
}

    重点是在foo_io_completion()请求autosuspend之后,foo_runtime_suspend()回调可能会
和foo_read_or_write()竞争.因此foo_runtime_suspend()不得不在允许suspend继续之前检查是
否有挂起的I/O请求(当持有私有的锁的时候).
    除此而外,power.autosuspend_delay域能够被用户空间在任何时间修改,如果驱动关心这个域,它
能够在持有他的私有锁的情况下,在runtime_suspend()回调里面调用
pm_runtime_autosuspend_expiration().如果函数返回一个非0值,那么延迟还没有超时,回调应该
返回-EAGAIN.



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值