Linux休眠唤醒之wakeup events framework

wakeup events framework

我们知道,系统处于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之间的同步问题。

wakeup events framework的功能总结

情况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

下面图片描述了wakeup events framework的architecture
Linux休眠唤醒之wakeup events framework_第1张图片

图片中红色边框的block是wakeup events相关的block:

  1. wakeup events framework core, 在driver/base/power/wakeup.c中实现,提供了wakeup events framework的核心功能, 包括:
抽象wakeup source和wakeup event的概念:
向各个device driver提供wakeup source的注册,使能等接口
向各个device driver提供wakeup event的上报,停止等接口
向上层的PM core(包括wakeup count, auto sleep, suspend, hibernate等模块)提供wakeup event的查询接口, 以判断是否可以suspend, 是否需要终止正在进行的suspend
  1. wakeup event framework sysfs, 将设备的wakeup信息,以sysfs的形式提供到用户空间,供用户空间程序查询,配置。在driver/base/power/sysfs.c中实现。
  2. wakeup lock/unlock, 为了兼容Android旧的wakeup lock机制而留下的一个后门, 扩展wakeup events framework的功能, 允许用户空间程序报告/停止wakeup events。换句话说,该后门允许用户空间的任意程序决定系统是否可以休眠。
  3. wakeup count, 基于wakeup events framework, 解决用户空间的同步问题
  4. autosleep, 允许系统在没有活动时(即一段时间内, 没有产生wakeup event), 自动休眠。

注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,
};
代码分析
wakeup source和wakeup event

在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的该变量, 就可以知道问题出在谁身上了)。
几个counters

在drivers/base/power/wakeup.c中,有几个比较重要的计数器, 时wakeup events framework的实现基础,包括:

  1. register wakeup events和saved_count

记录了系统运行以来产生的所有的wakeup event的个数,在wakeup source上报event时+1.

这个counter对解决用户空间同步问题很有帮助, 因为一般情况下(无论是用户程序主动suspend, 还是autosleep), 由专门的进程(或线程)触发suspend。 当这个进程判断系统满足suspend条件,决定suspend时,会记录一个counter值(saved_count), 在后面suspend的过程中,如果系统发现counter有变,就说明系统产生了新的wakeup event, 这样就可以终止suspend

  1. wakeup event in progress

记录正在被处理的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 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
device_set_wakeup_capable

该接口位于/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;
}

你可能感兴趣的:(linux)