wakeup count在电源管理中得位置
wakeup count得实现位于wakeup events framework中(drivers/base/power/wakeup.c),主要为两个模块提供接口: 通过PM core向用户空间提供sysfs接口,直接向autosleep提供接口。
wakeup count得功能
wakeup count得功能是suspend同步,实现思路如下:
1) 任何想发起电源状态切换得实体(可以是用户空间电源管理进程,也可以是内核线程,简称C),在发起状态切换前,读取系统的wakeup counts(该值记录了当前的wakeup event总数), 并将读取的counts告知wakeup events framework。
2) wakeup events framework记录该counts到一个全局变量中(saved_count)l
3) 随后C发起电源状态切换(如STR), 执行suspend过程
4) 在suspend过程中,wakeup events framework照旧工作(直到系统关闭), 上报wakeup events, 增加wakeup events counts.
5) suspend执行的一些时间点, 会调用wakeup events framework提供的接口(pm_wakeup_pending), 检查是否有wakeup有没有处理
6) 检查逻辑很简单,就是比较当前的wakeup events和saved wakeup counts(C发起电源状态切换时的counts), 如果不同,就要终止suspend。
wakeup count的实现逻辑
一个例子:
在进行代码分析之前,我们先用伪代码的方式,写出一个利用wakeup count进行suspend操作的例子, 然后基于该例子,分析相关的实现。
do {
ret = read(&cnt, "/sys/power/wakeup_count");
if (ret) {
ret = write(cnt, "/sys/power/wakeup_count");
} else {
countine;
}
} while (!ret);
write("mem", "/sys/power/state");
/* goto here after wakeup */
该例子表示:
a) 读取wakeup count的值,如果成功,将读取的值回写。否则说明有正在处理的wakeup event coutinue
b) 回写后, 判断返回值是否成功,如果不成功(说明读写过程中发生了wakeup events), 继续读写, 直到成功。成功后,可以触发电源状态切换。
/sys/power/wakeup_count
wakeup_count文件实在kernel/power/main.c利用power_attr注册的,如下(大家可以仔细阅读一下那一大段注释,内核很多注释写的非常好, 而好的注释就是软件功力的体现):
The 'wakeup_count' attribute, along with the functions defined in drivers/base/power/wakeup.c, provides a means by which wakeup events can be handled in a non-racy way.
“wakeup_count”属性以及 drivers/base/power/wakeup.c 中定义的函数提供了一种以非 racy 方式处理唤醒事件的方法。
If a wakeup event occurs when the system is in a sleep state, it simply is woken up.
In turn, if an event that would wake the system up from a sleep state occurs when it is undergoing a transition to that sleep state, the transition should be aborted.
Moreover, if such an event occurs when the system is in the working state, an attempt to start a transition to the given sleep state should fail during certain period after the detection of the event.
Using the 'state' attribute alone is not sufficient to satisfy these requirements, because a wakeup event may occur exactly when 'state'is being written to and may be delivered to user space right before it is frozen, so the event will remain only partially processed until the system is woken up by another event.
In particular, it won't cause the transition to a sleep state to be aborted.
如果在系统处于睡眠状态时发生唤醒事件,则只是将其唤醒。
反过来,如果在系统正在过渡到该睡眠状态时发生将系统从睡眠状态唤醒的事件,则应中止该转换。
此外,如果在系统处于工作状态时发生此类事件,则在检测到事件后的某些时间段内,尝试开始转换到给定睡眠状态应该失败。
仅使用 'state' 属性不足以满足这些要求,因为唤醒事件可能恰好在 'state' 被写入时发生,并且可能会在冻结之前传递到用户空间,因此该事件将仅保持部分处理状态,直到系统被另一个事件唤醒。
特别是,它不会导致过渡到睡眠状态中止。
This difficulty may be overcome if user space uses 'wakeup_count' before writing to 'state'. It first should read from 'wakeup_count' and store the read value.
Then, after carrying out its own preparations for the system transition to a sleep state, it should write the stored value to 'wakeup_count'. If that fails, at least one wakeup event has occurred since 'wakeup_count' was read and 'state' should not be written to.
Otherwise, it is allowed to write to 'state', but the transition will be aborted if there are any wakeup events detected after 'wakeup_count' was written to.如果用户空间在写入“state”之前使用“wakeup_count”,则可以克服此困难。 它首先应该从“wakeup_count”读取并存储读取值。
然后,在为系统过渡到睡眠状态进行自己的准备工作后,它应该将存储的值写入“wakeup_count”。 如果失败,则自读取“wakeup_count”以来至少发生了一个唤醒事件,并且不应写入“state”。
否则,允许写入“state”,但如果在写入“wakeup_count”后检测到任何唤醒事件,则转换将中止。
static ssize_t wakeup_count_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
unsigned int val;
return pm_get_wakeup_count(&val, true) ?
sprintf(buf, "%u\n", val) : -EINTR;
}
static ssize_t wakeup_count_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
unsigned int val;
int error;
error = pm_autosleep_lock();
if (error)
return error;
if (pm_autosleep_state() > PM_SUSPEND_ON) {
error = -EBUSY;
goto out;
}
error = -EINVAL;
if (sscanf(buf, "%u", &val) == 1) {
if (pm_save_wakeup_count(val))
error = n;
else
pm_print_active_wakeup_sources();
}
out:
pm_autosleep_unlock();
return error;
}
实现很简单:read时,直接调用pm_get_wakeup_count(注意第2个参数); write直接调用pm_save_wakeup_count(注意用户空间的wakeup count功能和auto sleep互斥)。 这两个接口均是wakeup events framework提供的接口。
pm_get_wakeup_count
bool pm_get_wakeup_count(unsigned int *count, bool block)
{
unsigned int cnt, inpr;
if (block) {
DEFINE_WAIT(wait);
for (;;) {
prepare_to_wait(&wakeup_count_wait_queue, &wait,
TASK_INTERRUPTIBLE);
split_counters(&cnt, &inpr);
if (inpr == 0 || signal_pending(current))
break;
schedule();
}
finish_wait(&wakeup_count_wait_queue, &wait);
}
split_counters(&cnt, &inpr);
*count = cnt;
return !inpr;
}
该接口有两个参数,一个是保存返回的count的指针,另一个指示是否block, 具体请参考代码逻辑:
a) 如果block为false, 直接读取registered wakeup events和wakeup events in progress两个counter值, 将register wakeup events交给第一个参数, 并返回wakeup events in progress的状态(若返回false, 说明当前有wakeup events正在处理, 不适合suspend)。
b) 如果block为true, 定义一个等待队列, 等待wakeup in progress为0, 再返回counter.
注: 由伪代码可知,sysfs发起的read动作,block为true, 所以如果有正在处理的wakeup events, read进程会阻塞。其他模块(如auto sleep)发起的read,则可能不需要阻塞。
pm_save_wakeup_count
bool pm_save_wakeup_count(unsigned int count)
{
unsigned int cnt, inpr;
unsigned long flags;
events_check_enabled = false;
spin_lock_irqsave(&events_lock, flags);
split_counters(&cnt, &inpr);
if (cnt == count && inpr == 0) {
saved_count = count;
events_check_enabled = true;
}
spin_unlock_irqrestore(&events_lock, flags);
return events_check_enabled;
}
1) 注意这个变量, events_check_enabled, 如果它不为真,pm_wakeup_pending接口直接返回false, 意味着如果不利用wakeup count功能, suspend过程中不会做任何wakeup events检查,也就不会进行任何的同步。
2)取出当前的registered wakeup events, waakeup 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变量中。
/sys/power/state
再回忆一下suspend的流程,再suspend接口中,suspend前的最后一刻,会调用pm_wakeup_pending接口,代码如下:
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
...
error = syscore_suspend();
if (!error) {
*wakeup = pm_wakeup_pending();
if (!(suspend_test(TEST_CORE) || *wakeup)) {
error = suspend_ops->enter(state);
events_check_enabled = false;
}
syscore_resume();
}
...
}
在write wakeup_count到调用pm_wakeup_pending这一段时间内,wakeup events framework会照常产生wakeup events, 因此如果pending返回true, 则不能“enter”, 就终止suspend。
注2: wakeup后,会清楚events_check_enabled标记。
之前介绍过的pm_wakeup_pending, 让我们再看一遍
bool pm_wakeup_pending(void)
{
unsigned long flags;
bool ret = false;
spin_lock_irqsave(&events_lock, flags);
if (events_check_enabled) {
unsigned int cnt, inpr;
split_counters(&cnt, &inpr);
ret = (cnt != saved_count || inpr > 0);
events_check_enabled = !ret;
}
spin_unlock_irqrestore(&events_lock, flags);
if (ret) {
pr_info("PM: Wakeup pending, aborting suspend\n");
pm_print_active_wakeup_sources();
}
return ret || pm_abort_suspend;
}
a) 首先会判断events_check_enabled是否有效, 无效直接返回false。
b) 获得cnt和inpr, 如果cnt不等于saved_count(说明这段时间有events产生), 或者inpr不为0(说明有events正在被处理), 返回true(告诉调用者,放弃吧,时机不到)。 同时清除events_check_enabled的状态。
c) 否则,返回false(放心睡吧),同时保持event_check_enabled的置位状态(以免pm_wakeup_pending再次调用)