Linux电源管理(13)_Driver的电源管理

来源:互联网 发布:北京赛车pk10软件 编辑:程序博客网 时间:2024/05/21 10:46

首先,回想一下wowo电源管理系列文章中提到的几个PM特性:

A. WakeUP Count/WakeUp Source

B. Wake Lock

C. Auto Sleep

D. Runtime Suspend


这篇文章就简单简单整理一下以上特性的在Driver中的使用场景,理解可能有偏差,大家多指教。来看看这个几个特性的实现分别在内核代码树的位置:

WakeUp Count/WakeUp Source:

    /linux/driver/base/power/wakeup.c

Wake Lock : 

    对用户层的:/linux/kernel/power/wakelock.c

    对内核层的:/linux/include/linux/wakelock.h

Auto Sleep:

    /linux/kernel/power/autosleep.c

Runtime Suspend:

    /linux/driver/base/power/runtime.c

有关PM的,集中在/kernel/power/目录(PM Core),以及/driver/base/power/目录(PM Function)。一个个来看看在驱动中怎么用这些特性吧。

先来看看WakeUp Count/WakeUp Souce。

刚开始接触这个东西,总容易被Wake Souce这个关键词迷惑,第一反应是把这东西跟HW Wake Souce牵连起来,但其实这东西跟硬件没什么关系,只是一个软件抽象出来的一种设备属性,所以如果在驱动开发过程中,需要该设备具备将系统从Low Power Mode唤醒的功能,还是不要被这个特性的名字所迷惑为好。

那么,这个特性怎么使用呢?回想一下wowo描述这个特性时提到的,wakeup count的目的是为了解决同步问题,也即是为了解决”在睡眠的过程中,接收到事件而想退出睡眠“的问题以及判断“在用户层发起睡眠请求时,当前系统是否符合睡眠条件”,关于wakeup_count在用户层的应用可以简单总结为如下代码:

  1. #define WAKEUP_COUNT "/sys/power/wakeup_count"
  2. #define POWER_STATE "/sys/power/state"
  3.  
  4. int ret;
  5. int wakeup;
  6. FILE *f_wakeup = fopen(WAKE_COUNT, "rb+");
  7. FILE *f_state = fopne(POWER_STATE, "rb+");
  8.  
  9. do {
  10. fscanf(f_wakeup, "%d", &wakeup);
  11. ret = fprintf(f_wakeup, "%d", wakeup);
  12. }while (ret < 0);
  13.  
  14. fprintf(f_state, "mem");

用户层的应用就不过多考虑了,这里主要看Driver层。那么,看看Driver中关于wc/ws的应用场景:1. 在事件处理完之前主动阻止suspend,有点类似于锁,关于这点的应用就是之后要提到的对内核层的wake lock;2. 系统已经suspend了,唤醒系统上报事件,并在时间处理完之前主动阻止suspend,这就跟硬件唤醒源有关系了,而且肯定跟中断有关系。

来看第一种应用:

  1. #include <xxx.h>
  2. ...
  3. #include <linux/platform_device.h>
  4. #include <linux/pm_wakeup.h>
  5.  
  6.  
  7. int xxx(struct device *dev)
  8. {
  9. pm_stay_awake(dev);
  10. ....
  11. pm_relax(dev);
  12. ...
  13. }
  14.  
  15. int xxx_probe(struct platform_device *pdev)
  16. {
  17. struct device *dev = pdev->dev;
  18. ...
  19.  
  20. device_init_wakeup(dev, true);
  21. ...
  22. }
  23.  
  24. int __init xxx_init(void)
  25. {
  26. return platform_driver_register(&xxx_device_driver);
  27. }
  28.  
  29. module_initcall(xxx_init);
  30. MODULE_LICENSE("GPL");
  31.  

device_init_wakeup()给这个device赋予了ws的属性,并且在执行xxx()函数过程可以阻止系统休眠。这种是主观上的阻止,也即驱动开发者预见到这段代码执行过程中不能休眠,从而主动给PM Core报告事件,这种使用场景跟中断没有关系,可以根据需求在任何内核执行路径上报告事件,目的只是为了阻止休眠而已,需要注意的是,这种设置是没办法唤醒已经休眠的系统的。

接下来看一种比较迷惑的情况:

  1. #include <xxx.h>
  2. ...
  3. #include <linux/platform_device.h>
  4. #include <linux/pm_wakeup.h>
  5.  
  6. struct device * dev;
  7.  
  8. int xxx_isr(int irq, void *dev_id)
  9. {
  10. pm_stay_awake(dev);
  11. ....
  12. pm_relax(dev);
  13. return IRQ_HANDLED;
  14. }
  15.  
  16. int xxx_probe(struct platform_device *pdev)
  17. {
  18. int ret;
  19. dev = pdev->dev;
  20. ...
  21. ret = request_irq(irq, xxx_isr, xxx, xxx, xxx);
  22. ...
  23. device_init_wakeup(dev, true);
  24. ...
  25. }
  26.  
  27. int __init xxx_init(void)
  28. {
  29. return platform_driver_register(&xxx_device_driver);
  30. }
  31.  
  32. module_initcall(xxx_init);
  33. MODULE_LICENSE("GPL");



