Linux电源管理(8)_Wakeup count功能

作者:wowo 发布于:2014-9-12 23:35 分类:电源管理子系统

1. 前言

Wakeup count是Wakeup events framework的组成部分,用于解决“system suspend和system wakeup events之间的同步问题”。本文将结合“Linux电源管理(6)_Generic PM之Suspend功能”和“Linux电源管理(7)_Wakeup events framework”两篇文章,分析wakeup count的功能、实现逻辑、背后的思考,同时也是对这两篇文章的复习和总结。

2. wakeup count在电源管理中的位置

wakeup count的实现位于wakeup events framework中(drivers/base/power/wakeup.c),主要为两个模块提供接口:通过PM core向用户空间提供sysfs接口;直接向autosleep(请参考下一篇文章)提供接口。

wakeup count architecture

3. wakeup count的功能

wakeup count的功能是suspend同步,实现思路是这样的:

1)任何想发起电源状态切换的实体(可以是用户空间电源管理进程,也可以是内核线程,简称C),在发起状态切换前,读取系统的wakeup counts(该值记录了当前的wakeup event总数),并将读取的counts告知wakeup events framework。

2)wakeup events framework记录该counts到一个全局变量中(saved_count)。

3)随后C发起电源状态切换(如STR),执行suspend过程。

4)在suspend的过程中,wakeup events framework照旧工作(直到系统中断被关闭),上报wakeup events,增加wakeup events counts。

5)suspend执行的一些时间点(可参考“Linux电源管理(6)_Generic PM之Suspend功能”),会调用wakeup  events framework提供的接口(pm_wakeup_pending),检查是否有wakeup没有处理。

6)检查逻辑很简单,就是比较当前的wakeup counts和saved wakeup counts(C发起电源状态切换时的counts),如果不同,就要终止suspend过程。

4. wakeup count的实现逻辑

4.1 一个例子

在进行代码分析之前,我们先用伪代码的形式,写出一个利用wakeup count进行suspend操作的例子,然后基于该例子,分析相关的实现。

   1: do {
   2:     ret = read(&cnt, "/sys/power/wakeup_count");
   3:     if (ret) {
   4:         ret = write(cnt, "/sys/power/wakeup_count");
   5:     } else {
   6:         countine;
   7:     }
   8: } while (!ret);
   9:  
  10: write("mem", "/sys/power/state");
  11:  
  12: /* goto here after wakeup */

例子很简单:

a)读取wakeup count值,如果成功,将读取的值回写。否则说明有正在处理的wakeup events,continue。

b)回写后,判断返回值是否成功,如果不成功(说明读、写的过程中产生了wakeup events),继续读、写,直到成功。成功后,可以触发电源状态切换。

4.2 /sys/power/wakeup_count

wakeup_count文件是在kernel/power/main.c中,利用power_attr注册的,如下(大家可以仔细研读一下那一大段注释,内核很多注释写的非常好,而好的注释,就是软件功力的体现):

   1: /*
   2:  * The 'wakeup_count' attribute, along with the functions defined in
   3:  * drivers/base/power/wakeup.c, provides a means by which wakeup events can be
   4:  * handled in a non-racy way.
   5:  *
   6:  * If a wakeup event occurs when the system is in a sleep state, it simply is
   7:  * woken up.  In turn, if an event that would wake the system up from a sleep
   8:  * state occurs when it is undergoing a transition to that sleep state, the
   9:  * transition should be aborted.  Moreover, if such an event occurs when the
  10:  * system is in the working state, an attempt to start a transition to the
  11:  * given sleep state should fail during certain period after the detection of
  12:  * the event.  Using the 'state' attribute alone is not sufficient to satisfy
  13:  * these requirements, because a wakeup event may occur exactly when 'state'
  14:  * is being written to and may be delivered to user space right before it is
  15:  * frozen, so the event will remain only partially processed until the system is
  16:  * woken up by another event.  In particular, it won't cause the transition to
  17:  * a sleep state to be aborted.
  18:  *
  19:  * This difficulty may be overcome if user space uses 'wakeup_count' before
  20:  * writing to 'state'.  It first should read from 'wakeup_count' and store
  21:  * the read value.  Then, after carrying out its own preparations for the system
  22:  * transition to a sleep state, it should write the stored value to
  23:  * 'wakeup_count'.  If that fails, at least one wakeup event has occurred since
  24:  * 'wakeup_count' was read and 'state' should not be written to.  Otherwise, it
  25:  * is allowed to write to 'state', but the transition will be aborted if there
  26:  * are any wakeup events detected after 'wakeup_count' was written to.
  27:  */
  28:  
  29: static ssize_t wakeup_count_show(struct kobject *kobj,
  30:                                 struct kobj_attribute *attr,
  31:                                 char *buf)
  32: {
  33:         unsigned int val;
  34:  
  35:         return pm_get_wakeup_count(&val, true) ?
  36:                 sprintf(buf, "%u\n", val) : -EINTR;
  37: }
  38:  
  39: static ssize_t wakeup_count_store(struct kobject *kobj,
  40:                                 struct kobj_attribute *attr,
  41:                                 const char *buf, size_t n)
  42: {
  43:         unsigned int val;
  44: int error;
  45:  
  46: error = pm_autosleep_lock();
  47: if (error)
  48:         return error;
  49:  
  50: if (pm_autosleep_state() > PM_SUSPEND_ON) {
  51:         error = -EBUSY;
  52:         goto out;
  53: }
  54:  
  55: error = -EINVAL;
  56: if (sscanf(buf, "%u", &val) == 1) {
  57:         if (pm_save_wakeup_count(val))
  58:                 error = n;
  59: }
  60:  
  61:  out:
  62: pm_autosleep_unlock();
  63: return error;
  64: }
  65:  
  66: tr(wakeup_count);

