蜗窝科技

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

Physh · · 电源管理子系统 · 阅读 37957 · 2014年12月存档

首先,回想一下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在用户层的应用可以简单总结为如下代码:

#define WAKEUP_COUNT   "/sys/power/wakeup_count"

#define POWER_STATE      "/sys/power/state"



int ret;

int wakeup;

FILE *f_wakeup = fopen(WAKE_COUNT, "rb+");

FILE *f_state = fopne(POWER_STATE, "rb+");



do {

    fscanf(f_wakeup, "%d", &wakeup);

    ret = fprintf(f_wakeup, "%d", wakeup);

}while (ret < 0);



fprintf(f_state, "mem");

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

来看第一种应用:

#include <xxx.h>

...

#include <linux/platform_device.h>

#include <linux/pm_wakeup.h>





int xxx(struct device *dev)

{

	pm_stay_awake(dev);

	....

	pm_relax(dev);

	...

}



int xxx_probe(struct platform_device *pdev)

{

	struct device *dev = pdev->dev;

	...



	device_init_wakeup(dev, true);

	...

}



int __init xxx_init(void)

{

	return platform_driver_register(&xxx_device_driver);

}



module_initcall(xxx_init);

MODULE_LICENSE("GPL");

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

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

#include <xxx.h>

...

#include <linux/platform_device.h>

#include <linux/pm_wakeup.h>



struct device * dev;



int xxx_isr(int irq, void *dev_id)

{

	pm_stay_awake(dev);

	....

	pm_relax(dev);

	

	return IRQ_HANDLED;

}



int xxx_probe(struct platform_device *pdev)

{

	int ret;

	dev = pdev->dev;

	...

	ret = request_irq(irq, xxx_isr, xxx, xxx, xxx);

	...

	device_init_wakeup(dev, true);

	...

}



int __init xxx_init(void)

{

	return platform_driver_register(&xxx_device_driver);

}



module_initcall(xxx_init);

MODULE_LICENSE("GPL");

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

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

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

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

#include <xxx.h>

...

#include <linux/platform_device.h>

#include <linux/pm_wakeup.h>



struct device * dev;



int xxx_isr(int irq, void *dev_id)

{

	pm_stay_awake(dev);

	....

	pm_relax(dev);	

	return IRQ_HANDLED;

}



int xxx_probe(struct platform_device *pdev)

{

	int ret;

	int irq;

	int flag;



	dev = pdev->dev;

	...

	ret = request_irq(irq, xxx_isr, flag | IRQF_NO_SUSPEND, xxx, xxx);

	...

	enable_irq_wake(irq);

	device_init_wakeup(dev, true);

	...

}



int __init xxx_init(void)

{

	return platform_driver_register(&xxx_device_driver);

}



module_initcall(xxx_init);

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()之前完成更好。

抛砖引玉,大家多讨论。

标签:Kerneldriver

继续讨论

新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。

评论(32 条,只读存档)

