我们知道,系统处于suspend状态,可以通过wakeup events唤醒。具体的wakeup events可以是按键按下, 可以是充电器插入,等等。 如果在suspend的过程中,产生了wakeup events,怎么办?答案很肯定, "wakeup"系统,由于此时系统没有真正的suspend, 所以这个”wakeup"只是假动作, 实际上只是终止suspend.
但是由于系统在suspend的过程中,会进行process freeze, device suspend等操作,而这些操作可能导致内核或用户空间不能及时获取wakeup events, 从而使系统不能正确wakeup, 这就是wakeup events framework要解决的问题:
system suspend和system wakeup events之间的同步问题。
情况1: 内核空间的同步
wakeup events产生后,通常以中断的形式通知device driver。 drvier会处理events, 处理的过程中,系统不能suspend.
注1: 同步问题只存在于中断开启的情况, 因为若中断关闭,就不会产生wakeup events,就不存在同步的概念。
情况2: 用户空间的同步
一般情况下,driver对wakeup events处理后,会交给用户空间程序继续处理,处理的过程,也不允许suspend. 这又可以分为两种情况:
1) 进行后续处理的用户进程,根本没有机会被调度,即该wakeup events无法上报到用户空间
2) 进行后续处理的用户进程被调度, 处理的过程中(以及处理结束后,决定终止suspend操作), 系统不能suspend
因此, wakeup events framework就包括三大功能
1. 解决内核空间的同步问题(framework的核心功能)
2. 解决用户空间同步问题的情景1(wakeup count功能)
3. 解决用户空间同步问题的情景2(wake lock功能)
注2:
用户空间同步的两种情况,乍一看,非常合乎情理,kernel 你得好好处理!事实上该同步问题还牵涉到另一个比较有争议的话题:日常的电源管理机制。是否要基于suspend实现?系统何时进入低功耗状态,应该由谁决定?kernel还是用户空间
这个最终会决定是否存在用户空间同步问题。但是,在当前这个时间点上,对这个话题,Linux kernel developers和Android developer持相反的观点。这也造成了wakeup events framework在实现上的撕裂。Kernel的本意是不愿处理用户空间同步问题的,但为了兼容Android平台,不得不增加相关的功能(Wakeup count和Wake lock)。
下面图片描述了wakeup events framework的architecture
图片中红色边框的block是wakeup events相关的block:
抽象wakeup source和wakeup event的概念:
向各个device driver提供wakeup source的注册,使能等接口
向各个device driver提供wakeup event的上报,停止等接口
向上层的PM core(包括wakeup count, auto sleep, suspend, hibernate等模块)提供wakeup event的查询接口, 以判断是否可以suspend, 是否需要终止正在进行的suspend
注3: 在linux kernel 看来,power是系统的核心资源,不应开放给用户程序随意访问,(wake lock机制违背了这个原则)。 而在运行时的电源管理过程中,系统何时进入了低功耗状态,也不是用户空间程序能决定的(auto sleep中枪了)。因此, kernel对上述功能的支持,非常的不乐意, 我们可以从kernel/power/main.c中sysfs attribute文件窥见一斑(只要定义了PM_SLEEP, 就一定支持wakeup count功能, 但auto sleep和wake lock功能,由另外的宏控制):
power_attr(pm_freeze_timeout);
#endif /* CONFIG_FREEZER*/
static struct attribute * g[] = {
&state_attr.attr,
#ifdef CONFIG_PM_TRACE
&pm_trace_attr.attr,
&pm_trace_dev_match_attr.attr,
#endif
#ifdef CONFIG_PM_SLEEP
&pm_async_attr.attr,
&wakeup_count_attr.attr,
#ifdef CONFIG_PM_AUTOSLEEP
&autosleep_attr.attr,
#endif
#ifdef CONFIG_PM_WAKELOCKS
&wake_lock_attr.attr,
&wake_unlock_attr.attr,
#endif
#ifdef CONFIG_PM_DEBUG
&pm_test_attr.attr,
#endif
#ifdef CONFIG_PM_SLEEP_DEBUG
&pm_print_times_attr.attr,
&pm_wakeup_irq_attr.attr,
#endif
#endif
#ifdef CONFIG_FREEZER
&pm_freeze_timeout_attr.attr,
#endif
NULL,
};
在kernel中,可以唤醒系统的只有设备(struct device), 但并不是每个设备都具有唤醒能力,哪些具有唤醒能力的设备称作wakeup source。 介绍struct devcie接口时,涉及到一个struct dev_pm_info类型的power变量。
struct device {
...
struct dev_pm_info power;
...
};
结构中有一个power变量,保存了和wakeup event相关的信息, 让我们接着看一下struct dev_pm_info数据结构
struct dev_pm_info {
...
unsigned int can_wakeup:1;
...
#ifdef CONFIG_PM_SLEEP
...
struct wakeup_source *wakeup;
...
#else
unsigned int should_wakeup:1;
#endif
};
can_wakeup,标识本设备是否具有唤醒能力。 只有具备唤醒能力的设备,才会在sysfs中有一个power目录, 用于提供所有的wakeup信息, 这些信息是以struct wakeup source形式组织起来的。也就是上面的wakeup 指针。 具体的信息看struct wakeup source的定义
struct wakeup_source {
const char *name;
struct list_head entry;
spinlock_t lock;
struct wake_irq *wakeirq;
struct timer_list timer;
unsigned long timer_expires;
ktime_t total_time;
ktime_t max_time;
ktime_t last_time;
ktime_t start_prevent_time;
ktime_t prevent_sleep_time;
unsigned long event_count;
unsigned long active_count;
unsigned long relax_count;
unsigned long expire_count;
unsigned long wakeup_count;
bool active:1;
bool autosleep_enabled:1;
};
因此,一个wakeup source代表了一个具有唤醒能力的设备,也称该设备为一个wakeup source.该结构中,各个字段的意义如下:
name,该wakeup source的名称,一般为对应的device name(有个例外,就是wakelock);
entry,用于将所有的wakeup source挂在一个链表中;
timer、timer_expires,一个wakeup source产生了wakeup event,称作wakeup source activate,wakeup event处理完毕后(不再需要系统为此保持active),称作deactivate。activate和deactivate的操作可以由driver亲自设置,也可以在activate时,指定一个timeout时间,时间到达后,由wakeup events framework自动将其设置为deactivate状态。这里的timer以及expires时间,就是用来实现该功能;
total_time,该wakeup source处于activate状态的总时间(可以指示该wakeup source对应的设备的繁忙程度、耗电等级);
max_time,该wakeup source持续处于activate状态的最大时间(越长越不合理);
last_time,该wakeup source上次active的开始时间;
start_prevent_time,该wakeup source开始阻止系统自动睡眠(auto sleep)的时间点;
prevent_sleep_time,该wakeup source阻止系统自动睡眠的总时间;
event_count,wakeup source上报的event个数;
active_count,wakeup source activate的次数;
relax_count, wakeup source deactivate的次数;
expire_count,wakeup source timeout到达的次数;
wakeup_count,wakeup source终止suspend过程的次数;
active,wakeup source的activate状态;
autosleep_enabled,记录系统auto sleep的使能状态(每个wakeup source都重复记录这样一个状态,这种设计真实不敢恭维!)。
wakeup source代表一个具有唤醒能力的设备,该设备产生的可以唤醒系统的事件,就称作wakeup event。 当wakeup source产生切换为activate状态; 当wakeup event处理完毕后, 要切换为deactivate状态。 因此我们再来理解一下几个wakeup source比较混淆的变量: event_count, active_count和wakeup count:
event_count, wakeup source产生的 wakeup event的个数
active_count, 产生的wakeup event时, wakeup source需要切换到activate状态,但并不是每次都要切换,因此之前有可能已经处于activate状态了。 因此active_count可能小于event_count, 换句话说,很有可能在前一个wakeup event没被处理完时,又产生了一个。 这从一定程度上反映了wakeup source所代表的设备的繁忙程度;
wakeup count: wakeup source在suspend过程中产生wakeup event的话, 就会终止suspend过程, 该变量记录了wakeup source终止suspend 过程的次数(如果发现系统总是suspend失败, 检查一下各个wakeup source的该变量, 就可以知道问题出在谁身上了)。
在drivers/base/power/wakeup.c中,有几个比较重要的计数器, 时wakeup events framework的实现基础,包括:
记录了系统运行以来产生的所有的wakeup event的个数,在wakeup source上报event时+1.
这个counter对解决用户空间同步问题很有帮助, 因为一般情况下(无论是用户程序主动suspend, 还是autosleep), 由专门的进程(或线程)触发suspend。 当这个进程判断系统满足suspend条件,决定suspend时,会记录一个counter值(saved_count), 在后面suspend的过程中,如果系统发现counter有变,就说明系统产生了新的wakeup event, 这样就可以终止suspend
记录正在被处理的event个数
当wakeup source产生wakeup event时,会通过wakeup events framework提供的接口将wakeup source设置为activate状态。 当该event处理结束后,设置为deactivate状态。activate到deactivate的区间,表示该event正在被处理。
当系统中有任何正在被处理的wakeup event时,则不允许suspend,如果suspend正在运行,则要终止。
思考一个问题: registered wakeup events在什么时候增加?答案是在wakeup events in progress减小时,因为已经完整的处理完一个event了,可以记录在案了。
基于这种特性,,kernel将他俩合并成一个为32位的整型数,以原子操作的形式,一起更新。 这种设计巧妙的令人叫绝,值得我们学习。具体如下:
/*
* Combined counters of registered wakeup events and wakeup events in progress.
* They need to be modified together atomically, so it's better to use one
* atomic variable to hold them both.
*/
static atomic_t combined_event_count = ATOMIC_INIT(0);
#define IN_PROGRESS_BITS (sizeof(int) * 4)
#define MAX_IN_PROGRESS ((1 << IN_PROGRESS_BITS) - 1)
static void split_counters(unsigned int *cnt, unsigned int *inpr)
{
unsigned int comb = atomic_read(&combined_event_count);
*cnt = (comb >> IN_PROGRESS_BITS);
*inpr = comb & MAX_IN_PROGRESS;
}
定义和读取:
cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);
wakeup events in progress减一,register wakeup events 加一, 这个设计牛皮。
在wakeup_source_activate中
cec = atomic_inc_return(&combined_event_count);
wakeup events in progress 加1
wakeup events framework的核心功能体现在它给底层的设备驱动程序提供用于上报wakeup event的接口,这些接口根据数据操作对象可分为两类,具体如下:
类型一:(操作对象位wakeup source, 编写设备驱动时,一般不会使用)
/* include/linux/pm_wakeup.h */
extern void __pm_stay_awake(struct wakeup_source *ws);
通知PM core, ws产生了wakeup event, 切正在处理, 因此不允许系统suspend(stay suspend)
extern void __pm_relax(struct wakeup_source *ws);
通知PM core, ws没有正在处理的wakeup event, 允许系统suspend(relax)
extern void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec);
上面两个接口的功能组合,通知PM core, ws产生了wakeup event, 会在msec毫秒内处理结束(wakeup events framework自动relax
注4: __pm_stay_awake和__pm_relax应成对调用
注5: 上面三个接口,均可以在中断上下文调用
类型二:(操作对象为device, 为设备驱动的常用接口)
/* include/linux/pm_wakeup.h */
extern int device_wakeup_enable(struct device *dev);
extern int device_wakeup_disable(struct device *dev);
对于can_wakeup的设备,使能或禁止wakup功能,主要是对struct wakeup_source结构的相关操作
extern void device_set_wakeup_capable(struct device *dev, bool capable);
设置dev的can_wakeup标志(enable或disable), 并增加或移除该设备在sysfs相关的power文件
extern int device_init_wakeup(struct device *dev, bool val);
设置devde的can_wakeup标志, 若是enable, 同时调用device_wakeup_enable使能wakeup功能
extern int device_set_wakeup_enable(struct device *dev, bool enable);
extern void pm_stay_awake(struct device *dev);
extern void pm_relax(struct device *dev);
extern void pm_wakeup_event(struct device *dev, unsigned int msec);
直接调用上面的wakeup source操作接口,操作device的struct wakeup_source变量,处理wakeup events
该接口位于/drivers/base/power/wakeup.c
/**
* device_set_wakeup_capable - Set/reset device wakeup capability flag.
* @dev: Device to handle.
* @capable: Whether or not @dev is capable of waking up the system from sleep.
*
* If @capable is set, set the @dev's power.can_wakeup flag and add its
* wakeup-related attributes to sysfs. Otherwise, unset the @dev's
* power.can_wakeup flag and remove its wakeup-related attributes from sysfs.
*
* This function may sleep and it can't be called from any context where
* sleeping is not allowed.
*/
void device_set_wakeup_capable(struct device *dev, bool capable)
{
if (!!dev->power.can_wakeup == !!capable)
return;
if (device_is_registered(dev) && !list_empty(&dev->power.entry)) {
if (capable) {
if (wakeup_sysfs_add(dev))
return;
} else {
wakeup_sysfs_remove(dev);
}
}
dev->power.can_wakeup = capable;
}