实现很简单:read时,直接调用pm_get_wakeup_count(注意第2个参数);write时,直接调用pm_save_wakeup_count(注意用户空间的wakeup count功能和auto sleep互斥,会在下篇文章解释原因)。这两个接口均是wakeup events framework提供的接口,跟着代码往下看吧。

4.3 pm_get_wakeup_count

pm_get_wakeup_count的实现如下:

   1: bool pm_get_wakeup_count(unsigned int *count, bool block)
   2: {
   3:         unsigned int cnt, inpr;
   4:  
   5:         if (block) {
   6:                 DEFINE_WAIT(wait);
   7:  
   8:                 for (;;) {
   9:                         prepare_to_wait(&wakeup_count_wait_queue, &wait,
  10:                                         TASK_INTERRUPTIBLE);
  11:                         split_counters(&cnt, &inpr);
  12:                         if (inpr == 0 || signal_pending(current))
  13:                                 break;
  14:  
  15:                         schedule();
  16:                 }
  17:                 finish_wait(&wakeup_count_wait_queue, &wait);
  18:         }
  19:  
  20:         split_counters(&cnt, &inpr);
  21:         *count = cnt;
  22:         return !inpr;
  23: }

该接口有两个参数,一个是保存返回的count值得指针,另一个指示是否block,具体请参考代码逻辑:

a)如果block为false,直接读取registered wakeup events和wakeup events in progress两个counter值,将registered wakeup events交给第一个参数,并返回wakeup events in progress的状态(若返回false,说明当前有wakeup events正在处理,不适合suspend)。

b)如果block为true,定义一个等待队列,等待wakeup events in progress为0,再返回counter。

 

注1:由4.2小节可知,sysfs发起的read动作,block为true,所以如果有正在处理的wakeup events,read进程会阻塞。其它模块(如auto sleep)发起的read,则可能不需要阻塞。

4.4 pm_save_wakeup_count

pm_save_wakeup_count的实现如下:

   1: bool pm_save_wakeup_count(unsigned int count)
   2: {
   3:         unsigned int cnt, inpr;
   4:         unsigned long flags;
   5:  
   6:         events_check_enabled = false;
   7:         spin_lock_irqsave(&events_lock, flags);
   8:         split_counters(&cnt, &inpr);
   9:         if (cnt == count && inpr == 0) {
  10:                 saved_count = count;
  11:                 events_check_enabled = true;
  12:         }
  13:         spin_unlock_irqrestore(&events_lock, flags);
  14:         return events_check_enabled;
  15: }

1)注意这个变量,events_check_enabled,如果它不为真,pm_wakeup_pending接口直接返回false,意味着如果不利用wakeup count功能,suspend过程中不会做任何wakeup events检查,也就不会进行任何的同步

2)解除当前的registered wakeup events、wakeup events in progress,保存在变量cnt和inpr中。

3)如果写入的值和cnt不同(说明读、写的过程中产生events),或者inpr不为零(说明有events正在被处理),返回false(说明此时不宜suspend)。

4)否则,events_check_enabled置位(后续的pm_wakeup_pending才会干活),返回true(可以suspend),并将当前的wakeup count保存在saved count变量中。

4.5 /sys/power/state

再回忆一下“Linux电源管理(6)_Generic PM之Suspend功能”中suspend的流程,在suspend_enter接口中,suspend前的最后一刻,会调用pm_wakeup_pending接口,代码如下:

   1: static int suspend_enter(suspend_state_t state, bool *wakeup)
   2: {
   3:     ...
   4:     error = syscore_suspend();
   5:     if (!error) {
   6:         *wakeup = pm_wakeup_pending();
   7:         if (!(suspend_test(TEST_CORE) || *wakeup)) {
   8:                 error = suspend_ops->enter(state);
   9:                 events_check_enabled = false;
  10:         }
  11:         syscore_resume();
  12:     }
  13:     ...
  14: }