以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

  • ecwmin

    "Auto Sleep跟Wake Lock是一对冤家,一个没事就让系统休眠,一个偏不让系统休眠...如果Driver不想让PM Core逮着机会就休眠,就干脆别让系统休眠,而是先把自己的事情处理完了再说." 写的真形象,多谢博主分享。
    • Physh

      @ecwmin:谢谢肯定,大家多多讨论,一个人的观点毕竟有限
  • haichunzhao

    Wake Count/Wake Souce最好还是写成WakeUp Count/WakeUp Source。 》wake count的目的是为了解决同步问题,也即是为了解决”在睡眠的过程中,接收到事件而想退出睡眠“的问题 这是其中一个方面;还有一方面决定是否发起睡眠动作。如果动作是由用户空间发起的,首先去读取wakeup count值,然后检测用户空间是否准备好睡眠,将读取的wakeup count值再写入节点,如果成功,再向/sys/power/state中写入sleep。
    • Physh

      @haichunzhao:嗯嗯,回头改了。
  • yang

    01-01 00:54:39.863 D/PowerManagerNotifier( 866): mGoToSleepBroadcastDone - sendNextBroadcast 01-01 00:29:13.659 V/kernel ( 3449): <6>[ 1836.671959] PM: suspend entry 2014-12-31 16:54:39.872099731 UTC 01-01 00:29:13.659 V/kernel ( 3449): <6>[ 1836.671968] PM: Syncing filesystems ... done. 01-01 00:29:13.672 V/kernel ( 3449): <6>[ 1836.685205] Freezing user space processes ... (elapsed 0.002 seconds) done. 01-01 00:29:13.674 V/kernel ( 3449): <6>[ 1836.686529] [ALS/PS] ltr559_irq_handler, in 01-01 00:29:13.674 V/kernel ( 3449): <6>[ 1836.687035] [ALS/PS] ltr559_schedwork, status=0X33 01-01 00:29:13.674 V/kernel ( 3449): <6>[ 1836.687041] [ALS/PS] ltr559_schedwork, ps interrupt 01-01 00:29:13.675 V/kernel ( 3449): <6>[ 1836.687345] Freezing remaining freezable tasks ... (elapsed 0.001 seconds) done. 01-01 00:29:13.675 V/kernel ( 3449): <6>[ 1836.687616] [ALS/PS] read_ps_adc_value, ps_val=174 01-01 00:29:13.675 V/kernel ( 3449): <6>[ 1836.687636] [ALS/PS] report_ps_input_event_zte, ps far 01-01 00:29:13.676 V/kernel ( 3449): <6>[ 1836.688350] [ALS/PS] report_ps_input_event_zte, adc_value=174,default_ps_lowthresh=185,default_ps_highthresh=214 01-01 00:29:13.676 V/kernel ( 3449): <6>[ 1836.689101] Suspending console(s) (use no_console_suspend to debug) 01-01 00:29:13.680 V/kernel ( 3449): <6>[ 1836.693195] fb_notifier_callback. 01-01 00:29:13.680 V/kernel ( 3449): <6>[ 1836.693206] mdss_dsi_event_handler: event=7 01-01 00:29:13.681 V/kernel ( 3449): <6>[ 1836.693222] fb_notifier_callback. 01-01 00:29:13.682 V/kernel ( 3449): <6>[ 1836.694800] ltr559_suspend 01-01 00:29:13.699 V/kernel ( 3449): <6>[ 1836.711522] PM: suspend of devices complete after 21.136 msecs 01-01 00:29:13.700 V/kernel ( 3449): <6>[ 1836.713124] PM: late suspend of devices complete after 1.594 msecs 01-01 00:29:13.703 V/kernel ( 3449): <6>[ 1836.715527] PM: noirq suspend of devices complete after 2.396 msecs 01-01 00:29:13.703 V/kernel ( 3449): <6>[ 1836.715532] Disabling non-boot CPUs ... 01-01 00:29:13.705 V/kernel ( 3449): <4>[ 1836.717235] IRQ39 no longer affine to CPU2 01-01 00:29:13.706 V/kernel ( 3449): <6>[ 1836.718347] CPU0:msm_cpu_pm_enter_sleep mode:3 during suspend 01-01 00:29:13.706 V/kernel ( 3449): <6>[ 1836.718347] Enabled clocks: 01-01 00:29:13.706 V/kernel ( 3449): <6>[ 1836.718347] xo_a_clk_src:2:2 [19200000] 01-01 00:29:13.706 V/kernel ( 3449): <6>[ 1836.718347] bimc_msmbus_a_clk:1:1 [532938752] -> bimc_a_clk:1:1 [532938752] 01-01 00:29:13.706 V/kernel ( 3449): <6>[ 1836.718347] pcnoc_keepalive_a_clk:1:1 [19200000] -> pcnoc_a_clk:1:1 [19200000 wowo,我遇到一个问题,上述log,系统在suspend过程中ltr559驱动上报了一个消息,但这个消息未及时传递到上层,只能在下一次其他中断源唤醒系统后才处理ltr559的消息。 已经设定了irq_enable_wake(irq);但是没设IRQF_NO_SUSPEND标志。我加上IRQF_NO_SUSPEND标志会有改善吗? 谢谢
    • wowo

      @yang:sorry,忘了在帖子下面回复了。
  • wowo

    从日志里面可以看到ltr559的中断已经被处理了(ltr559_irq_handler),但是handler中schedule了一个thread,而此时系统在suspend的过程中,已经没有机会调度了。所以这种情况irq_enable_wake(irq)和IRQF_NO_SUSPEND是解决不了的。需要使用Wakeup events framework,阻止系统suspend,具体可以参考“/pm_subsystem/wakeup_events_framework.html”中的描述。
    • yang

      @wowo:是的,加了pm_wakeup_event才可以
    • koala

      @wowo:但是handler中schedule了一个thread,而此时系统在suspend的过程中,已经没有机会调度了。 ===================================================== wowo,我的理解是: ltr559_schedwork 可以看到handler中schedule的thread能够被调度了。 report_ps_input_event_zte, ps far 并且调度中能上报中断,只是现在用户空间进程被冻结了,所以上层无法理睬这个input event而已。
      • wowo

        @koala:我想当然的认为freeze之后,内核线程无法调度了,事实还真不一定是这样。是不是您所说的这个样子,我也不太确定。我没有看过源码,不知道ltr559_schedwork是否是在线程中打印的。
  • xuchu

    请教个问题,在suspend的时候我做了disable irq,resume的时候我做了enable irq的动作。但出现系统唤醒后,有中断来了,一直没有进中断处理函数,这个中断一直处于pending状态???
    • wowo

      @xuchu:可以先确定一下,这个时候IRQ是否处于enable状态?
      • xuchu

        @wowo:恩,这个时候IRQ是处于enable状态,中断也有触发,就是没有进入中断处理函数
        • wowo

          @xuchu:系统行为是什么样子的?是不是在别的中断处理函数中出不来了?不然的话,如果中断处于pending状态了,没理由不调用handler。或者cat /proc/interrupts,查看一下各个中断的状态。
          • xuchu

            @wowo:刚才复现了这个问题,发现中断被mask掉了,但有调用enable_irq,为什么还被mask掉了呢?
            • wowo

              @xuchu:可能是enable和disable调用不对称,或者代码里面可能有同步问题。可以从这个思路去查一下。
              • xuchu

                @wowo:enable和disable对称我check了,请教两个问题: 1.驱动中仅仅申请完irq,这个时候irq默认是enable还是disable? 2.你提到的可能有同步问题是指?我还没理解。。
  • wowo

    1.驱动中仅仅申请完irq,这个时候irq默认是enable还是disable? enable 2.你提到的可能有同步问题是指?我还没理解。。 从你的信息中(复现了问题),说明这个问题不是必现的??如果不必现,那么正常逻辑应该没问题,会不会是enable时没有原子操作,导致结果异常?
    • wowo

      @wowo:另外,除了你显式的调用enable/disable,在suspend/resume的过程中,也可能会被irq模块调用。你查一下。
      • xuchu

        @wowo:好的,多谢。
  • passerby

    @wowo,当CPU从睡眠被唤醒时。PC指针会指向0还是?寄存器上下文这些东东是怎么被恢复的?我们在suspend之前将这些信息保存在那些地方的呢?
    • wowo

      @passerby:这个和具体平台的实现有关,有的平台,resume之后,会继续执行。有的平台,会保存回来的地址,然后让CPU core直接断电,resume后,重新执行,并跳转到保存的地址。
  • edwin.yan

    wowo你好,我现在调试的Android的内核版本是3.14,在suspend过程中跑到input子系统时会一直卡住无法休眠,通过调试找到的原因是在卡在进入input子系统suspend时的input_dev_release_keys()这个函数中,当系统注册了带按键功能的驱动时,在suspend的时候就会遇到驱动的wakelock锁从而无法suspend,不知wowo和linuxer大神有何建议修改?(注:input_dev_release_keys()函数在内核3.14版本中由resume函数提前到了suspend中,由于在suspend设备之前已经关闭了进程和线程,所以此函数中的lock锁上层无法响应解锁,所以会一直卡住,因为Google官方的内核也就使用到3.10版本,所以也不知道这个是不是系统的bug)
    • wowo

      @edwin.yan:之前我们是不是在邮件中讨论过这个事情?其实我对input子系统不是很了解,您描述的这种情况,我有一些疑问: 1. input_dev_release_keys中,模拟的按键up事件,会经过具体的driver吗? 2. input_dev_release_keys中,模拟的按键up事件,会试图上报wakeup event吗? 我觉得,对driver来说,它只会在检测到event和上报event之间,拿wakeup锁。上报之后,就会relex这个锁。所以,我还是建议,你确定一下最终阻止系统休眠的event到底是哪一个?例如,执行如下命令: cat /sys/kernel/debug/wakeup*
  • hongxiaoh

    请问 这个Enable clocks该怎么看? 是谁在睡眠中 没有关闭时钟呢? 最近发现suspend后,电流很大,发现有时钟没关,但是对时钟的log 不清楚。 Enabled clocks: 01-01 00:29:13.706 V/kernel ( 3449): <6>[ 1836.718347] xo_a_clk_src:2:2 [19200000] 01-01 00:29:13.706 V/kernel ( 3449): <6>[ 1836.718347] bimc_msmbus_a_clk:1:1 [532938752] -> bimc_a_clk:1:1 [532938752] 01-01 00:29:13.706 V/kernel ( 3449): <6>[ 1836.718347] pcnoc_keepalive_a_clk:1:1 [19200000] -> pcnoc_a_clk:1:1 [19200000
    • wowo

      @hongxiaoh:建议你可以去代码里面找一下打印对应的地方,如果实在找不到,说一声我帮你找:-)
      • hongxiaoh

        @wowo:这么快回复。微信5元饮料已发。 xiaoelectron 再次感谢。
  • leasy

    wowo我想问下如果查看唤醒源。 Android7.0平台,看log发现进入深度休眠前会先尝试进入sleep,最终退出(大概5秒)后,第二次进入sleep后持续稳定,直至用户去唤醒他。我不知道是什么方便导致的第一次sleep很快就退出了。第一次sleep的log如下: [ 828.004427] PM: suspend entry 2018-04-27 09:07:12.854119570 UTC [ 828.004445] PM: Syncing filesystems ... done. [ 828.026200] PM: Preparing system for mem sleep [ 828.028988] Freezing user space processes ... [ 828.082561] Error: returning -512 value [ 828.086992] (elapsed 0.057 seconds) done. [ 828.087022] Freezing remaining freezable tasks ... (elapsed 0.008 seconds) done. [ 828.095523] PM: Entering mem sleep [ 828.095540] Suspending console(s) (use no_console_suspend to debug) [ 828.164952] PM: suspend of devices complete after 65.764 msecs [ 828.171029] PM: late suspend of devices complete after 6.050 msecs [ 828.181415] PM: noirq suspend of devices complete after 10.366 msecs [ 828.181578] Disabling non-boot CPUs ... [ 828.189844] migrate_irqs: 15775 callbacks suppressed [ 828.189851] IRQ7 no longer affine to CPU1 [ 828.189857] IRQ8 no longer affine to CPU1 [ 828.189865] IRQ9 no longer affine to CPU1 [ 828.189872] IRQ10 no longer affine to CPU1 [ 828.189880] IRQ11 no longer affine to CPU1 [ 828.189886] IRQ12 no longer affine to CPU1 [ 828.189894] IRQ13 no longer affine to CPU1 [ 828.189901] IRQ14 no longer affine to CPU1 [ 828.189908] IRQ15 no longer affine to CPU1 [ 828.189916] IRQ16 no longer affine to CPU1 [ 828.279435] Suspended for 0.000 seconds [ 828.280625] Enabling non-boot CPUs ... [ 828.288770] CPU1 is up [ 828.297005] CPU2 is up [ 828.303590] CPU3 is up [ 828.314480] CPU4 is up [ 828.322787] CPU5 is up [ 828.331437] CPU6 is up [ 828.339823] CPU7 is up [ 828.343668] PM: noirq resume of devices complete after 2.891 msecs [ 828.350323] PM: early resume of devices complete after 2.737 msecs [ 828.481451] PM: resume of devices complete after 131.102 msecs [ 828.486712] PM: Finishing wakeup. [ 828.486725] Restarting tasks ... done. [ 828.579814] PM: suspend exit 2018-04-27 09:07:17.502919464 UTC 其中migrate_irqs: 15775 callbacks suppressed这log在第二次sleep的时候没打印,也不清楚是为什么。
  • arara

    系统suspend时会调用设备的RPM suspend吗
  • nowar

    IRQF_NO_SUSPEND这个标志的中断源在系统已经睡下去的时候还会持续发出中断吗?睡下后是不根本就不触发了还是触发但是cpu不处理?
    • smcdef

      @nowar:IRQF_NO_SUSPEND 仅仅是STR 的时候,不关闭中断。这并不代表具有唤醒系统的能力。如果需要唤醒系统。请调用 enable_irq_wake(),同时 drop 这个 flag。因为这两个玩意是水火不容。
      • nowar

        @smcdef:我知道IRQF_NO_SUSPEND没有唤醒的能力。问的是它在STR以后(已经睡下去了), 这个时候它还有中断发出来吗?