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(请参考下一篇文章)提供接口。
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。

评论:
2020-03-12 10:45
首先会判断events_check_enabled是否有效,无效直接返回false。
在kernel/power/suspend_test.c中test_suspend进入休眠的流程events_check_enabled == false,那休眠的整个过程中,pm_wakeup_pending的返回值都是false,没有去检测是否有event产生。
是否存在其他events_check_enabled == false时,系统进入休眠的情况?
2017-11-01 11:07
在N上遇到有很多的wakeup source:
Line 571: 09-16 09:44:07.490 0 0 I : [460489.904631,1] last active wakeup source: ipc000002b2_PowerManagerSer
Line 657: 09-16 09:44:09.380 0 0 I : [460491.346436,0] last active wakeup source: ipc000002b2_PowerManagerSer
Line 717: 09-16 09:44:11.446 0 0 I : [460492.174366,2] last active wakeup source: ipc000002b2_PowerManagerSer
Line 895: 09-16 09:44:17.257 0 0 I : [460494.912370,1] last active wakeup source: ipc000002b2_PowerManagerSer
Line 987: 09-16 09:44:18.967 0 0 I : [460496.271585,2] last active wakeup source: ipc000002b2_PowerManagerSer
Line 1106: 09-16 09:44:24.730 0 0 I : [460501.129454,2] last active wakeup source: ipc000002b2_PowerManagerSer
请问下你知道这是怎么产生的吗??
2017-09-12 16:06
请教一个问题:
我在高通的power key driver中看到,当设备处于suspend mode的时候,按power key唤醒设备,中断处理仅仅通过input子系统上报键值。且上报前没有任何的延迟操作。
所以我觉得这个时候,用户空间进程应该会存在还未resume回来或者没有被调度到的情况,而autosleep timeout,紧接着再次进入suspend mode。
而我现在看到的现象是从没出现过这种情况,我现在就是不清楚是像我猜测的那样?
还是每次用户空间都能准时收到?又或者Linux的电源管理部分有相关的地方保证了消息的发送处理?
2017-09-13 16:39
1)用户空间程序(例如android的EventHub,frameworks/native/services/inputflinger/EventHub.cpp)一直都poll(等待)在key event上。
2)当power key事件上报的时候,kernel会wakeup等待队列,以便让用户空间程序执行并读取到键值。
3)在等待的线程被执行之前,系统不能(或者没有机会)再次被休眠(autosleep也是在线程中?),直到等待的线程醒来。
4)处理键值的线程醒来执行后,就好办了~~~
2017-03-21 23:16
pm_save_wakeup_count中的
3)如果写入的值和cnt不同(说明读、写的过程中产生events),或者inpr不为零(说明有events正在被处理),返回false(说明此时不宜suspend)。
以及pm_wakeup_pending中的
b)获得cnt和inpr,如果cnt不等于saved_count(说明这段时间内有events产生),或者inpr不为0(说明有events正在被处理),返回true(告诉调用者,放弃吧,时机不到)。同时清除events_check_enabled的状态。
inpr不为0,停止suspend能理解,而对于“写入的值和cnt不同”和“cnt不等于saved_count”,cnt不是记录的已经处理完成的event的个数吗?如果event已经处理完成了,为什么要停止suspend而不是继续suspend呢?
2016-11-16 11:44
你好,有个问题请教下,我想建立一个驱动,里面实现suspend/resume接口,目的是在系统休眠和唤醒时分别被调用,用来实现一些功能控制外设的功能, 目前已经成功注册到Linux pm模块。当我手动执行echo mem > sys/power/state的时候,我写的suspend/resume能够成功执行。但是,现在的问题是,当我的Android设备自动进入休眠或者手按power键的时候,我的suspend/resume函数没有被调用,我追了一下,发现系统休眠的时候,貌似并没有执行sys/power/state这个节点,难道Android系统进入休眠的时候不走Linux的PM机制?
期待您的答复!
2016-11-16 13:05
2016-10-20 18:36
12 分钟前
@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)处的作用是什么呢?
===============================================
并不是所有设备都希望关中断,特别是那些有唤醒能力的设备。另外没有规定dirver->pm->suspend一定会关中断。
(1)处的作用,正好是处理那些有唤醒能力的设备,怎么处理呢?参考代码的注释就行了(确保此时这个设备能醒来,以便为当前的事情做准备):
/*
* If a device configured to wake up the system from sleep states
* has been suspended at run time and there's a resume request pending
* for it, this is equivalent to the device signaling wakeup, so the
* system suspend operation should be aborted.
*/
2016-10-20 19:05
2、我有最简单的疑问,Android N上插着usb,然后执行echo mem >sys/power/state,可以看到屏幕背光灭了,但是cat proc/kmsg log:<6>[ 3769.251077][2016-01-03 20:36:55.759322763]PM: suspend entry 2016-01-03 20:36:55.745081096 UTC
<6>[ 3769.259538][2016-01-03 20:36:55.839104013]PM: Syncing filesystems ... done.
<7>[ 3769.339710][2016-01-03 20:36:55.839118492]PM: Preparing system for mem sleep
<6>[ 3769.581798][2016-01-03 20:36:56.122554950]Restarting tasks ... done.
<6>[ 3769.620337][2016-01-03 20:36:56.122567710]PM: suspend exit 2016-01-03 20:36:56.114344950 UTC
不明白屏幕为什么会灭,手动写节点,在freeze task被终止suspend,没处理到device啊
另外,终止suspend的原因是usb锁没释放吗,我看wake_lock节点下有PowerManagerService.Display和PowerManagerService.WakeLocks这两个锁
2016-08-26 16:58
有一事不明,请教老师,主要就是内核都睡下去了,如何才醒得过来:
假设是STR,在函数suspend_enter中,最终调用如下函数进入了睡眠中。
error = suspend_ops->enter(state);
当函数返回的时候,睡眠就结束了,开始了resume的过程。
这个suspend_ops->enter(state);中做了些什么事情呢?它怎么能突然就醒了呢?
syscore_suspend()函数似乎是在irq禁止的情况下调用的。所以suspend_ops->enter(state)这个里面,中断是不是也禁止了?
就算中断没有禁止,中断处理器和这个函数处于两个执行线程中,如何从中断处理器中通知suspend_ops->enter(state)有wakeup事件需要醒过来呢?
能以ARM为例介绍一下吗?非常感谢老师了。
2016-04-18 16:36
先介绍一下背景,android4.4+kernel3.10
我们设计要求是当用户按下电源按键(用户请求系统休眠)时,系统必须无条件进入休眠。但用户再次按下电源键时必须无条件唤醒系统。
目前我们我们修改了powermanger中wakelock的机制保证满足我们的设计要求。
现在测试发现一个问题:有时候用户按电源键试图唤醒系统时,并没有将系统唤醒。通过log分析发现当用户按下电源键时,内核已经被唤醒了,但是紧接着又进入了suspend过程。
现在不清楚是什么原因。
2015-08-03 16:38
2015-08-03 21:42
2015-08-04 14:46
GIC和各个CPU的接口包括两种硬件信号:
(a)触发CPU中断的信号。nIRQCPU和nFIQCPU信号线,熟悉ARM CPU的工程师对这两个信号线应该不陌生,主要用来触发ARM cpu进入IRQ mode和FIQ mode。
(b)Wake up信号。nFIQOUT和nIRQOUT信号线,去ARM CPU的电源管理模块,用来唤醒CPU的
因此disable了CPU的中断仅仅是堵死了中断这一个分支而已,wakeup信号仍在正常工作中。
当CPU休眠的时候,中断关闭的时候,wakeup信号怎么才能产生呢?比如:外部一个按键按钮按下或者点击触摸屏的时候,GIC捕获到wakeup信号了?
在系统suspend之后,捕获这种特定信号来唤醒,在driver里注册中断是不是加入IRQF_NO_SUSPEND这个标识就可以做到了?
功能
最新评论
文章分类
随机文章
文章存档
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2024-04-20 12:29
我遇见有一个唤醒锁一直无法关闭,我想强制关闭这个唤醒锁,我应该怎样去调用接口强制关闭这个唤醒锁呢