这种情况和IRQ有关,之前以为这么做了之后这个设备就具备硬件唤醒功能了,现在想想还真是....其实,这样做了也只能保证在ISR执行期间不会休眠而已,这个设备中断是否具备硬件唤醒功能和wc/ws还是没什么关系。

那么,当确实需要具备硬件唤醒功能,怎么办呢?这里就要谈及一个硬件概念,唤醒中断源。

这个硬件功能不同厂商的处理方法不一,如三星有些SoC从硬件电气性上就使得某些中断Pin具备了唤醒CPU的功能,再如CSR的SoC规定了只有若干个Pin可以作为唤醒Pin使用,而Atmel的SoC则采用了软件的方式来创造唤醒中断源,也即先将所有中断禁止,之后在使能开发者设置的唤醒中断。说这么多中断的事,是因为Suspend的最终形态是WFI,wait for interrupt。要具备硬件唤醒功能,好歹得是一个能到达CPU Core的中断才行,而这中间经过各级中断管理器,并不说只要是一个中断就能唤醒系统的。

那么,想要具备唤醒系统的功能,就要从中断上下功夫,如下:

  1. #include <xxx.h>
  2. ...
  3. #include <linux/platform_device.h>
  4. #include <linux/pm_wakeup.h>
  5.  
  6. struct device * dev;
  7.  
  8. int xxx_isr(int irq, void *dev_id)
  9. {
  10. pm_stay_awake(dev);
  11. ....
  12. pm_relax(dev);
  13. return IRQ_HANDLED;
  14. }
  15.  
  16. int xxx_probe(struct platform_device *pdev)
  17. {
  18. int ret;
  19. int irq;
  20. int flag;
  21.  
  22. dev = pdev->dev;
  23. ...
  24. ret = request_irq(irq, xxx_isr, flag | IRQF_NO_SUSPEND, xxx, xxx);
  25. ...
  26. enable_irq_wake(irq);
  27. device_init_wakeup(dev, true);
  28. ...
  29. }
  30.  
  31. int __init xxx_init(void)
  32. {
  33. return platform_driver_register(&xxx_device_driver);
  34. }
  35.  
  36. module_initcall(xxx_init);
  37. MODULE_LICENSE("GPL");


这段代码中对中断做了两个特殊的处理,一个是在申请中断时加上了IRQF_NO_SUSPEND, 另一个是irq_enable_wake(irq); 这两个函数都可以赋予IRQ唤醒系统的能力,前者是在suspend过程中dpm_suspend_noirq()->suspend_devices_irq()时保留IRQF_NO_SUSPEND类型的中断响应,而后者直接跟irq_chip打交道,把唤醒功能的设置交由irq_chip driver处理。在Atmel的BSP中,这一个过程只是对一个名为wakeups的变量做了位标记,以便其随后在plat_form_ops->enter()时重新使能该中断。从使用角度我觉得,irq_enable_wake()会是一个更为保险且灵活的方法,毕竟更为直接而且禁用唤醒功能方便,disable_irq_wake()即可。

关于Wake Lock的使用,可以参考关于wc/ws的使用场景的前两种情况, wake_lock()只是对wc/ws应用的一种封装。

可以把wake_lock()当做一种锁来用,其内部使用了__pm_stay_awake()来使系统保持唤醒状态。别忘记,这还是一种开发者主观上的使用,即开发者希望wake_lock() ---> wake_unlock()期间系统不休眠,类似一种特殊的临界区。

同时,相对wc/ws,wake_lock()更像是一种快捷方式,开发者不用去使能设备的唤醒属性,也不用再去主动上报事件,只要在希望保持唤醒的特殊临界区的前后使用wake lock就可以达到目的。

Auto Sleep跟Wake Lock是一对冤家,一个没事就让系统休眠,一个偏不让系统休眠。简单来讲,Auto Sleep就是一直尝试sleep,如通过条件不满足,比如有事件pending(这可能是用户层持有wake_lock, driver持有wake lock,以及上报的pending时间还没有处理完),就返回,过一会再来尝试。所以,如果使能了Auto Sleep这个特性,那写驱动的时候就要考虑到某段代码是否允许休眠后起来接着运行,如果不能,就要使用wake_lock()保护起来。毕竟Auto Sleep这个特性是对于Driver来说,是被动的,异步的,不可预期的,如果Driver不想让PM Core逮着机会就休眠,就干脆别让系统休眠,而是先把自己的事情处理完了再说。

Runtime Suspend相对Auto Sleep而言就是更为Driver主观的行为了,虽然不一定但可以预期,到在调用pm_runtime_put()之后该设备可能进入runtime_idle的状态。runtime_idle的行为是Driver确定的,之后的runtime_suspend也是Driver确定的,PM Core只是在维持设备的引用计数,当确定设备空闲时调用Driver提供的接口,使得设备进入idle或suspend。

所以,开发者需要注意的事情,是保证设备的电源行为符合内核文档所描述的行为,即suspend的状态下,不占用CPU,不与主存交互等(但不一定需要进入low power mode),以及,使得设备的suspend/resume功能正常。另外就是同步问题,pm_runtime_put()之后的代码可能会在runtime_idle之后执行,所以重要的代码等还是在pm_runtime_put()之前完成更好。

抛砖引玉,大家多讨论。

原创粉丝点击