在write wakeup_count到调用pm_wakeup_pending这一段时间内,wakeup events framework会照常产生wakeup events,因此如果pending返回true,则不能“enter”,终止suspend吧!

注2:wakeup后,会清除events_check_enabled标记。

Linux电源管理(7)_Wakeup events framework”中已经介绍过pm_wakeup_pending了,让我们再看一遍吧:

   1: bool pm_wakeup_pending(void)
   2: {
   3:         unsigned long flags;
   4:         bool ret = false;
   5:  
   6:         spin_lock_irqsave(&events_lock, flags);
   7:         if (events_check_enabled) {
   8:                 unsigned int cnt, inpr;
   9:  
  10:                 split_counters(&cnt, &inpr);
  11:                 ret = (cnt != saved_count || inpr > 0);
  12:                 events_check_enabled = !ret;
  13:         }
  14:         spin_unlock_irqrestore(&events_lock, flags);
  15:  
  16:         if (ret)
  17:                 print_active_wakeup_sources();
  18:  
  19:         return ret;
  20: }

a)首先会判断events_check_enabled是否有效,无效直接返回false。有效的话:

b)获得cnt和inpr,如果cnt不等于saved_count(说明这段时间内有events产生),或者inpr不为0(说明有events正在被处理),返回true(告诉调用者,放弃吧,时机不到)。同时清除events_check_enabled的状态。

c)否则,返回false(放心睡吧),同时保持events_check_enabled的置位状态(以免pm_wakeup_pending再次调用)。

Okay,结束了,等待wakeup吧~~~~

 

原创文章,转发请注明出处。蜗窝科技,www.wowotech.net

标签: Linux 电源管理 wakeup count

评论:

koala
2015-06-30 20:13
注意这个变量,events_check_enabled,如果它不为真,pm_wakeup_pending接口直接返回false,意味着如果不利用wakeup count功能,suspend过程中不会做任何wakeup events检查,也就不会进行任何的同步。
------如果events_check_enabled不为真的话,应该不会跑到pm_wakeup_pending的吧。会发生wakeup_count_store回写错误,用户空间进程被终止。所以"也就不会进行任何的同步"不会存在这种情况的吧。
koala
2015-06-30 20:17
@koala:所以,只要跑到pm_wakeup_pending时,events_check_enabled肯定会被置1的,不然pm_wakeup_pending返回0,suspend_ops->enter(state)一定会被执行。
蜗蜗
2015-06-30 21:28
@koala:可能您理解错我的意思了,/sys/power/state可以在不操作wakeup count的前提下使用的,在这种情况下,suspend过程会跑到pm_wakeup_pending,由于events_check_enabled为false,则不会进行任何的检查,继续suspend过程。
其实这就是传统suspend的方法,即不依赖wakeup count时的方法,此时kernel不会做任何同步处理。
koala
2015-06-29 15:39
hello wowo:
请问伪代码的调用逻辑是在哪个地方?是在autosuspend_wakeup_count.c的suspend_thread_func()中吗?
该函数是在autosuspend_wakeup_count_init定义的,但是MT6753平台(5.1)select earlysuspend,而MSM8916(4.4)select autosleep。两个平台均没有用wakeup count机制。
wowo
2015-06-29 20:09
@koala:这个我还真没有研究过。正如你所说,Android中是通过system/core/libsuspend/库调用的,调用的地方,我也不熟悉。
andy01011501
2016-05-14 14:59
@koala:应该是在suspend_thread_func()这里。是否使用wakeupcount这个机制,要看autosuspend_init这里面,它初始化了三种机制,分别是earlysuspend、autosleep和wakeup_count,只有前面两种不成功,才会使用wakeupcount。
然后autosuspend_wakeup_count_init,创建了suspend_thread_func()。
sam
2016-05-31 10:51
@andy01011501:mtk Android M最终选择了wakeupcount
Roy
2015-04-01 16:45
蜗蜗:
    打扰了,我在android L上碰到一个问题,libsuspend.so读取wakeup_count时,hold了不能返回,已知在pm_get_wakeup_count一直有事件工作,请问有什么方法跟踪什么原因导致hold了?十分感谢。
wowo
2015-04-01 18:38
@Roy:可以这样:
1. 先确认一下您的系统是否使能了trace功能(默认情况下都会使能)。
2. 如果使能,会存在“/sys/kernel/debug/tracing/”目录。
3. echo 1 > /sys/kernel/debug/tracing/events/power/wakeup_source_activate/enable,使能wakeup source activate的trace输出。
4. cat /sys/kernel/debug/tracing/trace,查看哪些driver一直处于activate状态,输出信息如下:
# entries-in-buffer/entries-written: 132/132   #P:1
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
    kworker/u8:0-28958 [000] d..2 11250.490538: wakeup_source_activate: delay_lock state=0x2f900004
    kworker/u8:0-28958 [000] d..4 11250.491522: wakeup_source_activate: atc260x-wall state=0x2f910004
    kworker/u8:0-28958 [000] d..4 11250.491553: wakeup_source_activate: atc260x-usb state=0x2f910005
     kworker/0:0-25134 [000] d..4 11250.491645: wakeup_source_activate: NETLINK state=0x2f910006
主要包括driver的名称,和当前的“combined_event_count”的值。

希望能给您一些参考。
Roy
2015-04-01 18:46
@wowo:我会按照你的建议试试,感谢你的回复
xxfxxf33
2015-02-05 11:27
好的  我先去了解一下这方面的知识  谢谢
xxfxxf33
2015-01-26 20:11
蜗蜗 我有一点不太明白,希望你能帮助我理解一下 在本文的如下描述中

“4.5 /sys/power/state

再回忆一下“Linux电源管理(6)_Generic PM之Suspend功能”中suspend的流程,在suspend_enter接口中,suspend前的最后一刻,会调用pm_wakeup_pending接口,代码如下:”

如果在执行 error = suspend_ops->enter(state);的时候系统产生wake_events  系统会终止sleep吗?为什么
谢谢
wowo
2015-01-26 22:48
@xxfxxf33:这个问题的关键是:关中断。由于执行 error = suspend_ops->enter(state);之前,local irq已经被关闭,这个过程如果产生了wakeup event,CPU是无法处理的,也就是所谓的pending状态。
如果有irq pending状态,我相信CPU suspend enter的指令,会不能正确执行(这是我的猜测,我们可以在实际系统验证一下),因此会直接返回err。
xxfxxf33
2015-02-04 18:46
@wowo:wowo  我验证了你的猜测  上述操作会导致CPU无法休眠  但是所有的中断都被disable了,不明白这里的Pending状态是什么意思。
wowo
2015-02-04 20:01
@xxfxxf33:这要先看一下Linuxer同学写的中断子系统的文章。
简单的说,irq disable,是指“CPU响应中断的能力被disable了”,即在中断产生的情况下,CPU不能响应,所以不能调用我们的中断handler。
但是irq disable了,不代表中断不能产生,中断控制器(如GIC),一样可以产生中断,并把中断的状态(称作pending状态)保存在一定的位置。CPU在suspend前,可以查询这些pending状态,如果有中断pending,CPU就可以退出suspend。
cracker
2016-10-20 16:36
@wowo:这里我还是很疑惑,如下:
1、error = syscore_suspend();是不是说代码执行到这里,cpu停止工作进入suspend?
2、suspend如果GIC产生中断,会唤醒cpu并从代码syscore_suspend()退出并开始执行下面的代码
*wakeup = pm_wakeup_pending();
        if (!(suspend_test(TEST_CORE) || *wakeup)) {
            trace_suspend_resume(TPS("machine_suspend"),
                state, true);
            error = suspend_ops->enter(state);
            trace_suspend_resume(TPS("machine_suspend"),
                state, false);
            events_check_enabled = false;
        } else if (*wakeup) {
            pm_get_active_wakeup_sources(suspend_abort,
                MAX_SUSPEND_ABORT_LEN);
            log_suspend_abort_reason(suspend_abort);
            error = -EBUSY;
        }
        syscore_resume()
因为wakeup_count给driver和用户空间使用,这里用户进程被suspend,pm_wakeup_pending返回true是dirver report event的吗?可是irq不是被disable了吗?
wowo
2016-10-20 17:11
@cracker:syscore_suspend被调用的时候,中断才刚刚禁止,这时调用pm_wakeup_pending检查wakeup event,检查的是在这之前(中断被禁止之前)产生的event,也是说是driver通过中断handler上报的event。
cracker
2016-10-20 18:18
@wowo:在_device_suspend如下代码
    if (pm_runtime_barrier(dev) && device_may_wakeup(dev))
        pm_wakeup_event(dev, 0);(1)

    if (pm_wakeup_pending()) {       (2)
        pm_get_active_wakeup_sources(suspend_abort,
            MAX_SUSPEND_ABORT_LEN);
        log_suspend_abort_reason(suspend_abort);
        async_error = -EBUSY;
        goto Complete;
    }
这段代码(2)处不是在做设备睡眠前的最后检查吗?因为这里检查完就调用dirver->pm->suspend去关中断了。如果设备没有wakeup event,才会走到syscore_suspend

另外,(1)处的作用是什么呢?

2014-09-18 15:56
上篇文章的这些counter,很早在看内核文档时候,那时候还不了解,这次明白了。
谢谢!
蜗蜗
2014-09-18 23:47
@海:不用客气,我的理解也不一定准确,可以多讨论。

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog